From 7dec14367cf4ec24506c3ec004d65850eb05112d Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:26:23 +0100 Subject: [PATCH 01/45] ICRC-107 initial draft --- ICRCs/ICRC-107/ICRC-107.md | 175 +++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 ICRCs/ICRC-107/ICRC-107.md diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md new file mode 100644 index 00000000..65fbb270 --- /dev/null +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -0,0 +1,175 @@ +| Status | +|:------:| +| Draft | + +# ICRC-107: Fee Collection + +## 1. Introduction + +Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collection, often charging fees for **transfer** transactions but not for **approve** transactions. However, there is currently no standardized way to: + +1. Indicate and configure **who** should collect fees (or if fees are burned). +2. Record fee collection information on existing ledgers. +3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee information. + +**ICRC-107** aims to address this gap by defining: +- A set of **metadata entries** that indicate how fees are collected. +- A **minimal interface** for setting and enabling fee collection. +- **A backward-compatible standard** for **populating and understanding fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. +- Guidance on how to **interpret fee collection fields**, as recorded in blocks, in a consistent way. + +### Non-Requirements +This standard does **not** address or require: +- Unsetting or removing a previously configured fee collector. +- Having multiple fee collectors (e.g., distinct collectors for transfers vs. approvals). +- Generalizing the mechanism for future block types beyond transfers/approvals. +- Prescribing detailed behavior for ledger upgrades that enable fee collection. + +By focusing on **backward-compatible** enhancements and clear metadata, this standard allows ledgers to adopt fee collection with minimal disruption, while giving external tools a reliable way to parse and display fee-related data. + +--- + +## 2. Metadata + +A ledger implementing **ICRC-107** **MUST** include the following entry in the output of the `icrc1_supported_standards` method (indented for display): + + record { + name = "ICRC-107"; + url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107" + } + +Additionally, the ledger **MUST** provide (at minimum) these metadata entries, retrievable via the `icrc1_metadata` method: + +1. **`icrc107_fee_collector` (text)** + The textual representation of the principal (or account) designated as the fee collector. + +2. **`icrc107_fee_collection_transfers` (bool)** + Indicates whether fees are collected on **transfer** transactions (`true`) or not (`false`). + +3. **`icrc107_fee_collection_approvals` (bool)** + Indicates whether fees are collected on **approve** transactions (`true`) or not (`false`). + +If a ledger has not set a fee collector, it may return an empty string or omit the metadata key for `icrc107_fee_collector`. Similarly, if fees are never collected for transfers or approvals, the corresponding booleans should be `false` or omitted. + +--- + +## 3. Fee Collection Management Interface + +A ledger **implementing ICRC-107** **SHOULD** expose the following methods to programmatically configure and enable fee collection. + + + // Sets the fee collector principal/account. + // If not set, or set to an empty string, fees are effectively burned. + icrc107_set_fee_collector: (text) -> (); + + // Enables fee collection for transfer transactions. + // Returns the currently set fee collector if successful, or an error if none is set. + icrc107_turn_on_fee_collector_transfers: () -> (variant { + Ok : text; // The fee collector principal + Err : text; // Error message + }); + + // Enables fee collection for approve transactions. + // Returns the currently set fee collector if successful, or an error if none is set. + icrc107_turn_on_fee_collector_approvals: () -> (variant { + Ok : text; // The fee collector principal + Err : text; // Error message + }); + +### Method Semantics + +- **`icrc107_set_fee_collector(fee_collector: text)`** + Updates the ledger’s metadata (`icrc107_fee_collector`) to the specified principal/account. + If this method is never called, fees are assumed to be burned by default. + +- **`icrc107_turn_on_fee_collector_transfers()`** + Sets `icrc107_fee_collection_transfers` to `true`. + If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err` describing why it cannot be enabled. + +- **`icrc107_turn_on_fee_collector_approvals()`** + Sets `icrc107_fee_collection_approvals` to `true`. + If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err`. + +These endpoints allow ledger administrators (or canister controllers) to enable fee collection for different transaction types once the fee collector account is specified. + +--- + +## 4. Fee Information in Blocks + +This section defines how **ICRC-107** determines whether a fee is collected or burned **exclusively** from information **contained in the blocks themselves**. No reliance on external metadata (e.g., `icrc107_fee_collector`) is assumed. + +### 4.1 Determining the Fee + +When reading or processing a block that may carry a fee, the ledger or external tools **SHOULD** apply the following logic to identify the final fee amount: + +1. **Check for an Explicit Transaction Fee (`tx.fee`)** + - If the transaction itself contains a `fee` field (e.g., `tx.fee`), that value **SHOULD** take precedence as the fee to be applied. + +2. **Fallback to `effective_fee`** + - If no `tx.fee` is provided, the ledger or tool **SHOULD** refer to the block’s `effective_fee` field (or an equivalent ledger-defined field) to determine the fee. + +3. **No Explicit Fee** + - If neither `tx.fee` nor `effective_fee` is available, then the ledger MAY default the fee to zero or burn any implied fee as per its internal policy. + - An **ICRC-107**-compliant ledger interprets the absence of a specified fee as no fee collected, or a fee of zero. + +### 4.2 Fee Collection Logic + +After determining the fee amount, **ICRC-107** specifies how to decide **whether** a fee is burned or collected, and if collected, **who** receives it, **based solely on block contents**: + +1. **Check for Fee Collector Fields** + - If the block contains a direct account field (e.g., `fee_col`) or a reference field (e.g., `fee_col_block`, `app_fee_col_block`), the fee is be collected by the referenced account. + - If **neither** a direct account field nor a reference field is present, the fee is burned. + +2. **Resolving a Reference to a Previous Block** + - If a reference field is used (`fee_col_block` or `app_fee_col_block`), the ledger or tool **MUST** locate that earlier block and extract the account specified there (e.g., `fee_col`). + - If the referenced block does not contain a valid collector the fee is burned. + + +### 4.3 Recording Fee Collection in Blocks for ICRC-1 and ICRC-2 Transactions + +The block schema for ICRC-1 and ICRC-2 transactions are specified in the ICRC-3 standard. This section explains how to extend those schemas to include backwards compatible fee collection information. + +#### 4.3.1 Transfer Blocks + +Transfer blocks in ICRC-based ledgers can be identified by one of the following: +- **`btype = "1xfer"`** (ICRC-1 style), +- **`btype = "2xfer"`** (ICRC-2 style), +- or a **transfer transaction** (`tx.op = "xfer"`). + +To **record and interpret** fee-collection information in these blocks: + +- **`fee_col` (Account)** + - A direct indicator of which account receives the collected fee. +- **`fee_col_block` (nat)** + - A pointer to an earlier block where `fee_col` is explicitly set. + +**How to Determine Who Collects the Fee** +1. If `fee_col` is present in the block, that account is the collector. +2. If `fee_col` is absent but `fee_col_block` is present, fetch the referenced block; the `fee_col` in that block is the collector. +3. If neither is present, **no** collector can be inferred, and the fee is burned. + +#### 4.3.2 Approve Blocks + +Approve blocks in ICRC-based ledgers can be identified by: +- **`btype = "2approve"`** (ICRC-2 style), +- or a **transaction object** (`tx.op = "approve"`). + +To **record and interpret** fee-collection information in these blocks: + +- **`fee_col` (Account)** + - A direct indicator of which account receives the collected fee for the approval operation. +- **`app_fee_col_block` (nat)** + - A pointer to an earlier block where `fee_col` is explicitly set for approves. + +**How to Determine Who Collects the Fee** +1. If `fee_col` is present in the block, that account is the collector. +2. If `fee_col` is absent but `app_fee_col_block` is present, fetch the referenced block; the `fee_col` in that block is the collector. +3. If neither is present, **no** collector can be inferred, and the fee is burned. + +--- + +By strictly focusing on **block-level fields**: + +- **Ledgers** remain free to implement or omit references in blocks while still following a consistent approach for collecting or burning fees. +- **External tools** (wallets, explorers) can determine the collector without requiring out-of-band metadata or additional method calls. If no collector info is embedded (directly or via a reference), the fee is considered burned. +- This specification thereby ensures **ICRC-1**, **ICRC-2**, and **ICRC-3** ledgers can remain consistent with **ICRC-107** using only the fields present in each block. From f45dbed581b1c7bf4b19d04e841fcb8d2949ef11 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:46:31 +0100 Subject: [PATCH 02/45] bool -> text for metadata --- ICRCs/ICRC-107/ICRC-107.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 65fbb270..e7239409 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -43,13 +43,13 @@ Additionally, the ledger **MUST** provide (at minimum) these metadata entries, r 1. **`icrc107_fee_collector` (text)** The textual representation of the principal (or account) designated as the fee collector. -2. **`icrc107_fee_collection_transfers` (bool)** - Indicates whether fees are collected on **transfer** transactions (`true`) or not (`false`). +2. **`icrc107_fee_collection_transfers` (text)** + Indicates whether fees are collected on **transfer** transactions ("true") or not ("false"). -3. **`icrc107_fee_collection_approvals` (bool)** - Indicates whether fees are collected on **approve** transactions (`true`) or not (`false`). +3. **`icrc107_fee_collection_approvals` (text)** + Indicates whether fees are collected on **approve** transactions ("true") or not ("false"). -If a ledger has not set a fee collector, it may return an empty string or omit the metadata key for `icrc107_fee_collector`. Similarly, if fees are never collected for transfers or approvals, the corresponding booleans should be `false` or omitted. +If a ledger has not set a fee collector, it may return an empty string or omit the metadata key for `icrc107_fee_collector`. Similarly, if fees are not collected for transfers or approvals, the values for the corresponding metadata should be "false" or omitted. --- From 63bff2088d18667661e160145804079e4babd8b1 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:48:16 +0100 Subject: [PATCH 03/45] bool -> text for metadata --- ICRCs/ICRC-107/ICRC-107.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index e7239409..73ec422e 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -83,11 +83,11 @@ A ledger **implementing ICRC-107** **SHOULD** expose the following methods to pr If this method is never called, fees are assumed to be burned by default. - **`icrc107_turn_on_fee_collector_transfers()`** - Sets `icrc107_fee_collection_transfers` to `true`. + Sets `icrc107_fee_collection_transfers` to "true". If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err` describing why it cannot be enabled. - **`icrc107_turn_on_fee_collector_approvals()`** - Sets `icrc107_fee_collection_approvals` to `true`. + Sets `icrc107_fee_collection_approvals` to "true". If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err`. These endpoints allow ledger administrators (or canister controllers) to enable fee collection for different transaction types once the fee collector account is specified. From b5bae30907b2ccb0ef4ea971116e9fec9fda7937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BW=C2=B0MacAir?= Date: Tue, 4 Feb 2025 11:18:27 +0100 Subject: [PATCH 04/45] option 1 from the discussion --- ICRCs/ICRC-107/ICRC-107.md | 280 ++++++++++++++++++++----------------- 1 file changed, 151 insertions(+), 129 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 73ec422e..fdccee4d 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,175 +1,197 @@ -| Status | -|:------:| -| Draft | -# ICRC-107: Fee Collection +# ICRC-107: Fee Collection (Purely Block-Based) -## 1. Introduction +## 1. Introduction & Motivation Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collection, often charging fees for **transfer** transactions but not for **approve** transactions. However, there is currently no standardized way to: 1. Indicate and configure **who** should collect fees (or if fees are burned). -2. Record fee collection information on existing ledgers. -3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee information. +2. Record fee collection information directly in ledger blocks. +3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee information in a consistent way. **ICRC-107** aims to address this gap by defining: -- A set of **metadata entries** that indicate how fees are collected. -- A **minimal interface** for setting and enabling fee collection. -- **A backward-compatible standard** for **populating and understanding fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. -- Guidance on how to **interpret fee collection fields**, as recorded in blocks, in a consistent way. +- A mechanism to specify **fee collection details** (collector account and applicable operations) directly in blocks. +- A **backward-compatible standard** for **recording and interpreting fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. +- Clear rules on how fee collection information evolves over time and how subsequent blocks rely on prior updates. -### Non-Requirements -This standard does **not** address or require: -- Unsetting or removing a previously configured fee collector. -- Having multiple fee collectors (e.g., distinct collectors for transfers vs. approvals). -- Generalizing the mechanism for future block types beyond transfers/approvals. -- Prescribing detailed behavior for ledger upgrades that enable fee collection. - -By focusing on **backward-compatible** enhancements and clear metadata, this standard allows ledgers to adopt fee collection with minimal disruption, while giving external tools a reliable way to parse and display fee-related data. +This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. --- -## 2. Metadata - -A ledger implementing **ICRC-107** **MUST** include the following entry in the output of the `icrc1_supported_standards` method (indented for display): - - record { - name = "ICRC-107"; - url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107" - } +## 2. Fee Collection Mechanism -Additionally, the ledger **MUST** provide (at minimum) these metadata entries, retrievable via the `icrc1_metadata` method: +### 2.1 Fields in Blocks -1. **`icrc107_fee_collector` (text)** - The textual representation of the principal (or account) designated as the fee collector. +A block **MAY** include the following fields to define or update fee collection settings: -2. **`icrc107_fee_collection_transfers` (text)** - Indicates whether fees are collected on **transfer** transactions ("true") or not ("false"). +- **`fee_col`**: An **account** (ICRC account format) to which fees are paid. + **Format**: + ```candid + record { + principal: principal; + subaccount: opt blob; + } + ``` +- **`fee_ops`**: An **array of block types** (strings) for which fees **should** be collected. This standard specifies two types of blocks for which fee collection can occur: "transfer" and "approve". -3. **`icrc107_fee_collection_approvals` (text)** - Indicates whether fees are collected on **approve** transactions ("true") or not ("false"). +When a block includes either or both fields, it **overrides** the previously known settings from that point onward. -If a ledger has not set a fee collector, it may return an empty string or omit the metadata key for `icrc107_fee_collector`. Similarly, if fees are not collected for transfers or approvals, the values for the corresponding metadata should be "false" or omitted. +#### 2.1.1 Defaults +- If `fee_ops` is **omitted**, it defaults to `["transfer"]`. +- If `fee_col` is **omitted**, the previously known collector remains in effect (or none if it was never set). --- -## 3. Fee Collection Management Interface - -A ledger **implementing ICRC-107** **SHOULD** expose the following methods to programmatically configure and enable fee collection. - +## 3. Fee Computation - // Sets the fee collector principal/account. - // If not set, or set to an empty string, fees are effectively burned. - icrc107_set_fee_collector: (text) -> (); +### 3.1 Determining the Fee Amount - // Enables fee collection for transfer transactions. - // Returns the currently set fee collector if successful, or an error if none is set. - icrc107_turn_on_fee_collector_transfers: () -> (variant { - Ok : text; // The fee collector principal - Err : text; // Error message - }); +The fee amount in any block can be derived via: +- An explicit `tx.fee` field in the block’s transaction. +- A fallback `effective_fee` or other ledger-defined field if `tx.fee` is absent. +- Zero if neither is present. - // Enables fee collection for approve transactions. - // Returns the currently set fee collector if successful, or an error if none is set. - icrc107_turn_on_fee_collector_approvals: () -> (variant { - Ok : text; // The fee collector principal - Err : text; // Error message - }); +### 3.2 Collected or Burned -### Method Semantics - -- **`icrc107_set_fee_collector(fee_collector: text)`** - Updates the ledger’s metadata (`icrc107_fee_collector`) to the specified principal/account. - If this method is never called, fees are assumed to be burned by default. - -- **`icrc107_turn_on_fee_collector_transfers()`** - Sets `icrc107_fee_collection_transfers` to "true". - If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err` describing why it cannot be enabled. - -- **`icrc107_turn_on_fee_collector_approvals()`** - Sets `icrc107_fee_collection_approvals` to "true". - If a valid fee collector has been set, returns `Ok(fee_collector)`. Otherwise, returns `Err`. - -These endpoints allow ledger administrators (or canister controllers) to enable fee collection for different transaction types once the fee collector account is specified. +After determining the fee amount, check the **current** configuration (from the most recent update): +1. **Operation Present**: If the block’s operation (e.g., `"transfer"`, `"approve"`, etc.) is in the active list (`fee_ops`), the fee is collected by the active `fee_col`. +2. **Operation Absent**: If the operation is **not** in the active list, the fee is burned. +3. **No Collector**: If no collector has ever been set, fees are burned by default. --- -## 4. Fee Information in Blocks - -This section defines how **ICRC-107** determines whether a fee is collected or burned **exclusively** from information **contained in the blocks themselves**. No reliance on external metadata (e.g., `icrc107_fee_collector`) is assumed. +## 4. Determining the Current Fee Configuration -### 4.1 Determining the Fee +At any block height `h`, to figure out **who** collects fees and for **which** operations: +1. **Traverse blocks** backward (or maintain an in-memory state) until you find the most recent block `< h` that included `fee_col` or `fee_ops`. +2. The fee-collector account from that block is **active**, and the operation list from that block is **active** (defaulting to `["transfer"]` if omitted). +3. If no such block is found, no collector is active and **all fees are burned**. -When reading or processing a block that may carry a fee, the ledger or external tools **SHOULD** apply the following logic to identify the final fee amount: - -1. **Check for an Explicit Transaction Fee (`tx.fee`)** - - If the transaction itself contains a `fee` field (e.g., `tx.fee`), that value **SHOULD** take precedence as the fee to be applied. - -2. **Fallback to `effective_fee`** - - If no `tx.fee` is provided, the ledger or tool **SHOULD** refer to the block’s `effective_fee` field (or an equivalent ledger-defined field) to determine the fee. - -3. **No Explicit Fee** - - If neither `tx.fee` nor `effective_fee` is available, then the ledger MAY default the fee to zero or burn any implied fee as per its internal policy. - - An **ICRC-107**-compliant ledger interprets the absence of a specified fee as no fee collected, or a fee of zero. - -### 4.2 Fee Collection Logic - -After determining the fee amount, **ICRC-107** specifies how to decide **whether** a fee is burned or collected, and if collected, **who** receives it, **based solely on block contents**: - -1. **Check for Fee Collector Fields** - - If the block contains a direct account field (e.g., `fee_col`) or a reference field (e.g., `fee_col_block`, `app_fee_col_block`), the fee is be collected by the referenced account. - - If **neither** a direct account field nor a reference field is present, the fee is burned. - -2. **Resolving a Reference to a Previous Block** - - If a reference field is used (`fee_col_block` or `app_fee_col_block`), the ledger or tool **MUST** locate that earlier block and extract the account specified there (e.g., `fee_col`). - - If the referenced block does not contain a valid collector the fee is burned. +--- +## 5. Examples + +### 5.1 Setting a Collector (Defaults to Transfers Only) + +```json +{ + "block_index": 100, + "timestamp": 1690000000, + "kind": "transaction", + "fee_col": { + "principal": "aaaaa-aa", + "subaccount": null + }, + "transaction": { + "operation": "transfer", + "from": { + "principal": "bbbbb-bb", + "subaccount": null + }, + "to": { + "principal": "ccccc-cc", + "subaccount": null + }, + "amount": 5000, + "fee": 10 + } +} +``` + +- Future blocks that perform a **transfer** pay fees to `aaaaa-aa`. +- **Approve** or other operations are not in the default set, so their fees are burned. + +### 5.2 Block with Approve but No Update + +```json +{ + "block_index": 101, + "timestamp": 1690000500, + "kind": "transaction", + "transaction": { + "operation": "approve", + "from": { + "principal": "bbbbb-bb", + "subaccount": null + }, + "spender": { + "principal": "ccccc-cc", + "subaccount": null + }, + "amount": 3000 + } +} +``` + +- No new fee settings. The **previous** block #100 had `["transfer"]` as the fee-bearing operation. +- This is an `"approve"` operation, **not** in `["transfer"]`, so the fee (if any) is burned. + +### 5.3 Updating Collector and Adding Approves + +```json +{ + "block_index": 150, + "timestamp": 1690001000, + "kind": "transaction", + "fee_col": { + "principal": "zzzzz-zz", + "subaccount": null + }, + "fee_ops": ["transfer", "approve"], + "transaction": { + "operation": "approve", + "from": { + "principal": "bbbbb-bb", + "subaccount": null + }, + "spender": { + "principal": "ccccc-cc", + "subaccount": null + }, + "amount": 6000, + "fee": 5 + } +} +``` + +- At block #150, the collector is changed to `zzzzz-zz`, **and** we explicitly list `["transfer", "approve"]`. +- From block #150 onward, **both** transfers and approves incur fees, credited to `zzzzz-zz`. +- The **approve** operation in this same block has a fee of 5 tokens. -### 4.3 Recording Fee Collection in Blocks for ICRC-1 and ICRC-2 Transactions +--- -The block schema for ICRC-1 and ICRC-2 transactions are specified in the ICRC-3 standard. This section explains how to extend those schemas to include backwards compatible fee collection information. +## 6. API for Managing Fee Collection -#### 4.3.1 Transfer Blocks +### 6.1 Methods -Transfer blocks in ICRC-based ledgers can be identified by one of the following: -- **`btype = "1xfer"`** (ICRC-1 style), -- **`btype = "2xfer"`** (ICRC-2 style), -- or a **transfer transaction** (`tx.op = "xfer"`). +#### 6.1.1 `set_fee_collection` -To **record and interpret** fee-collection information in these blocks: +Sets or updates the fee collector account and/or the set of fee-bearing operations. -- **`fee_col` (Account)** - - A direct indicator of which account receives the collected fee. -- **`fee_col_block` (nat)** - - A pointer to an earlier block where `fee_col` is explicitly set. +**Candid Definition:** +```candid +set_fee_collection: (opt Account, opt vec text) -> (); +``` -**How to Determine Who Collects the Fee** -1. If `fee_col` is present in the block, that account is the collector. -2. If `fee_col` is absent but `fee_col_block` is present, fetch the referenced block; the `fee_col` in that block is the collector. -3. If neither is present, **no** collector can be inferred, and the fee is burned. +#### 6.1.2 `get_fee_collection` -#### 4.3.2 Approve Blocks +Retrieves the current fee collector account and the list of fee-bearing operations. -Approve blocks in ICRC-based ledgers can be identified by: -- **`btype = "2approve"`** (ICRC-2 style), -- or a **transaction object** (`tx.op = "approve"`). +**Candid Definition:** +```candid +get_fee_collection: () -> (opt Account, vec text) query; +``` -To **record and interpret** fee-collection information in these blocks: +--- -- **`fee_col` (Account)** - - A direct indicator of which account receives the collected fee for the approval operation. -- **`app_fee_col_block` (nat)** - - A pointer to an earlier block where `fee_col` is explicitly set for approves. +## 7. Interaction with New Standards -**How to Determine Who Collects the Fee** -1. If `fee_col` is present in the block, that account is the collector. -2. If `fee_col` is absent but `app_fee_col_block` is present, fetch the referenced block; the `fee_col` in that block is the collector. -3. If neither is present, **no** collector can be inferred, and the fee is burned. +Any new standard that introduces **additional block types** **MUST** specify how those block types interact with the fee collection mechanism defined in **ICRC-107**. --- -By strictly focusing on **block-level fields**: +## 8. Summary -- **Ledgers** remain free to implement or omit references in blocks while still following a consistent approach for collecting or burning fees. -- **External tools** (wallets, explorers) can determine the collector without requiring out-of-band metadata or additional method calls. If no collector info is embedded (directly or via a reference), the fee is considered burned. -- This specification thereby ensures **ICRC-1**, **ICRC-2**, and **ICRC-3** ledgers can remain consistent with **ICRC-107** using only the fields present in each block. +- **Purely On-Chain**: Fee collection settings are managed entirely within the block history. +- **Flexible Defaults**: Default fee-bearing operations are `["transfer"]` if not explicitly specified. +- **Transparent Evolution**: The chain’s history defines how fee collection evolves over time, ensuring full traceability. From ef25f9e63a7b077eef6580910ab3237130ea39d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BW=C2=B0MacAir?= Date: Fri, 7 Feb 2025 11:14:36 +0100 Subject: [PATCH 05/45] new versions --- ICRCs/ICRC-107/ICRC-107.md.old | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 ICRCs/ICRC-107/ICRC-107.md.old diff --git a/ICRCs/ICRC-107/ICRC-107.md.old b/ICRCs/ICRC-107/ICRC-107.md.old new file mode 100644 index 00000000..55c64892 --- /dev/null +++ b/ICRCs/ICRC-107/ICRC-107.md.old @@ -0,0 +1,123 @@ +# ICRC-107: Fee Collection + +## 1. Introduction & Motivation + +Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collection, often charging fees for **transfer** transactions but not for **approve** transactions. However, there is currently no standardized way to: + +1. Indicate and configure **who** should collect fees (or if fees are burned). +2. Record fee collection settings directly in ledger blocks. +3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee settings in a consistent way. + +**ICRC-107** aims to address this gap by defining: + +- A mechanism to specify **fee collection settings** (collector account and applicable operations) directly in blocks. +- A **backward-compatible standard** for **recording and interpreting fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. +- Clear rules on how fee collection settings evolve over time and how subsequent blocks rely on prior updates. + +This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. + +--- + +## 2. Overview + +For each block, the fee amount and the account paying the fee are determined according to rules specific to the block type. +The fee collection settings then determine if the fee is **collected** (removed from the payer account and added to a fee collector account) +or **burned** (removed from the payer account and from the total supply): + + +--- + +## 4. Fee Collection Settings + +Fee collection settings determine the procedures for handling transaction fees within the ledger. These settings specify whether a fee is collected by an account or burned. When a transaction incurs a fee, the active fee collection settings at the time the block is created dictate how the fee is handled: + +- If a **fee collector account** (`fee_col`) is set, the fee is transferred to that account. +- If no **fee collector account** is set, the fee is burned (removed from circulation). +- The **operations list** (`col_ops`) defines which transaction types are subject to fee collection. +- Changes to fee collection settings are recorded directly in blocks, ensuring an **on-chain history** of modifications. + +The following subsections describe the fields in blocks that manage fee collection settings and how they are used in transactions. + +### 4.1 Fields in Blocks + +A block **MAY** include the following fields to define or update fee collection settings: + +1. **fee_col** (optional, `Map`) + - `principal`: `Blob` - The principal of the fee collector. + - `subaccount`: `Blob` (optional) - The subaccount identifier, if applicable. + +2. **col_ops** (optional, `Vec`) An array of operation types (strings) for which fees **should** be collected. If `col_ops` is omitted, it defaults to `["transfer"]` (fee is collected for transfers only). + +3. **prev_fee_col_info** (optional, `Nat`) A natural number (`Nat`) referencing the block index at which the ledger last updated `fee_col` or `col_ops`. If present, it helps external tools quickly locate the previous fee collection settings. + +--- + +## 5. Determining Fee Collection for ICRC-1 and ICRC-2 Blocks + +### 5.1 How to Calculate the Fee for ICRC-1 and ICRC-2 blocks + +The format of blocks follows the ICRC-3 standard. For a complete specification of block structure and fields, refer to [ICRC-3 Standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). + +To determine the **final fee amount** for a given block: + +1. **Check `tx.fee`** + - If present, the value of `tx.fee` is the fee for this block. + +2. **Else, check the top-level `fee` field** + - If `tx.fee` is **not set**, and a top-level field `fee` exists in the block, then that is the fee. + +3. **Else, fallback to `0`** + - If **neither** `tx.fee` nor `fee` is present, the default fee is `0`. + +The paying account is the source account for both transfers and approve transactions. + + +### 5.2 How `col_ops` Determines Fee Collection + +The `col_ops` field defines which block types incur a fee that is collected (instead of burned). For ICRC-1 and ICRC-2 blocks, the mapping from a `col_ops` entry to actual block types is: + +| **col_ops Entry** | **Block Types Affected** | +|-------------------|----------------------------------------------------------------------------------------------------------| +| **"transfer"** | Blocks with `btype = "1xfer"` or `btype = "2xfer"`. If `btype` is not set, then `tx.op = "xfer"` or `"2xfer"`. | +| **"approve"** | Blocks with `btype = "2approve"`. If `btype` is not set, then `tx.op = "approve"`. | + +**Note**: By merging the `"transfer"` and `"icrc2_transfer"` concept, any reference to `"transfer"` in `col_ops` applies to **both** ICRC-1 (`1xfer`) **and** ICRC-2 (`2xfer`) blocks. + +Concretely, + +1. **Identify Block Type** + - If `btype` is present, it takes precedence. + - Otherwise, use `tx.op`. + +2. **Check `col_ops`** + - If the block type is in `col_ops`, the fee is collected by the active `fee_col`. + - Otherwise, the fee is burned. + +If no `fee_col` is set, the fee is burned by default. + +--- + +## 6. Minimal API for Fee Collection Settings + +### 6.1 `set_fee_collection` + +``` +set_fee_collection: (opt Map, opt Vec) -> (); +``` + +### 6.2 `get_fee_collection` + +``` +get_fee_collection: () -> (opt Map, Vec) query; +``` + +--- + +## 7. Summary + +- **On-Chain Configuration**: Fee collection settings (`fee_col`, `col_ops`) are updated via blocks, with no external metadata needed. +- **Fee Collection & Burning**: If `fee_col` is set, the fee is credited to the collector; otherwise, it is burned. +- **Governance**: Only the **ledger controller** can update fee collection settings. +- **Backward-Compatible**: Works seamlessly with ICRC-1, ICRC-2, and ICRC-3 block definitions. + +--- From fc15a30f20f3a8e621405ed93f2b2de9cf692c5c Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:51:02 +0100 Subject: [PATCH 06/45] pass --- ICRCs/ICRC-107/ICRC-107.md | 245 +++++++++++++-------------------- ICRCs/ICRC-107/ICRC-107.md.old | 49 +++---- 2 files changed, 122 insertions(+), 172 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index fdccee4d..2c26ff4d 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,197 +1,146 @@ - -# ICRC-107: Fee Collection (Purely Block-Based) +# ICRC-107: Fee Collection ## 1. Introduction & Motivation -Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collection, often charging fees for **transfer** transactions but not for **approve** transactions. However, there is currently no standardized way to: +Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees, approve transactions typically do not. Currently, there is no standardized way to: + +- Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. +- Record fee collection settings directly on-chain in ledger blocks. +- Provide consistent semantics for wallets, explorers, and other integrations to interpret fee structures. + +ICRC-107 extends [ICRC-3](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md), adding semantics for fee collection while ensuring full compatibility with the existing block format. + +### ICRC-107 Proposal -1. Indicate and configure **who** should collect fees (or if fees are burned). -2. Record fee collection information directly in ledger blocks. -3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee information in a consistent way. -**ICRC-107** aims to address this gap by defining: -- A mechanism to specify **fee collection details** (collector account and applicable operations) directly in blocks. -- A **backward-compatible standard** for **recording and interpreting fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. -- Clear rules on how fee collection information evolves over time and how subsequent blocks rely on prior updates. +ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuring clarity and interoperability across different ICRC-based ledgers. It defines: -This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. +- A fee collection configuration specifying the collector account (`fee_col`) and the operations (`col_ops`) subject to fees. +- A backward-compatible extension to ICRC-3 blocks, allowing fee collection settings to be recorded and modified within ledger history. +- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred to the collector for the operations specified via `col_ops`; otherwise, they are burned. + +By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. --- -## 2. Fee Collection Mechanism +## 2. Fee Collection + +### 2.1 Overview + +For each block added to the ledger, some party must pay a fee. The amount and payer of the fee depend on the specific transaction type. + +Fee collection settings determine how fees are processed. These settings include: -### 2.1 Fields in Blocks +- A fee collector account (`fee_col`). If `fee_col` is not set, all fees are burned. +- A list of operations (`col_ops`) for which fees are collected. If an operation is not in `col_ops`, the fee is burned. + +Changes to fee collection settings are recorded on-chain, ensuring a transparent history of modifications. + +### 2.2 Fee Collection Settings A block **MAY** include the following fields to define or update fee collection settings: -- **`fee_col`**: An **account** (ICRC account format) to which fees are paid. - **Format**: - ```candid - record { - principal: principal; - subaccount: opt blob; - } - ``` -- **`fee_ops`**: An **array of block types** (strings) for which fees **should** be collected. This standard specifies two types of blocks for which fee collection can occur: "transfer" and "approve". +- **`fee_col`** (optional, `Map`) + - `principal`: `Blob` — The principal of the fee collector. + - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. -When a block includes either or both fields, it **overrides** the previously known settings from that point onward. +- **`col_ops`** (optional, `Vec`) + - A list of operation types for which fees **should** be collected. + - If omitted, defaults to `"transfer"` (fees are collected only for transfers). -#### 2.1.1 Defaults -- If `fee_ops` is **omitted**, it defaults to `["transfer"]`. -- If `fee_col` is **omitted**, the previously known collector remains in effect (or none if it was never set). +- **`prev_fee_col_info`** (optional, `Nat`) + - The block index at which `fee_col` or `col_ops` was last updated. + - Enables quick lookup of previous fee collection settings. + +**Note:** The block format follows the [ICRC-3 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). --- -## 3. Fee Computation +## 3. Handling Fee Collection for ICRC-1 and ICRC-2 Blocks -### 3.1 Determining the Fee Amount +### 3.1 How to Calculate the Fee for ICRC-1 and ICRC-2 Blocks -The fee amount in any block can be derived via: -- An explicit `tx.fee` field in the block’s transaction. -- A fallback `effective_fee` or other ledger-defined field if `tx.fee` is absent. -- Zero if neither is present. +Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a block: -### 3.2 Collected or Burned +1. **Check `tx.fee`** + - If present, `tx.fee` is the fee for this block. -After determining the fee amount, check the **current** configuration (from the most recent update): -1. **Operation Present**: If the block’s operation (e.g., `"transfer"`, `"approve"`, etc.) is in the active list (`fee_ops`), the fee is collected by the active `fee_col`. -2. **Operation Absent**: If the operation is **not** in the active list, the fee is burned. -3. **No Collector**: If no collector has ever been set, fees are burned by default. +2. **Else, check the top-level `fee` field** + - If `tx.fee` is **not set**, and a top-level field `fee` exists in the block, then that is the fee. ---- +3. **Else, fallback to `0`** + - If **neither** `tx.fee` nor `fee` is present, the default fee is `0`. -## 4. Determining the Current Fee Configuration +The paying account is the source account for both transfer and approve transactions. -At any block height `h`, to figure out **who** collects fees and for **which** operations: -1. **Traverse blocks** backward (or maintain an in-memory state) until you find the most recent block `< h` that included `fee_col` or `fee_ops`. -2. The fee-collector account from that block is **active**, and the operation list from that block is **active** (defaulting to `["transfer"]` if omitted). -3. If no such block is found, no collector is active and **all fees are burned**. +### 3.2 How `col_ops` Determines Fee Collection ---- +The `col_ops` field defines which block types incur fees that are collected instead of burned. For ICRC-1 and ICRC-2 blocks, `col_ops` entries map to block types as follows: -## 5. Examples - -### 5.1 Setting a Collector (Defaults to Transfers Only) - -```json -{ - "block_index": 100, - "timestamp": 1690000000, - "kind": "transaction", - "fee_col": { - "principal": "aaaaa-aa", - "subaccount": null - }, - "transaction": { - "operation": "transfer", - "from": { - "principal": "bbbbb-bb", - "subaccount": null - }, - "to": { - "principal": "ccccc-cc", - "subaccount": null - }, - "amount": 5000, - "fee": 10 - } -} -``` +| **col_ops Entry** | **Block Types Affected** | +|-------------------|--------------------------| +| **"transfer"** | Blocks with `btype = "1xfer"` or `btype = "2xfer"`. If `btype` is not set, use `tx.op = "xfer"` or `"2xfer"`. | +| **"approve"** | Blocks with `btype = "2approve"`. If `btype` is not set, use `tx.op = "approve"`. | -- Future blocks that perform a **transfer** pay fees to `aaaaa-aa`. -- **Approve** or other operations are not in the default set, so their fees are burned. - -### 5.2 Block with Approve but No Update - -```json -{ - "block_index": 101, - "timestamp": 1690000500, - "kind": "transaction", - "transaction": { - "operation": "approve", - "from": { - "principal": "bbbbb-bb", - "subaccount": null - }, - "spender": { - "principal": "ccccc-cc", - "subaccount": null - }, - "amount": 3000 - } -} -``` +#### Key Rules: -- No new fee settings. The **previous** block #100 had `["transfer"]` as the fee-bearing operation. -- This is an `"approve"` operation, **not** in `["transfer"]`, so the fee (if any) is burned. - -### 5.3 Updating Collector and Adding Approves - -```json -{ - "block_index": 150, - "timestamp": 1690001000, - "kind": "transaction", - "fee_col": { - "principal": "zzzzz-zz", - "subaccount": null - }, - "fee_ops": ["transfer", "approve"], - "transaction": { - "operation": "approve", - "from": { - "principal": "bbbbb-bb", - "subaccount": null - }, - "spender": { - "principal": "ccccc-cc", - "subaccount": null - }, - "amount": 6000, - "fee": 5 - } -} -``` +1. **Determine Block Type** + - If `btype` is present, it takes precedence. + - Otherwise, use `tx.op`. -- At block #150, the collector is changed to `zzzzz-zz`, **and** we explicitly list `["transfer", "approve"]`. -- From block #150 onward, **both** transfers and approves incur fees, credited to `zzzzz-zz`. -- The **approve** operation in this same block has a fee of 5 tokens. +2. **Check if `col_ops` applies to the block type** + - If the block type corresponds to an entry in `col_ops`, the fee is collected by `fee_col`. + - Otherwise, the fee is burned. ---- +If no `fee_col` is set, all fees are burned by default. -## 6. API for Managing Fee Collection +--- -### 6.1 Methods +## 4. Minimal API for Fee Collection Settings -#### 6.1.1 `set_fee_collection` +### 4.1 `set_fee_collection` -Sets or updates the fee collector account and/or the set of fee-bearing operations. +Updates fee collection settings. Only callable by the ledger controller. -**Candid Definition:** -```candid -set_fee_collection: (opt Account, opt vec text) -> (); +``` +set_fee_collection: (opt Map, opt Vec) -> (); ``` -#### 6.1.2 `get_fee_collection` +### 4.2 `get_fee_collection` -Retrieves the current fee collector account and the list of fee-bearing operations. +Returns the current fee collection settings and the block index of the last update. -**Candid Definition:** -```candid -get_fee_collection: () -> (opt Account, vec text) query; +``` +get_fee_collection: () -> (opt Map, Vec, Nat) query; ``` --- -## 7. Interaction with New Standards +## 5. Interaction with Future Standards + +Any new standard that introduces additional block types **MUST** specify how those block types interact with the fee collection mechanism defined in **ICRC-107**. Specifically: + +1. **Fee Payer Determination** + - Define how the fee amount is determined and who is responsible for paying the fee. + +2. **Fee Applicability** + - Clarify which new block types, if any, are subject to fee collection. -Any new standard that introduces **additional block types** **MUST** specify how those block types interact with the fee collection mechanism defined in **ICRC-107**. +3. **Fee Collection Rules** + - Define entries for `fee_col` corresponding to the new block types. --- -## 8. Summary -- **Purely On-Chain**: Fee collection settings are managed entirely within the block history. -- **Flexible Defaults**: Default fee-bearing operations are `["transfer"]` if not explicitly specified. -- **Transparent Evolution**: The chain’s history defines how fee collection evolves over time, ensuring full traceability. + + + +## 6. Summary + +- **On-Chain Configuration**: Fee collection settings (`fee_col`, `col_ops`) are recorded and modified within ledger blocks. +- **Fee Collection & Burning**: If `fee_col` is set and a block type appears in `fee_ops` then fees for that block type are collected by `fee_col`. Otherwise, the fee is burned. +- **Governance**: Only the **ledger controller** can update fee collection settings. +- **Backward-Compatible**: Works seamlessly with ICRC-1, ICRC-2, and ICRC-3 block definitions. + + +--- diff --git a/ICRCs/ICRC-107/ICRC-107.md.old b/ICRCs/ICRC-107/ICRC-107.md.old index 55c64892..032df1fd 100644 --- a/ICRCs/ICRC-107/ICRC-107.md.old +++ b/ICRCs/ICRC-107/ICRC-107.md.old @@ -14,31 +14,28 @@ Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collec - A **backward-compatible standard** for **recording and interpreting fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. - Clear rules on how fee collection settings evolve over time and how subsequent blocks rely on prior updates. -This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. +This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. +It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. --- -## 2. Overview +## 2. Fee Collection -For each block, the fee amount and the account paying the fee are determined according to rules specific to the block type. -The fee collection settings then determine if the fee is **collected** (removed from the payer account and added to a fee collector account) -or **burned** (removed from the payer account and from the total supply): +### 2.1 Overview +For each block added to the ledgers, some party pays fees. The fee amount and the account paying the fee are determined according to rules specific to the block type. +Fee collection settings determine how fees are handled for specific block types. These settings include a fee collector account (`fee_col`) and +a list of operations (`col_ops`) for which fees are collected. If `fee_col` is not set, then fees for all operations are burned. +If no fee collector is set or the operation is not in `col_ops`, the fee is burned. +Changes to fee collection settings are recorded directly in blocks, ensuring an **on-chain history** of modifications. ---- - -## 4. Fee Collection Settings - -Fee collection settings determine the procedures for handling transaction fees within the ledger. These settings specify whether a fee is collected by an account or burned. When a transaction incurs a fee, the active fee collection settings at the time the block is created dictate how the fee is handled: - -- If a **fee collector account** (`fee_col`) is set, the fee is transferred to that account. -- If no **fee collector account** is set, the fee is burned (removed from circulation). -- The **operations list** (`col_ops`) defines which transaction types are subject to fee collection. -- Changes to fee collection settings are recorded directly in blocks, ensuring an **on-chain history** of modifications. +For each block corresponding to some operation, if that operation is not subject to fee collection or a fee collector is not set then the fee is burned (i.e. removed from +the account that pays the fee and from the total supply). +Otherwise, the fee is transferred to the fee collector account. The following subsections describe the fields in blocks that manage fee collection settings and how they are used in transactions. -### 4.1 Fields in Blocks +## 2.2 Fee Collection Settings A block **MAY** include the following fields to define or update fee collection settings: @@ -52,9 +49,9 @@ A block **MAY** include the following fields to define or update fee collection --- -## 5. Determining Fee Collection for ICRC-1 and ICRC-2 Blocks +## 3. Determining Fee Collection for ICRC-1 and ICRC-2 Blocks -### 5.1 How to Calculate the Fee for ICRC-1 and ICRC-2 blocks +### 3.1 How to Calculate the Fee for ICRC-1 and ICRC-2 blocks The format of blocks follows the ICRC-3 standard. For a complete specification of block structure and fields, refer to [ICRC-3 Standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). @@ -72,7 +69,7 @@ To determine the **final fee amount** for a given block: The paying account is the source account for both transfers and approve transactions. -### 5.2 How `col_ops` Determines Fee Collection +### 3.2 How `col_ops` Determines Fee Collection The `col_ops` field defines which block types incur a fee that is collected (instead of burned). For ICRC-1 and ICRC-2 blocks, the mapping from a `col_ops` entry to actual block types is: @@ -97,23 +94,27 @@ If no `fee_col` is set, the fee is burned by default. --- -## 6. Minimal API for Fee Collection Settings +## 4. Minimal API for Fee Collection Settings + + +### 4.1 `set_fee_collection` -### 6.1 `set_fee_collection` +This method sets or updates the fee collection settings. It can only be called by the controller of the canister. ``` set_fee_collection: (opt Map, opt Vec) -> (); ``` -### 6.2 `get_fee_collection` +### 4.2 `get_fee_collection` +This method returns the current `fee_col`, `col_ops`, and the block index of the last update (`prev_fee_col_info`). ``` -get_fee_collection: () -> (opt Map, Vec) query; +get_fee_collection: () -> (opt Map, Vec, Nat) query; ``` --- -## 7. Summary +## 5. Summary - **On-Chain Configuration**: Fee collection settings (`fee_col`, `col_ops`) are updated via blocks, with no external metadata needed. - **Fee Collection & Burning**: If `fee_col` is set, the fee is credited to the collector; otherwise, it is burned. From d2a50c3dc94f5c2777fb6ef4d434df2dc6d6a4d0 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:09:07 +0100 Subject: [PATCH 07/45] final draft icrc-107 --- ICRCs/ICRC-107/ICRC-107.md | 106 ++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 32 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 2c26ff4d..55aa0eb3 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -2,22 +2,21 @@ ## 1. Introduction & Motivation -Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees, approve transactions typically do not. Currently, there is no standardized way to: +Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees, approve transactions typically do not. However, there is no standardized way to: - Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. - Record fee collection settings directly on-chain in ledger blocks. - Provide consistent semantics for wallets, explorers, and other integrations to interpret fee structures. -ICRC-107 extends [ICRC-3](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md), adding semantics for fee collection while ensuring full compatibility with the existing block format. +ICRC-107 extends **ICRC-3**, adding semantics for fee collection while ensuring full compatibility with the existing block format. This proposal eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. ### ICRC-107 Proposal - ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuring clarity and interoperability across different ICRC-based ledgers. It defines: -- A fee collection configuration specifying the collector account (`fee_col`) and the operations (`col_ops`) subject to fees. +- A fee collection configuration specifying the collector account (`fee_col`) subject to fees. - A backward-compatible extension to ICRC-3 blocks, allowing fee collection settings to be recorded and modified within ledger history. -- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred to the collector for the operations specified via `col_ops`; otherwise, they are burned. +- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred to the collector; otherwise, they are burned. By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. @@ -38,18 +37,39 @@ Changes to fee collection settings are recorded on-chain, ensuring a transparent ### 2.2 Fee Collection Settings +**Backwards Compatibility:** +To ensure compatibility with existing ICRC-3 implementations, `fee_col` **MUST** continue to be recorded using the existing array format: + +``` +fee_col; +variant { + Array = vec { + variant { Blob = principal_blob }; + variant { Blob = subaccount_blob }; + } +} +``` + +- The **first Blob** represents the **principal** of the fee collector. +- The **second Blob** represents the **subaccount**, or a zeroed-out Blob if no subaccount is used. + +This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. + A block **MAY** include the following fields to define or update fee collection settings: -- **`fee_col`** (optional, `Map`) - - `principal`: `Blob` — The principal of the fee collector. - - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. +- **`fee_col`** (optional, `Map`) -- **`col_ops`** (optional, `Vec`) - - A list of operation types for which fees **should** be collected. - - If omitted, defaults to `"transfer"` (fees are collected only for transfers). + - `principal`: `Blob` — The principal of the fee collector. + - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. -- **`prev_fee_col_info`** (optional, `Nat`) - - The block index at which `fee_col` or `col_ops` was last updated. +- **`col_ops`** (optional, `Vec`) + + - A list of operation types for which fees **should** be collected. + - If omitted, defaults to `"transfer"` (fees are collected only for transfers). + +- **`prev_fee_col_info`** (optional, `Nat`) + + - The block index at which `fee_col` or `col_ops` was last updated. - Enables quick lookup of previous fee collection settings. **Note:** The block format follows the [ICRC-3 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). @@ -98,49 +118,71 @@ If no `fee_col` is set, all fees are burned by default. ## 4. Minimal API for Fee Collection Settings -### 4.1 `set_fee_collection` +### 4.1 `icrc107_set_fee_collection` + +This method allows the ledger controller to update the fee collection settings. It modifies the fee_col account, which determines where collected fees are sent, and updates the col_ops list, which specifies the transaction types for which fees should be collected. The updated settings are recorded in the next block added to the ledger. + +``` +icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); +``` -Updates fee collection settings. Only callable by the ledger controller. +with ``` -set_fee_collection: (opt Map, opt Vec) -> (); +type Account = record { + owner: principal; + subaccount: opt blob; +}; + +type SetFeeCollectionRequest = record { + fee_col: Account; + fee_ops: Vec +} ``` -### 4.2 `get_fee_collection` -Returns the current fee collection settings and the block index of the last update. +### 4.2 `icrc107_get_fee_collection` +This method retrieves the currently active fee collection settings, including the fee_col account (if set), the list of transaction types (col_ops) for which fees are collected, and the block index of the last recorded update. This allows external systems, such as wallets and explorers, to determine how fee collection is configured. ``` -get_fee_collection: () -> (opt Map, Vec, Nat) query; +icrc107_get_fee_collection: () -> (opt Account, Vec, Nat) query; ``` +**Note:** The block returned reflects the last block where a change in fee collection information was recorded. However, this block may not contain the most recent settings (as returned in the first part of the response) if no block was created between setting fee collection information and retrieving it. The latest fee collection settings will be recorded in the first block that will be added to the ledger. + --- ## 5. Interaction with Future Standards Any new standard that introduces additional block types **MUST** specify how those block types interact with the fee collection mechanism defined in **ICRC-107**. Specifically: -1. **Fee Payer Determination** - - Define how the fee amount is determined and who is responsible for paying the fee. - -2. **Fee Applicability** +1. **Fee Applicability** - Clarify which new block types, if any, are subject to fee collection. -3. **Fee Collection Rules** - - Define entries for `fee_col` corresponding to the new block types. +2. **Fee Payer Determination** + - Define how the fee amount is determined and who is responsible for paying the fee. ---- +3. **Fee Collection Rules** + - Define new entry types for `fee_col` corresponding to the new block types. + - Specify how `fee_col` should be applied to determine whether fees for the new block types are collected or burned. +By ensuring that any future standards explicitly define their interaction with fee collection, ICRC-107 remains a robust and extensible framework. +--- +## 6. Reporting Compliance with ICRC-Supported Standards +Ledgers implementing ICRC-107 **MUST** indicate their compliance through the `icrc1_supported_standards` and `icrc10_supported_standards` methods, as defined in ICRC-1 and ICRC-10 by including the following record in the output of these methods: -## 6. Summary +``` +record { + name = "ICRC-107"; + url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107.md" +} +``` -- **On-Chain Configuration**: Fee collection settings (`fee_col`, `col_ops`) are recorded and modified within ledger blocks. -- **Fee Collection & Burning**: If `fee_col` is set and a block type appears in `fee_ops` then fees for that block type are collected by `fee_col`. Otherwise, the fee is burned. -- **Governance**: Only the **ledger controller** can update fee collection settings. -- **Backward-Compatible**: Works seamlessly with ICRC-1, ICRC-2, and ICRC-3 block definitions. +--- +## 7. Summary ---- +ICRC-107 provides a self-contained, transparent, and interoperable framework for managing transaction fees across ICRC-based ledgers. From b24b937071a864fd262e45ec61d857a373f3dbcf Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:10:50 +0100 Subject: [PATCH 08/45] deleted old version of icrc107 --- ICRCs/ICRC-107/ICRC-107.md.old | 124 --------------------------------- 1 file changed, 124 deletions(-) delete mode 100644 ICRCs/ICRC-107/ICRC-107.md.old diff --git a/ICRCs/ICRC-107/ICRC-107.md.old b/ICRCs/ICRC-107/ICRC-107.md.old deleted file mode 100644 index 032df1fd..00000000 --- a/ICRCs/ICRC-107/ICRC-107.md.old +++ /dev/null @@ -1,124 +0,0 @@ -# ICRC-107: Fee Collection - -## 1. Introduction & Motivation - -Many ICRC-based ledgers (such as **ckBTC**) already implement partial fee collection, often charging fees for **transfer** transactions but not for **approve** transactions. However, there is currently no standardized way to: - -1. Indicate and configure **who** should collect fees (or if fees are burned). -2. Record fee collection settings directly in ledger blocks. -3. Provide clear semantics for wallets, explorers, and other integrations to **interpret** fee settings in a consistent way. - -**ICRC-107** aims to address this gap by defining: - -- A mechanism to specify **fee collection settings** (collector account and applicable operations) directly in blocks. -- A **backward-compatible standard** for **recording and interpreting fee-collection fields** in ICRC-3 blocks corresponding to ICRC-1 and ICRC-2 transactions. -- Clear rules on how fee collection settings evolve over time and how subsequent blocks rely on prior updates. - -This design ensures that **all** fee-related information is stored entirely **on-chain** in the block history, without reliance on external metadata or specialized block types. -It provides a fully self-contained, transparent standard for fee collection that simplifies integration with wallets, explorers, and other tools. - ---- - -## 2. Fee Collection - -### 2.1 Overview - -For each block added to the ledgers, some party pays fees. The fee amount and the account paying the fee are determined according to rules specific to the block type. -Fee collection settings determine how fees are handled for specific block types. These settings include a fee collector account (`fee_col`) and -a list of operations (`col_ops`) for which fees are collected. If `fee_col` is not set, then fees for all operations are burned. -If no fee collector is set or the operation is not in `col_ops`, the fee is burned. -Changes to fee collection settings are recorded directly in blocks, ensuring an **on-chain history** of modifications. - -For each block corresponding to some operation, if that operation is not subject to fee collection or a fee collector is not set then the fee is burned (i.e. removed from -the account that pays the fee and from the total supply). -Otherwise, the fee is transferred to the fee collector account. - -The following subsections describe the fields in blocks that manage fee collection settings and how they are used in transactions. - -## 2.2 Fee Collection Settings - -A block **MAY** include the following fields to define or update fee collection settings: - -1. **fee_col** (optional, `Map`) - - `principal`: `Blob` - The principal of the fee collector. - - `subaccount`: `Blob` (optional) - The subaccount identifier, if applicable. - -2. **col_ops** (optional, `Vec`) An array of operation types (strings) for which fees **should** be collected. If `col_ops` is omitted, it defaults to `["transfer"]` (fee is collected for transfers only). - -3. **prev_fee_col_info** (optional, `Nat`) A natural number (`Nat`) referencing the block index at which the ledger last updated `fee_col` or `col_ops`. If present, it helps external tools quickly locate the previous fee collection settings. - ---- - -## 3. Determining Fee Collection for ICRC-1 and ICRC-2 Blocks - -### 3.1 How to Calculate the Fee for ICRC-1 and ICRC-2 blocks - -The format of blocks follows the ICRC-3 standard. For a complete specification of block structure and fields, refer to [ICRC-3 Standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). - -To determine the **final fee amount** for a given block: - -1. **Check `tx.fee`** - - If present, the value of `tx.fee` is the fee for this block. - -2. **Else, check the top-level `fee` field** - - If `tx.fee` is **not set**, and a top-level field `fee` exists in the block, then that is the fee. - -3. **Else, fallback to `0`** - - If **neither** `tx.fee` nor `fee` is present, the default fee is `0`. - -The paying account is the source account for both transfers and approve transactions. - - -### 3.2 How `col_ops` Determines Fee Collection - -The `col_ops` field defines which block types incur a fee that is collected (instead of burned). For ICRC-1 and ICRC-2 blocks, the mapping from a `col_ops` entry to actual block types is: - -| **col_ops Entry** | **Block Types Affected** | -|-------------------|----------------------------------------------------------------------------------------------------------| -| **"transfer"** | Blocks with `btype = "1xfer"` or `btype = "2xfer"`. If `btype` is not set, then `tx.op = "xfer"` or `"2xfer"`. | -| **"approve"** | Blocks with `btype = "2approve"`. If `btype` is not set, then `tx.op = "approve"`. | - -**Note**: By merging the `"transfer"` and `"icrc2_transfer"` concept, any reference to `"transfer"` in `col_ops` applies to **both** ICRC-1 (`1xfer`) **and** ICRC-2 (`2xfer`) blocks. - -Concretely, - -1. **Identify Block Type** - - If `btype` is present, it takes precedence. - - Otherwise, use `tx.op`. - -2. **Check `col_ops`** - - If the block type is in `col_ops`, the fee is collected by the active `fee_col`. - - Otherwise, the fee is burned. - -If no `fee_col` is set, the fee is burned by default. - ---- - -## 4. Minimal API for Fee Collection Settings - - -### 4.1 `set_fee_collection` - -This method sets or updates the fee collection settings. It can only be called by the controller of the canister. - -``` -set_fee_collection: (opt Map, opt Vec) -> (); -``` - -### 4.2 `get_fee_collection` - -This method returns the current `fee_col`, `col_ops`, and the block index of the last update (`prev_fee_col_info`). -``` -get_fee_collection: () -> (opt Map, Vec, Nat) query; -``` - ---- - -## 5. Summary - -- **On-Chain Configuration**: Fee collection settings (`fee_col`, `col_ops`) are updated via blocks, with no external metadata needed. -- **Fee Collection & Burning**: If `fee_col` is set, the fee is credited to the collector; otherwise, it is burned. -- **Governance**: Only the **ledger controller** can update fee collection settings. -- **Backward-Compatible**: Works seamlessly with ICRC-1, ICRC-2, and ICRC-3 block definitions. - ---- From 88617a334d0bae91b05335acdba427a524548a6e Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:10:47 +0100 Subject: [PATCH 09/45] first pass over Thomas' comments --- ICRCs/ICRC-107/ICRC-107.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 55aa0eb3..c4befbf4 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -2,7 +2,7 @@ ## 1. Introduction & Motivation -Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees, approve transactions typically do not. However, there is no standardized way to: +Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees that is collected or burned, approve transactions typically do not. However, there is no standardized way to: - Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. - Record fee collection settings directly on-chain in ledger blocks. @@ -86,10 +86,8 @@ Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a b - If present, `tx.fee` is the fee for this block. 2. **Else, check the top-level `fee` field** - - If `tx.fee` is **not set**, and a top-level field `fee` exists in the block, then that is the fee. + - If `tx.fee` is **not set**, then a top-level field `fee` exists in the block, and that is the fee. -3. **Else, fallback to `0`** - - If **neither** `tx.fee` nor `fee` is present, the default fee is `0`. The paying account is the source account for both transfer and approve transactions. @@ -136,16 +134,16 @@ type Account = record { type SetFeeCollectionRequest = record { fee_col: Account; - fee_ops: Vec + col_ops: Vec } ``` ### 4.2 `icrc107_get_fee_collection` -This method retrieves the currently active fee collection settings, including the fee_col account (if set), the list of transaction types (col_ops) for which fees are collected, and the block index of the last recorded update. This allows external systems, such as wallets and explorers, to determine how fee collection is configured. +This method retrieves the currently active fee collection settings, including the `fee_col` account (if set), the list of transaction types (`col_ops`) for which fees are collected, and the block index of the last recorded update. This allows external systems, such as wallets and explorers, to determine how fee collection is configured. ``` -icrc107_get_fee_collection: () -> (opt Account, Vec, Nat) query; +icrc107_get_fee_collection: () -> (opt Account, Vec, opt Nat) query; ``` **Note:** The block returned reflects the last block where a change in fee collection information was recorded. However, this block may not contain the most recent settings (as returned in the first part of the response) if no block was created between setting fee collection information and retrieving it. The latest fee collection settings will be recorded in the first block that will be added to the ledger. @@ -164,7 +162,7 @@ Any new standard that introduces additional block types **MUST** specify how tho 3. **Fee Collection Rules** - Define new entry types for `fee_col` corresponding to the new block types. - - Specify how `fee_col` should be applied to determine whether fees for the new block types are collected or burned. + - Specify how `col_ops` should be applied to determine whether fees for the new block types are collected or burned. By ensuring that any future standards explicitly define their interaction with fee collection, ICRC-107 remains a robust and extensible framework. From d58a8210ea6949b7d45a169c12f5748602d1625d Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:40:44 +0100 Subject: [PATCH 10/45] implemented WG decisions --- ICRCs/ICRC-107/ICRC-107.md | 81 +++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index c4befbf4..098f6e04 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -2,7 +2,7 @@ ## 1. Introduction & Motivation -Fee collection in ICRC-based ledgers (e.g., ckBTC) is inconsistently implemented. While transfer transactions often incur fees that is collected or burned, approve transactions typically do not. However, there is no standardized way to: +The absence of a unified approach has resulted in inconsistencies in fee collection across ICRC-based ledgers (e.g., ckBTC). In the ckBTC ledger, the fee collector collects fees for transfer operations, while fees for approve operations are burned, demonstrating the need for a unified standard. However, there is no standardized way to: - Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. - Record fee collection settings directly on-chain in ledger blocks. @@ -16,7 +16,7 @@ ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuri - A fee collection configuration specifying the collector account (`fee_col`) subject to fees. - A backward-compatible extension to ICRC-3 blocks, allowing fee collection settings to be recorded and modified within ledger history. -- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred to the collector; otherwise, they are burned. +- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred from the paying account to the collector; otherwise, they are burned, —meaning they are debited from the paying account and removed from the total supply. By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. @@ -26,19 +26,21 @@ By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reli ### 2.1 Overview -For each block added to the ledger, some party must pay a fee. The amount and payer of the fee depend on the specific transaction type. +For each block added to the ledger, some party may have to pay a fee. The amount and payer of the fee depend on the specific operation type. Fee collection settings determine how fees are processed. These settings include: -- A fee collector account (`fee_col`). If `fee_col` is not set, all fees are burned. -- A list of operations (`col_ops`) for which fees are collected. If an operation is not in `col_ops`, the fee is burned. -Changes to fee collection settings are recorded on-chain, ensuring a transparent history of modifications. +- A fee collector account (`fee_col`). If `fee_col` is not set, all fees are burned. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. +- A list of operations (`fee_ops`) for which fees are collected. If an operation is not in `fee_ops`, the fee is burned. +- A flag `fee_burn` explicitly determines that all fees are burned. If set to "true", fees are always burned, regardless of `fee_col`. + +Changes to fee collection settings are recorded on-chain and take effect immediately in the block where they appear. ### 2.2 Fee Collection Settings **Backwards Compatibility:** -To ensure compatibility with existing ICRC-3 implementations, `fee_col` **MUST** continue to be recorded using the existing array format: +To ensure compatibility with existing ICRC-3 implementations, `fee_col` **MUST** be recorded using the existing array format: ``` fee_col; @@ -50,7 +52,7 @@ variant { } ``` -- The **first Blob** represents the **principal** of the fee collector. +- The **first Blob** represents the **principal** of the fee collector; if this is the anonymous principal (0x04) then fees for all operations are burned. - The **second Blob** represents the **subaccount**, or a zeroed-out Blob if no subaccount is used. This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. @@ -58,19 +60,20 @@ This ensures that existing tools and explorers relying on the current `fee_col` A block **MAY** include the following fields to define or update fee collection settings: - **`fee_col`** (optional, `Map`) - - `principal`: `Blob` — The principal of the fee collector. - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. -- **`col_ops`** (optional, `Vec`) +- **`fee_burn`** (optional, `Text`) + - If `"true"`, fees are burned (removed from supply), irrespective of `fee_col` settings. + - If `"false"`, fees are transferred to `fee_col`. + - Defaults to `"true"` if `fee_col` is unset. - - A list of operation types for which fees **should** be collected. - - If omitted, defaults to `"transfer"` (fees are collected only for transfers). +- **`fee_ops`** (optional, `Vec`) -- **`prev_fee_col_info`** (optional, `Nat`) + - A list of operation types for which fees **should** be collected. + - If omitted, defaults to `["1xfer","2xfer"]` (fees are collected only for transfers). - - The block index at which `fee_col` or `col_ops` was last updated. - - Enables quick lookup of previous fee collection settings. +Blocks where `fee_ops` is specified, or `fee_burn` is set to "false" MUST also specify `fee_col`. **Note:** The block format follows the [ICRC-3 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). @@ -91,26 +94,36 @@ Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a b The paying account is the source account for both transfer and approve transactions. -### 3.2 How `col_ops` Determines Fee Collection +### 3.2 How to Determine the Fee Collector for a Block + +Find the most recent block where fee collection settings are present. +* If no such block exists, the fees in the current block are burned. +* If the most recent block with fee settings has `fee_burn` = "true", then fees in the current block are burned. +* Otherwise, the active fee collector is the account specified by `fee_col`, and fees are collected for the operations listed in `fee_ops` (see the next section). + -The `col_ops` field defines which block types incur fees that are collected instead of burned. For ICRC-1 and ICRC-2 blocks, `col_ops` entries map to block types as follows: +### 3.3 How `fee_ops` Determines Fee Collection for a Block -| **col_ops Entry** | **Block Types Affected** | +The `fee_ops` field defines which block types incur fees that are collected instead of burned. For ICRC-1 and ICRC-2 blocks, `fee_ops` entries map to block types as follows: + +| **fee_ops Entry** | **Block Types Affected** | |-------------------|--------------------------| -| **"transfer"** | Blocks with `btype = "1xfer"` or `btype = "2xfer"`. If `btype` is not set, use `tx.op = "xfer"` or `"2xfer"`. | -| **"approve"** | Blocks with `btype = "2approve"`. If `btype` is not set, use `tx.op = "approve"`. | +| "1xfer" | Blocks with `btype = "1xfer"` and blocks where `btype` is not set and `tx.op = "xfer"` and there is no `tx.spender` field | +| "2xfer" | Blocks with `btype = "2xfer"` and blocks where `btype` is not set and `tx.op = "xfer"` and the field `tx.spender` is set. | +| "2approve" | Blocks with `btype = "2approve"` and blocks where `btype` is not set and `tx.op = "approve"`. | #### Key Rules: 1. **Determine Block Type** - If `btype` is present, it takes precedence. - - Otherwise, use `tx.op`. + - Otherwise, use `tx.op` to determine the block type. -2. **Check if `col_ops` applies to the block type** - - If the block type corresponds to an entry in `col_ops`, the fee is collected by `fee_col`. +2. **Check if `fee_ops` applies to the block type** + - If the block type corresponds to an entry in `fee_ops`, the fee is collected by `fee_col`. - Otherwise, the fee is burned. -If no `fee_col` is set, all fees are burned by default. +If `fee_col` is set but `fee_ops` is not, `fee_ops` defaults to `["1xfer, 2xfer"]`, i.e. fees are only collected for transfer operations. + --- @@ -118,7 +131,7 @@ If no `fee_col` is set, all fees are burned by default. ### 4.1 `icrc107_set_fee_collection` -This method allows the ledger controller to update the fee collection settings. It modifies the fee_col account, which determines where collected fees are sent, and updates the col_ops list, which specifies the transaction types for which fees should be collected. The updated settings are recorded in the next block added to the ledger. +This method allows a ledger controller to update the fee collection settings. It modifies the `fee_col` account, which determines where collected fees are sent, and updates the `fee_ops` list, which specifies the transaction types for which fees should be collected. The updated settings are recorded in the next block added to the ledger. ``` icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); @@ -133,20 +146,26 @@ type Account = record { }; type SetFeeCollectionRequest = record { - fee_col: Account; - col_ops: Vec + fee_col: opt Account; + fee_ops: Vec } ``` +If `fee_col` is not provided then fee collection reverts to fee burning. + ### 4.2 `icrc107_get_fee_collection` -This method retrieves the currently active fee collection settings, including the `fee_col` account (if set), the list of transaction types (`col_ops`) for which fees are collected, and the block index of the last recorded update. This allows external systems, such as wallets and explorers, to determine how fee collection is configured. +This method retrieves the currently active fee collection settings. Unless they are changed they would apply to the next block added to the ledger. + ``` -icrc107_get_fee_collection: () -> (opt Account, Vec, opt Nat) query; +icrc107_get_fee_collection: () -> (opt Account, Vec) query; ``` -**Note:** The block returned reflects the last block where a change in fee collection information was recorded. However, this block may not contain the most recent settings (as returned in the first part of the response) if no block was created between setting fee collection information and retrieving it. The latest fee collection settings will be recorded in the first block that will be added to the ledger. +`icrc107_get_fee_collection` returns two values: + +* `opt Account` (`fee_col`) – The active fee collector account. If `null`, fees are burned (`fee_burn` = "true"). +* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_col` is set but `fee_ops` is empty, no fees are collected and all fees are burned. --- @@ -162,7 +181,7 @@ Any new standard that introduces additional block types **MUST** specify how tho 3. **Fee Collection Rules** - Define new entry types for `fee_col` corresponding to the new block types. - - Specify how `col_ops` should be applied to determine whether fees for the new block types are collected or burned. + - Specify how `fee_ops` should be applied to determine whether fees for the new block types are collected or burned. By ensuring that any future standards explicitly define their interaction with fee collection, ICRC-107 remains a robust and extensible framework. From 19764d9b9911a70ecb2b6ae1ab2af367ad0064ae Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:53:38 +0100 Subject: [PATCH 11/45] minor edits --- ICRCs/ICRC-107/ICRC-107.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 098f6e04..8fbdf4eb 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -16,7 +16,7 @@ ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuri - A fee collection configuration specifying the collector account (`fee_col`) subject to fees. - A backward-compatible extension to ICRC-3 blocks, allowing fee collection settings to be recorded and modified within ledger history. -- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred from the paying account to the collector; otherwise, they are burned, —meaning they are debited from the paying account and removed from the total supply. +- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred from the paying account to the collector; otherwise, they are burned — meaning they are debited from the paying account and removed from the total supply. By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. @@ -72,6 +72,7 @@ A block **MAY** include the following fields to define or update fee collection - A list of operation types for which fees **should** be collected. - If omitted, defaults to `["1xfer","2xfer"]` (fees are collected only for transfers). +- If `fee_ops` is explicitly set to an empty list (`[]`), no fees are collected, and all fees are burned. Blocks where `fee_ops` is specified, or `fee_burn` is set to "false" MUST also specify `fee_col`. @@ -152,6 +153,7 @@ type SetFeeCollectionRequest = record { ``` If `fee_col` is not provided then fee collection reverts to fee burning. +The fee collections settings will be recorded in the first block that is created after calling this endpoint. ### 4.2 `icrc107_get_fee_collection` @@ -165,7 +167,9 @@ icrc107_get_fee_collection: () -> (opt Account, Vec) query; `icrc107_get_fee_collection` returns two values: * `opt Account` (`fee_col`) – The active fee collector account. If `null`, fees are burned (`fee_burn` = "true"). -* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_col` is set but `fee_ops` is empty, no fees are collected and all fees are burned. +* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_col` is set but `fee_ops` is an empty list (`[]`), no fees are collected and all fees are burned. + + --- @@ -197,9 +201,3 @@ record { url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107.md" } ``` - ---- - -## 7. Summary - -ICRC-107 provides a self-contained, transparent, and interoperable framework for managing transaction fees across ICRC-based ledgers. From 023326efd2ed1fbb3a33f89cf5fb149b2a1ddf73 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:16:28 +0100 Subject: [PATCH 12/45] minor edits --- ICRCs/ICRC-107/ICRC-107.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 8fbdf4eb..d52ef1dc 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -2,7 +2,8 @@ ## 1. Introduction & Motivation -The absence of a unified approach has resulted in inconsistencies in fee collection across ICRC-based ledgers (e.g., ckBTC). In the ckBTC ledger, the fee collector collects fees for transfer operations, while fees for approve operations are burned, demonstrating the need for a unified standard. However, there is no standardized way to: +The lack of a unified approach has led to inconsistencies in fee collection across ICRC-based ledgers (e.g., ckBTC). For instance, in the ckBTC ledger, fees for transfer operations are collected by a designated fee collector, whereas fees for approve operations are burned—even when some fee collection details are present in approve blocks. However, there is no standardized way to: + - Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. - Record fee collection settings directly on-chain in ledger blocks. From d33d2af8cc591ec8845abe68f16e00dbe508a3bb Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 3 Mar 2025 15:54:48 +0100 Subject: [PATCH 13/45] eliminated fee_burn --- ICRCs/ICRC-107/ICRC-107.md | 64 +++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index d52ef1dc..77d1b7b4 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -29,12 +29,10 @@ By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reli For each block added to the ledger, some party may have to pay a fee. The amount and payer of the fee depend on the specific operation type. -Fee collection settings determine how fees are processed. These settings include: +Fee collection settings determine how fees are processed. These settings consist of: - -- A fee collector account (`fee_col`). If `fee_col` is not set, all fees are burned. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. +- A fee collector account (`fee_col`). If `fee_col` has never been set block, all fees are burned. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. - A list of operations (`fee_ops`) for which fees are collected. If an operation is not in `fee_ops`, the fee is burned. -- A flag `fee_burn` explicitly determines that all fees are burned. If set to "true", fees are always burned, regardless of `fee_col`. Changes to fee collection settings are recorded on-chain and take effect immediately in the block where they appear. @@ -64,18 +62,12 @@ A block **MAY** include the following fields to define or update fee collection - `principal`: `Blob` — The principal of the fee collector. - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. -- **`fee_burn`** (optional, `Text`) - - If `"true"`, fees are burned (removed from supply), irrespective of `fee_col` settings. - - If `"false"`, fees are transferred to `fee_col`. - - Defaults to `"true"` if `fee_col` is unset. - **`fee_ops`** (optional, `Vec`) - - A list of operation types for which fees **should** be collected. - If omitted, defaults to `["1xfer","2xfer"]` (fees are collected only for transfers). -- If `fee_ops` is explicitly set to an empty list (`[]`), no fees are collected, and all fees are burned. + - If `fee_ops` is explicitly set to an empty list (`[]`), no fees are collected, and all fees are burned. -Blocks where `fee_ops` is specified, or `fee_burn` is set to "false" MUST also specify `fee_col`. **Note:** The block format follows the [ICRC-3 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). @@ -83,7 +75,7 @@ Blocks where `fee_ops` is specified, or `fee_burn` is set to "false" MUST also s ## 3. Handling Fee Collection for ICRC-1 and ICRC-2 Blocks -### 3.1 How to Calculate the Fee for ICRC-1 and ICRC-2 Blocks +### 3.1 Determining the Fee for ICRC-1 and ICRC-2 Blocks Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a block: @@ -94,25 +86,41 @@ Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a b - If `tx.fee` is **not set**, then a top-level field `fee` exists in the block, and that is the fee. -The paying account is the source account for both transfer and approve transactions. +The fee payer is always the source account in both transfer and approve transactions. + + +## **3.2 Determining the Active Fee Settings for a Block** + +To determine the **active fee settings** (who receives the fee and whether it is collected or burned), follow this algorithm: -### 3.2 How to Determine the Fee Collector for a Block +1. **Find the most recent block (including the current block) that defines `fee_col`** + - If `fee_col = []`, all fees in the block are **burned**, and `fee_ops` is ignored. + - Otherwise, use this value as the **active fee collector** (`active_fee_col`). + - If no `fee_col` is found in any prior block, all fees **are burned by default**. -Find the most recent block where fee collection settings are present. -* If no such block exists, the fees in the current block are burned. -* If the most recent block with fee settings has `fee_burn` = "true", then fees in the current block are burned. -* Otherwise, the active fee collector is the account specified by `fee_col`, and fees are collected for the operations listed in `fee_ops` (see the next section). +2. **Determine `fee_ops` from the same block where `fee_col` was set** + - If `fee_ops` is set in that block, use it as the **active fee_ops**. + - If `fee_ops` is **not set** in that block, **default to `["1xfer", "2xfer"]`**, meaning only transfer operations collect fees. +3. **Determine whether the fee should be collected or burned** + - Identify the **block type**: + - If `btype` is present, use it. + - Otherwise, infer from `tx.op`. + - If the **block type is in `fee_ops`**, fees are **collected** by `active_fee_col`. + - Otherwise, fees are **burned**. -### 3.3 How `fee_ops` Determines Fee Collection for a Block -The `fee_ops` field defines which block types incur fees that are collected instead of burned. For ICRC-1 and ICRC-2 blocks, `fee_ops` entries map to block types as follows: -| **fee_ops Entry** | **Block Types Affected** | -|-------------------|--------------------------| -| "1xfer" | Blocks with `btype = "1xfer"` and blocks where `btype` is not set and `tx.op = "xfer"` and there is no `tx.spender` field | -| "2xfer" | Blocks with `btype = "2xfer"` and blocks where `btype` is not set and `tx.op = "xfer"` and the field `tx.spender` is set. | -| "2approve" | Blocks with `btype = "2approve"` and blocks where `btype` is not set and `tx.op = "approve"`. | + ## **3.3 Mapping `fee_ops` to Block Types** + + The `fee_ops` field specifies which block types **have their fees collected** instead of burned. For ICRC-1 and ICRC-2 blocks, `fee_ops` maps to block types as follows: + + | **fee_ops Entry** | **Block Types Affected** | + |-------------------|--------------------------| + | `"1xfer"` | Blocks with `btype = "1xfer"` or where `btype` is not set, `tx.op = "xfer"`, and `tx.spender` is **not** set. | + | `"2xfer"` | Blocks with `btype = "2xfer"` or where `btype` is not set, `tx.op = "xfer"`, and `tx.spender` **is** set. | + | `"2approve"` | Blocks with `btype = "2approve"` or where `btype` is not set and `tx.op = "approve"`. | + #### Key Rules: @@ -124,7 +132,6 @@ The `fee_ops` field defines which block types incur fees that are collected inst - If the block type corresponds to an entry in `fee_ops`, the fee is collected by `fee_col`. - Otherwise, the fee is burned. -If `fee_col` is set but `fee_ops` is not, `fee_ops` defaults to `["1xfer, 2xfer"]`, i.e. fees are only collected for transfer operations. --- @@ -167,10 +174,9 @@ icrc107_get_fee_collection: () -> (opt Account, Vec) query; `icrc107_get_fee_collection` returns two values: -* `opt Account` (`fee_col`) – The active fee collector account. If `null`, fees are burned (`fee_burn` = "true"). -* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_col` is set but `fee_ops` is an empty list (`[]`), no fees are collected and all fees are burned. - +* `opt Account` (`fee_col`) – The active fee collector account. If `null`, fees are burned (this corresponds to `fee_col=[]` on-chain). +* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_ops=[]` no fees are collected. This is equivalent to `fee_col=[]`, meaning all fees are burned. --- From 199280be01910660a96662399783c7ddaa9ffaaa Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:04:28 +0100 Subject: [PATCH 14/45] minor changes --- ICRCs/ICRC-107/ICRC-107.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 77d1b7b4..5731494d 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -31,7 +31,7 @@ For each block added to the ledger, some party may have to pay a fee. The amount Fee collection settings determine how fees are processed. These settings consist of: -- A fee collector account (`fee_col`). If `fee_col` has never been set block, all fees are burned. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. +- A fee collector account (`fee_col`). If `fee_col` has never been set in any block, fees are burned by default. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. - A list of operations (`fee_ops`) for which fees are collected. If an operation is not in `fee_ops`, the fee is burned. Changes to fee collection settings are recorded on-chain and take effect immediately in the block where they appear. @@ -174,9 +174,13 @@ icrc107_get_fee_collection: () -> (opt Account, Vec) query; `icrc107_get_fee_collection` returns two values: -* `opt Account` (`fee_col`) – The active fee collector account. If `null`, fees are burned (this corresponds to `fee_col=[]` on-chain). +* `opt Account` (`fee_col`) – The active fee collector account. + - If `null`, fees are burned (this corresponds to `fee_col=[]` on-chain). + - Otherwise, fees are collected by the returned `Account` -* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. If `fee_ops=[]` no fees are collected. This is equivalent to `fee_col=[]`, meaning all fees are burned. +* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. + - If `fee_ops=[]`, no fees are collected, and all fees are burned. This is equivalent to `fee_col=[]`, meaning that all transactions incur a fee that is removed from the supply. + - The API **does not apply a default** — it always returns the explicitly set value. The defaulting behavior (`["1xfer", "2xfer"]`) applies only at the block processing level when `fee_ops` is missing from a block. --- From 86b676a142ad53f3cbb8cad2c0b51cb92df8441b Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:33:45 +0100 Subject: [PATCH 15/45] clarified what fee_ops = [] means --- ICRCs/ICRC-107/ICRC-107.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 5731494d..a0e0d23e 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -51,10 +51,12 @@ variant { } ``` -- The **first Blob** represents the **principal** of the fee collector; if this is the anonymous principal (0x04) then fees for all operations are burned. +- The **first Blob** represents the **principal** of the fee collector. - The **second Blob** represents the **subaccount**, or a zeroed-out Blob if no subaccount is used. - -This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. +- Burning fees (`fee_col = []`): + * Fees are burned if `fee_col` is not set in any prior vlock. + * To explicitly indicate burning, set `fee_col` to `variant {Array = vec {} }`, meaning the array is empty. + * This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. A block **MAY** include the following fields to define or update fee collection settings: @@ -89,7 +91,7 @@ Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a b The fee payer is always the source account in both transfer and approve transactions. -## **3.2 Determining the Active Fee Settings for a Block** +### **3.2 Determining the Active Fee Settings for a Block** To determine the **active fee settings** (who receives the fee and whether it is collected or burned), follow this algorithm: @@ -110,8 +112,7 @@ To determine the **active fee settings** (who receives the fee and whether it is - Otherwise, fees are **burned**. - - ## **3.3 Mapping `fee_ops` to Block Types** +### **3.3 Mapping `fee_ops` to Block Types** The `fee_ops` field specifies which block types **have their fees collected** instead of burned. For ICRC-1 and ICRC-2 blocks, `fee_ops` maps to block types as follows: From e6e4122c373f6d034b3a8c59d90e90460fb665cc Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:06:49 +0100 Subject: [PATCH 16/45] new summary --- ICRCs/ICRC-107/ICRC-107.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index a0e0d23e..ec5acf68 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -5,7 +5,7 @@ The lack of a unified approach has led to inconsistencies in fee collection across ICRC-based ledgers (e.g., ckBTC). For instance, in the ckBTC ledger, fees for transfer operations are collected by a designated fee collector, whereas fees for approve operations are burned—even when some fee collection details are present in approve blocks. However, there is no standardized way to: -- Define fee collection rules—who receives fees, which operations are charged, and whether fees are burned. +- Define fee collection rules — who receives fees, which operations are charged, and whether fees are burned. - Record fee collection settings directly on-chain in ledger blocks. - Provide consistent semantics for wallets, explorers, and other integrations to interpret fee structures. @@ -15,12 +15,13 @@ ICRC-107 extends **ICRC-3**, adding semantics for fee collection while ensuring ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuring clarity and interoperability across different ICRC-based ledgers. It defines: -- A fee collection configuration specifying the collector account (`fee_col`) subject to fees. -- A backward-compatible extension to ICRC-3 blocks, allowing fee collection settings to be recorded and modified within ledger history. -- Clear rules governing fee distribution: If `fee_col` is set, fees are transferred from the paying account to the collector; otherwise, they are burned — meaning they are debited from the paying account and removed from the total supply. +- A fee collection configuration specifying the collector account (icrc107_fee_col), which applies to all subsequent blocks. -By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. +- A new block type for setting `icrc107_fee_col` to designate the fee collector. + +- A backward-compatible mechanism where, if `icrc107_fee_col` has never been set, legacy fee_col logic applies. +By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. --- ## 2. Fee Collection @@ -55,7 +56,7 @@ variant { - The **second Blob** represents the **subaccount**, or a zeroed-out Blob if no subaccount is used. - Burning fees (`fee_col = []`): * Fees are burned if `fee_col` is not set in any prior vlock. - * To explicitly indicate burning, set `fee_col` to `variant {Array = vec {} }`, meaning the array is empty. + * To explicitly indicate burning, set `fee_col` to `variant {Array = vec {} }`, meaning the array is empty. * This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. A block **MAY** include the following fields to define or update fee collection settings: From 4654feb04089c1355984e6a2043883dc396301ac Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:13:42 +0100 Subject: [PATCH 17/45] included examples, legacy fee collection logic, compliance reporting --- ICRCs/ICRC-107/ICRC-107.md | 257 ++++++++++++++++++------------------- 1 file changed, 124 insertions(+), 133 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index ec5acf68..02e5b725 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -2,215 +2,206 @@ ## 1. Introduction & Motivation -The lack of a unified approach has led to inconsistencies in fee collection across ICRC-based ledgers (e.g., ckBTC). For instance, in the ckBTC ledger, fees for transfer operations are collected by a designated fee collector, whereas fees for approve operations are burned—even when some fee collection details are present in approve blocks. However, there is no standardized way to: +Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. Some ledgers assign a designated fee collector for transfer operations, while others burn fees for approve operations—even when fee collection details appear in approve blocks. This inconsistency leads to challenges in defining fee collection rules, recording fee settings on-chain, and ensuring clear semantics for wallets and explorers. ICRC-107 builds on the ICRC-3 block schema, introducing a new block type (`107feecol`) to configure fee collection settings on-chain. This ensures compatibility with existing ICRC-3 implementations while extending functionality for fee handling. +By embedding fee collection settings on-chain, ICRC-107 provides the following benefits: +- Transparency: Fee collection rules are visible and auditable on the ledger. -- Define fee collection rules — who receives fees, which operations are charged, and whether fees are burned. -- Record fee collection settings directly on-chain in ledger blocks. -- Provide consistent semantics for wallets, explorers, and other integrations to interpret fee structures. +- Interoperability: Wallets and explorers can rely on consistent fee handling across ICRC-based ledgers. -ICRC-107 extends **ICRC-3**, adding semantics for fee collection while ensuring full compatibility with the existing block format. This proposal eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. +- Simplified Integration: Eliminates the need for off-chain metadata to determine fee collection behavior -### ICRC-107 Proposal +ICRC-107 applies to all ICRC-based ensuring consistent fee collection behavior across transfers and approvals. This standard is backward-compatible with existing ICRC-based ledgers, allowing them to transition gradually to the new fee collection mechanism, while documenting legacy fee collection logic. -ICRC-107 introduces a standardized mechanism for on-chain fee collection, ensuring clarity and interoperability across different ICRC-based ledgers. It defines: +### ICRC-107 Proposal -- A fee collection configuration specifying the collector account (icrc107_fee_col), which applies to all subsequent blocks. +ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity and interoperability across different ICRC-based ledgers. It defines: +- A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. - A new block type for setting `icrc107_fee_col` to designate the fee collector. +- A backward-compatible mechanism where, if `icrc107_fee_col` has never been set, legacy `fee_col` logic applies. -- A backward-compatible mechanism where, if `icrc107_fee_col` has never been set, legacy fee_col logic applies. +By embedding fee collection settings entirely on-chain, ICRC-107 ensures transparency and simplifies integration with external tools. -By embedding fee collection settings entirely on-chain, ICRC-107 eliminates reliance on off-chain metadata, simplifies integration with wallets and explorers, and ensures full transparency in fee handling. --- ## 2. Fee Collection ### 2.1 Overview -For each block added to the ledger, some party may have to pay a fee. The amount and payer of the fee depend on the specific operation type. - -Fee collection settings determine how fees are processed. These settings consist of: +Each block requires a fee, which a designated party must pay based on the operation type. -- A fee collector account (`fee_col`). If `fee_col` has never been set in any block, fees are burned by default. Burning occurs by removing the fee from the paying account and reducing the total token supply by that amount. -- A list of operations (`fee_ops`) for which fees are collected. If an operation is not in `fee_ops`, the fee is burned. +The fee collection configuration controls how the ledger processes fees. This configuration consists of a single global fee collector account (`icrc107_fee_col`). When a block updates this setting, the change takes effect immediately. -Changes to fee collection settings are recorded on-chain and take effect immediately in the block where they appear. +- If `icrc107_fee_col` is set to a ledger account, that account collects all subsequent fees. +- If `icrc107_fee_col` is set to the empty account (see below), the ledger burns all subsequent fees. +- The most recent `icrc107_fee_col` setting applies to each block. +- By default, the ledger burns all block fees until the first `icrc107_fee_col` setting is applied. If `icrc107_fee_col` has never been set, the ledger follows legacy `fee_col` logic (see Section 5). +- A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. -### 2.2 Fee Collection Settings +Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic may be in place (See Section 5). -**Backwards Compatibility:** -To ensure compatibility with existing ICRC-3 implementations, `fee_col` **MUST** be recorded using the existing array format: - -``` -fee_col; -variant { - Array = vec { - variant { Blob = principal_blob }; - variant { Blob = subaccount_blob }; - } -} -``` +This standard uses the Account representation defined in ICRC-3, as an array of blobs. +The empty account is represented as an empty array (`[]`) in the `icrc107_fee_col` field. The empty account (`[]`) is a reserved value that explicitly indicates fee burning, ensuring unambiguous interpretation across implementations. +When `icrc107_fee_col` is set to the empty account, all subsequent fees are burned. This ensures a clear and unambiguous mechanism for fee burning. -- The **first Blob** represents the **principal** of the fee collector. -- The **second Blob** represents the **subaccount**, or a zeroed-out Blob if no subaccount is used. -- Burning fees (`fee_col = []`): - * Fees are burned if `fee_col` is not set in any prior vlock. - * To explicitly indicate burning, set `fee_col` to `variant {Array = vec {} }`, meaning the array is empty. - * This ensures that existing tools and explorers relying on the current `fee_col` format remain functional. +### 2.2 ICRC-107 Block Schema -A block **MAY** include the following fields to define or update fee collection settings: +ICRC-107 introduces a new block type that follows the ICRC-3 specification to configure `icrc107_fee_col`. -- **`fee_col`** (optional, `Map`) - - `principal`: `Blob` — The principal of the fee collector. - - `subaccount`: `Blob` (optional) — The subaccount identifier, if applicable. +In addition to the ICRC-3 specific requirements, a fee collector configuration block: +- **MUST** contain a field `btype` with value `"107feecol"`. +- **MUST** contain a field `icrc107_fee_col` of type `Array`, specifying the owner and, optionally, a subaccount, following the ICRC-3 standard. -- **`fee_ops`** (optional, `Vec`) - - A list of operation types for which fees **should** be collected. - - If omitted, defaults to `["1xfer","2xfer"]` (fees are collected only for transfers). - - If `fee_ops` is explicitly set to an empty list (`[]`), no fees are collected, and all fees are burned. +#### Example Block +Below is an example of a valid **fee collector configuration block** that sets the fee collector to a specific account: -**Note:** The block format follows the [ICRC-3 standard](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-3.md). +``` +variant { Map = vec { + // Block type: indicates this is a fee collector configuration block + record { "btype"; variant { Text = "107feecol" }}; + // Fee collector account: specifies the account that will collect fees + record { "icrc107_fee_col"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount + }} }; + // Timestamp: indicates when the block was created + 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" + }}; +}}; +``` ---- +A block that unsets the fee collector, i.e. from this point onward all fees are burned: -## 3. Handling Fee Collection for ICRC-1 and ICRC-2 Blocks +``` +variant { Map = vec { + record { "btype"; variant { Text = "107feecol" }}; + record { "icrc107_fee_col"; variant { Array = vec { + }}}; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + record { "phash"; + variant { + Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" + }}; +}}; +``` -### 3.1 Determining the Fee for ICRC-1 and ICRC-2 Blocks -Blocks follow the ICRC-3 standard. To determine the **final fee amount** for a block: -1. **Check `tx.fee`** - - If present, `tx.fee` is the fee for this block. +--- -2. **Else, check the top-level `fee` field** - - If `tx.fee` is **not set**, then a top-level field `fee` exists in the block, and that is the fee. +# ICRC-107: Methods for Setting and Getting Fee Collection +## 3. Methods for Setting and Getting Fee Collection -The fee payer is always the source account in both transfer and approve transactions. +### 3.1 `icrc107_set_fee_collection` +This method allows a ledger controller to update the fee collection settings. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in the next block added to the ledger. -### **3.2 Determining the Active Fee Settings for a Block** +``` +icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); +``` -To determine the **active fee settings** (who receives the fee and whether it is collected or burned), follow this algorithm: +#### Request Type: +``` +type Account = record { + owner: principal; + subaccount: opt blob; +}; -1. **Find the most recent block (including the current block) that defines `fee_col`** - - If `fee_col = []`, all fees in the block are **burned**, and `fee_ops` is ignored. - - Otherwise, use this value as the **active fee collector** (`active_fee_col`). - - If no `fee_col` is found in any prior block, all fees **are burned by default**. +type SetFeeCollectionRequest = record { + icrc107_fee_col: opt Account; +}; +``` -2. **Determine `fee_ops` from the same block where `fee_col` was set** - - If `fee_ops` is set in that block, use it as the **active fee_ops**. - - If `fee_ops` is **not set** in that block, **default to `["1xfer", "2xfer"]`**, meaning only transfer operations collect fees. +This method MUST only be callable by the ledger controller or authorized principals. Unauthorized calls SHOULD result in an error. -3. **Determine whether the fee should be collected or burned** - - Identify the **block type**: - - If `btype` is present, use it. - - Otherwise, infer from `tx.op`. - - If the **block type is in `fee_ops`**, fees are **collected** by `active_fee_col`. - - Otherwise, fees are **burned**. +- If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. +- If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. +The `icrc107_set_fee_collection` method MUST return an error in the following cases: -### **3.3 Mapping `fee_ops` to Block Types** +- The caller is not authorized to modify fee collection settings. +- The provided `Account` is invalid (e.g., malformed principal or subaccount)." - The `fee_ops` field specifies which block types **have their fees collected** instead of burned. For ICRC-1 and ICRC-2 blocks, `fee_ops` maps to block types as follows: - | **fee_ops Entry** | **Block Types Affected** | - |-------------------|--------------------------| - | `"1xfer"` | Blocks with `btype = "1xfer"` or where `btype` is not set, `tx.op = "xfer"`, and `tx.spender` is **not** set. | - | `"2xfer"` | Blocks with `btype = "2xfer"` or where `btype` is not set, `tx.op = "xfer"`, and `tx.spender` **is** set. | - | `"2approve"` | Blocks with `btype = "2approve"` or where `btype` is not set and `tx.op = "approve"`. | +### 3.2 `icrc107_get_fee_collection` +This method retrieves the currently active fee collection settings. Unless changed, these settings apply to the next block added to the ledger. -#### Key Rules: +``` +icrc107_get_fee_collection: () -> (opt Account) query; +``` -1. **Determine Block Type** - - If `btype` is present, it takes precedence. - - Otherwise, use `tx.op` to determine the block type. -2. **Check if `fee_ops` applies to the block type** - - If the block type corresponds to an entry in `fee_ops`, the fee is collected by `fee_col`. - - Otherwise, the fee is burned. +This method should return the currently active fee collection settings: + - If the response is `null`, fees are burned + - If the response is a valid `Account`, fees are collected by that account. --- -## 4. Minimal API for Fee Collection Settings -### 4.1 `icrc107_set_fee_collection` +## 4. Reporting Compliance -This method allows a ledger controller to update the fee collection settings. It modifies the `fee_col` account, which determines where collected fees are sent, and updates the `fee_ops` list, which specifies the transaction types for which fees should be collected. The updated settings are recorded in the next block added to the ledger. +Compliance with the current standard is reported as follows. -``` -icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); -``` +### 4.1 Supported Standards -with +Ledgers implementing ICRC-107 **MUST** indicate their compliance through the `icrc1_supported_standards` and `icrc10_supported_standards` methods, by including in the output of these methods: ``` -type Account = record { - owner: principal; - subaccount: opt blob; -}; - -type SetFeeCollectionRequest = record { - fee_col: opt Account; - fee_ops: Vec -} +variant { Vec = vec { + record { + "name"; variant { Text = "ICRC-107" }; + "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107.md" }; + } +}}; ``` -If `fee_col` is not provided then fee collection reverts to fee burning. -The fee collections settings will be recorded in the first block that is created after calling this endpoint. - +### 4.2 Supported Block Types -### 4.2 `icrc107_get_fee_collection` - -This method retrieves the currently active fee collection settings. Unless they are changed they would apply to the next block added to the ledger. +Ledgers implementing ICRC-107 **MUST** report the new block type in response to `icrc3_supported_block_types`. The output of the call must include ``` -icrc107_get_fee_collection: () -> (opt Account, Vec) query; +variant { Vec = vec { + record { "name"; variant { Text = "107feecol" }; "url"; variant { Text = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107.md" } }; +}}; ``` -`icrc107_get_fee_collection` returns two values: - -* `opt Account` (`fee_col`) – The active fee collector account. - - If `null`, fees are burned (this corresponds to `fee_col=[]` on-chain). - - Otherwise, fees are collected by the returned `Account` +## 5. Legacy Fee Collection Mechanisms -* `Vec` (`fee_ops`) – The list of operation types for which fees are collected. - - If `fee_ops=[]`, no fees are collected, and all fees are burned. This is equivalent to `fee_col=[]`, meaning that all transactions incur a fee that is removed from the supply. - - The API **does not apply a default** — it always returns the explicitly set value. The defaulting behavior (`["1xfer", "2xfer"]`) applies only at the block processing level when `fee_ops` is missing from a block. +The Dfinity maintained ICRC ledgers include a fee colelction mechanism which, for completeness is described below. ---- +### 5.1 Legacy Behavior (`fee_col`) -## 5. Interaction with Future Standards +- If `fee_col` is set in a block, the designated account collects **only transfer fees** (`1xfer` and `2xfer`) from that block onward. +- Other fees (e.g., `2approve`) are burned. +- If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. -Any new standard that introduces additional block types **MUST** specify how those block types interact with the fee collection mechanism defined in **ICRC-107**. Specifically: -1. **Fee Applicability** - - Clarify which new block types, if any, are subject to fee collection. +New implementations SHOULD avoid using fee_col and instead use icrc107_fee_col for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. -2. **Fee Payer Determination** - - Define how the fee amount is determined and who is responsible for paying the fee. +### 5.2 Handling Fee Collection for ICRC-1 and ICRC-2 Blocks -3. **Fee Collection Rules** - - Define new entry types for `fee_col` corresponding to the new block types. - - Specify how `fee_ops` should be applied to determine whether fees for the new block types are collected or burned. +To determine who collects the fee in a block: -By ensuring that any future standards explicitly define their interaction with fee collection, ICRC-107 remains a robust and extensible framework. +1. Check for fee collection configuration + - If a previous block set `icrc107_fee_col`, the ledger uses the behavior specified by this standard. ---- -## 6. Reporting Compliance with ICRC-Supported Standards +2. If no `icrc107_fee_col` setting exists (legacy behavior): -Ledgers implementing ICRC-107 **MUST** indicate their compliance through the `icrc1_supported_standards` and `icrc10_supported_standards` methods, as defined in ICRC-1 and ICRC-10 by including the following record in the output of these methods: + - If the block is of type `2approve` then the fee is burned or no `fee_col` + - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: + - If `fee_col` is specified in the block the fee is collected by `fee_col`. + - If `fee_col_block` is specified in the block the fee is collected by the account specified in the `fee_col` field of the block with index `fee_col_block`. -``` -record { - name = "ICRC-107"; - url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107.md" -} -``` +--- From 06e6d5dd6628752fb2b5a7e870fd2a8b80144b01 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:06:01 +0100 Subject: [PATCH 18/45] one more pass --- ICRCs/ICRC-107/ICRC-107.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 02e5b725..50dd65a8 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -9,9 +9,9 @@ By embedding fee collection settings on-chain, ICRC-107 provides the following b - Interoperability: Wallets and explorers can rely on consistent fee handling across ICRC-based ledgers. -- Simplified Integration: Eliminates the need for off-chain metadata to determine fee collection behavior +- Simplified Integration: Eliminates the need for off-chain metadata to determine fee collection behavior. -ICRC-107 applies to all ICRC-based ensuring consistent fee collection behavior across transfers and approvals. This standard is backward-compatible with existing ICRC-based ledgers, allowing them to transition gradually to the new fee collection mechanism, while documenting legacy fee collection logic. +ICRC-107 applies to all ICRC-based ledgers ensuring consistent fee collection behavior across transfers and approvals. This standard is backward-compatible with existing ICRC-based ledgers, allowing them to transition gradually to the new fee collection mechanism, while documenting legacy fee collection logic. ### ICRC-107 Proposal @@ -41,9 +41,7 @@ The fee collection configuration controls how the ledger processes fees. This co Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic may be in place (See Section 5). -This standard uses the Account representation defined in ICRC-3, as an array of blobs. -The empty account is represented as an empty array (`[]`) in the `icrc107_fee_col` field. The empty account (`[]`) is a reserved value that explicitly indicates fee burning, ensuring unambiguous interpretation across implementations. -When `icrc107_fee_col` is set to the empty account, all subsequent fees are burned. This ensures a clear and unambiguous mechanism for fee burning. +The empty account is represented as an empty array (`[]`) in the `icrc107_fee_col` field and it is a reserved value that explicitly indicates fee burning, ensuring unambiguous interpretation across implementations. ### 2.2 ICRC-107 Block Schema @@ -168,7 +166,7 @@ variant { Vec = vec { ### 4.2 Supported Block Types -Ledgers implementing ICRC-107 **MUST** report the new block type in response to `icrc3_supported_block_types`. The output of the call must include +Ledgers implementing ICRC-107 **MUST** report the new block type in response to `icrc3_supported_block_types`. The output of the call must include ``` variant { Vec = vec { @@ -178,7 +176,7 @@ variant { Vec = vec { ## 5. Legacy Fee Collection Mechanisms -The Dfinity maintained ICRC ledgers include a fee colelction mechanism which, for completeness is described below. +The Dfinity maintained ICRC ledgers include a fee collection mechanism which, for completeness is described below. ### 5.1 Legacy Behavior (`fee_col`) @@ -186,8 +184,7 @@ The Dfinity maintained ICRC ledgers include a fee colelction mechanism which, fo - Other fees (e.g., `2approve`) are burned. - If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. - -New implementations SHOULD avoid using fee_col and instead use icrc107_fee_col for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. +New implementations SHOULD avoid using fee_col and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. ### 5.2 Handling Fee Collection for ICRC-1 and ICRC-2 Blocks @@ -202,6 +199,6 @@ To determine who collects the fee in a block: - If the block is of type `2approve` then the fee is burned or no `fee_col` - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - - If `fee_col_block` is specified in the block the fee is collected by the account specified in the `fee_col` field of the block with index `fee_col_block`. + - If `fee_col_block` is specified use the `fee_col` from the referenced block index. --- From eb7d6a4c4228621e0593e6fa473c9b9ba2530c6f Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:44:15 +0100 Subject: [PATCH 19/45] further clarifications on legacy behaviour --- ICRCs/ICRC-107/ICRC-107.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 50dd65a8..019c4917 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -41,7 +41,8 @@ The fee collection configuration controls how the ledger processes fees. This co Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic may be in place (See Section 5). -The empty account is represented as an empty array (`[]`) in the `icrc107_fee_col` field and it is a reserved value that explicitly indicates fee burning, ensuring unambiguous interpretation across implementations. +Fee burning is explicitly recorded on-chain by setting `icrc_107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation accross implementations. + ### 2.2 ICRC-107 Block Schema @@ -51,6 +52,7 @@ In addition to the ICRC-3 specific requirements, a fee collector configuration b - **MUST** contain a field `btype` with value `"107feecol"`. - **MUST** contain a field `icrc107_fee_col` of type `Array`, specifying the owner and, optionally, a subaccount, following the ICRC-3 standard. +- To explicitly burn fees, `icrc107_fee_col` **MUST** be set to `variant { Array = vec {}}`. #### Example Block @@ -75,7 +77,7 @@ variant { Map = vec { }}; ``` -A block that unsets the fee collector, i.e. from this point onward all fees are burned: +A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): ``` variant { Map = vec { @@ -140,9 +142,10 @@ icrc107_get_fee_collection: () -> (opt Account) query; This method should return the currently active fee collection settings: - - If the response is `null`, fees are burned - - If the response is a valid `Account`, fees are collected by that account. + - If the response is `null`, fees are burned. This corresponds to `icrc107_fee_col = variant { Array = vec {}}` on-chain. + - If the response is a valid `Account`, fees are collected by that account. This corresponds to `icrc_107_fee_col` being set to the ICRC-3 representation of the account on-chain. +This method strictly returns the last explicitly set value of `icrc107_fee_col`. It does not infer defaults, and if no fee collector was ever set, it returns null. --- @@ -174,14 +177,13 @@ variant { Vec = vec { }}; ``` -## 5. Legacy Fee Collection Mechanisms +## 5. Note on Legacy Fee Collection Mechanisms The Dfinity maintained ICRC ledgers include a fee collection mechanism which, for completeness is described below. ### 5.1 Legacy Behavior (`fee_col`) -- If `fee_col` is set in a block, the designated account collects **only transfer fees** (`1xfer` and `2xfer`) from that block onward. -- Other fees (e.g., `2approve`) are burned. +- If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior - If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. New implementations SHOULD avoid using fee_col and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. From d3accbf0c778115b7cd99f1bfea58f6de1dc99f6 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:57:11 +0100 Subject: [PATCH 20/45] typos --- ICRCs/ICRC-107/ICRC-107.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 019c4917..9d2aa862 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -41,7 +41,7 @@ The fee collection configuration controls how the ledger processes fees. This co Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic may be in place (See Section 5). -Fee burning is explicitly recorded on-chain by setting `icrc_107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation accross implementations. +Fee burning is explicitly recorded on-chain by setting `icrc107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation accross implementations. ### 2.2 ICRC-107 Block Schema @@ -143,9 +143,9 @@ icrc107_get_fee_collection: () -> (opt Account) query; This method should return the currently active fee collection settings: - If the response is `null`, fees are burned. This corresponds to `icrc107_fee_col = variant { Array = vec {}}` on-chain. - - If the response is a valid `Account`, fees are collected by that account. This corresponds to `icrc_107_fee_col` being set to the ICRC-3 representation of the account on-chain. + - If the response is a valid `Account`, fees are collected by that account. This corresponds to `icrc107_fee_col` being set to the ICRC-3 representation of the account on-chain. -This method strictly returns the last explicitly set value of `icrc107_fee_col`. It does not infer defaults, and if no fee collector was ever set, it returns null. +This method strictly returns the last explicitly set value of `icrc107_fee_col`. It does not infer defaults, and if no fee collector was ever set, it returns `opt Account = null`. --- @@ -183,7 +183,7 @@ The Dfinity maintained ICRC ledgers include a fee collection mechanism which, fo ### 5.1 Legacy Behavior (`fee_col`) -- If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior +- If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior as implemented in Dfinity-maintained ICRC-3 ledgers. - If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. New implementations SHOULD avoid using fee_col and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. From 12eb7caaba8d4f30a95700e93b34421c898405e2 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:19:46 +0100 Subject: [PATCH 21/45] typo --- ICRCs/ICRC-107/ICRC-107.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 9d2aa862..a37dd6d4 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -39,9 +39,9 @@ The fee collection configuration controls how the ledger processes fees. This co - By default, the ledger burns all block fees until the first `icrc107_fee_col` setting is applied. If `icrc107_fee_col` has never been set, the ledger follows legacy `fee_col` logic (see Section 5). - A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. -Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic may be in place (See Section 5). +Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic that may be in place (See Section 5). -Fee burning is explicitly recorded on-chain by setting `icrc107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation accross implementations. +Fee burning is explicitly recorded on-chain by setting `icrc107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation across implementations. ### 2.2 ICRC-107 Block Schema From 9582f01ce251d7c07b564ab65b9acb4c859036a8 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:02:31 +0100 Subject: [PATCH 22/45] Update ICRCs/ICRC-107/ICRC-107.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist --- ICRCs/ICRC-107/ICRC-107.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index a37dd6d4..d312aee0 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -186,7 +186,7 @@ The Dfinity maintained ICRC ledgers include a fee collection mechanism which, fo - If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior as implemented in Dfinity-maintained ICRC-3 ledgers. - If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. -New implementations SHOULD avoid using fee_col and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. +New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. ### 5.2 Handling Fee Collection for ICRC-1 and ICRC-2 Blocks From 0944115f4455bd33747eb260adb9db36c37c74d4 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:05:29 +0100 Subject: [PATCH 23/45] Update ICRCs/ICRC-107/ICRC-107.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist From 100b5322f1554c0020bab24d89c4c81d3086e048 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 20 Mar 2025 11:21:59 +0100 Subject: [PATCH 24/45] addressed comments --- ICRCs/ICRC-107/ICRC-107.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index d312aee0..7feb9e77 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -19,7 +19,7 @@ ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity a - A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. - A new block type for setting `icrc107_fee_col` to designate the fee collector. -- A backward-compatible mechanism where, if `icrc107_fee_col` has never been set, legacy `fee_col` logic applies. +- A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. By embedding fee collection settings entirely on-chain, ICRC-107 ensures transparency and simplifies integration with external tools. @@ -102,7 +102,7 @@ variant { Map = vec { ### 3.1 `icrc107_set_fee_collection` -This method allows a ledger controller to update the fee collection settings. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in the next block added to the ledger. +This method allows a ledger controller to update the fee collection settings. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. ``` icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); @@ -128,7 +128,7 @@ This method MUST only be callable by the ledger controller or authorized princip The `icrc107_set_fee_collection` method MUST return an error in the following cases: - The caller is not authorized to modify fee collection settings. -- The provided `Account` is invalid (e.g., malformed principal or subaccount)." +- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount)." ### 3.2 `icrc107_get_fee_collection` From 597195c29834cc3cef0121c0493f21ed062fbb59 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 20 Mar 2025 11:25:07 +0100 Subject: [PATCH 25/45] rewrite legacy part --- ICRCs/ICRC-107/ICRC-107.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 7feb9e77..43b081a4 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -36,7 +36,7 @@ The fee collection configuration controls how the ledger processes fees. This co - If `icrc107_fee_col` is set to a ledger account, that account collects all subsequent fees. - If `icrc107_fee_col` is set to the empty account (see below), the ledger burns all subsequent fees. - The most recent `icrc107_fee_col` setting applies to each block. -- By default, the ledger burns all block fees until the first `icrc107_fee_col` setting is applied. If `icrc107_fee_col` has never been set, the ledger follows legacy `fee_col` logic (see Section 5). +- Until `icrc107_fee_col` is set fees are burned, unless legacy `fee_col` logic applies (see Section 5). - A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic that may be in place (See Section 5). @@ -183,8 +183,11 @@ The Dfinity maintained ICRC ledgers include a fee collection mechanism which, fo ### 5.1 Legacy Behavior (`fee_col`) + +- Until `icrc107_fee_col` is set, the ledger follows this legacy behavior, using `fee_col` only for transfers. +- If `fee_col`is not set, all fees are burned. - If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior as implemented in Dfinity-maintained ICRC-3 ledgers. -- If `icrc107_fee_col` is not set, the ledger follows this legacy behavior, using `fee_col` only for transfers. + New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. @@ -198,7 +201,7 @@ To determine who collects the fee in a block: 2. If no `icrc107_fee_col` setting exists (legacy behavior): - - If the block is of type `2approve` then the fee is burned or no `fee_col` + - If the block is of type `2approve` then the fee is burned - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - If `fee_col_block` is specified use the `fee_col` from the referenced block index. From 1cef2a6bfd94ebfb579dc1887ccc36e3ec8ed054 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 20 Mar 2025 11:29:09 +0100 Subject: [PATCH 26/45] rewrite --- ICRCs/ICRC-107/ICRC-107.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 43b081a4..546a464d 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -35,7 +35,7 @@ The fee collection configuration controls how the ledger processes fees. This co - If `icrc107_fee_col` is set to a ledger account, that account collects all subsequent fees. - If `icrc107_fee_col` is set to the empty account (see below), the ledger burns all subsequent fees. -- The most recent `icrc107_fee_col` setting applies to each block. +- An `icrc107_fee_col` block configures the fee collection for all subsequent blocks, until superseded by another `icrc107_fee_col` block. - Until `icrc107_fee_col` is set fees are burned, unless legacy `fee_col` logic applies (see Section 5). - A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. From 1f30ad30aa5deb6c115adef0c95a09460a1265fe Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:30:32 +0200 Subject: [PATCH 27/45] introduced tx structure --- ICRCs/ICRC-107/ICRC-107.md | 124 ++++++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 35 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 546a464d..de184272 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,6 +1,6 @@ # ICRC-107: Fee Collection -## 1. Introduction & Motivation +## Introduction & Motivation Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. Some ledgers assign a designated fee collector for transfer operations, while others burn fees for approve operations—even when fee collection details appear in approve blocks. This inconsistency leads to challenges in defining fee collection rules, recording fee settings on-chain, and ensuring clear semantics for wallets and explorers. ICRC-107 builds on the ICRC-3 block schema, introducing a new block type (`107feecol`) to configure fee collection settings on-chain. This ensures compatibility with existing ICRC-3 implementations while extending functionality for fee handling. By embedding fee collection settings on-chain, ICRC-107 provides the following benefits: @@ -23,11 +23,20 @@ ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity a By embedding fee collection settings entirely on-chain, ICRC-107 ensures transparency and simplifies integration with external tools. + +## 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`, the owner's principal). +- **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. + + + --- -## 2. Fee Collection +## Fee Collection -### 2.1 Overview +### Overview Each block requires a fee, which a designated party must pay based on the operation type. @@ -35,7 +44,7 @@ The fee collection configuration controls how the ledger processes fees. This co - If `icrc107_fee_col` is set to a ledger account, that account collects all subsequent fees. - If `icrc107_fee_col` is set to the empty account (see below), the ledger burns all subsequent fees. -- An `icrc107_fee_col` block configures the fee collection for all subsequent blocks, until superseded by another `icrc107_fee_col` block. +- An `icrc107_fee_col` block configures the fee collection for all subsequent blocks, until superseded by another `icrc107_fee_col` block. - Until `icrc107_fee_col` is set fees are burned, unless legacy `fee_col` logic applies (see Section 5). - A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. @@ -44,15 +53,34 @@ Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic that Fee burning is explicitly recorded on-chain by setting `icrc107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation across implementations. -### 2.2 ICRC-107 Block Schema -ICRC-107 introduces a new block type that follows the ICRC-3 specification to configure `icrc107_fee_col`. -In addition to the ICRC-3 specific requirements, a fee collector configuration block: +### ICRC-107 Transaction and Block Schema + +Each `107feecol` block consists of the following top-level fields: + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|-------------------|------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `btype` | `Text` | Yes | **MUST** be `"107feecol"`. | +| `ts` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) 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 fee collection configuration transaction. See `tx` Field Schema below. | + + +### `tx` Field Schema + +The `tx` field for a `107feecol` block is a `Map` that contains the following fields: + +| Field | Type (ICRC-3 `Value`) | Required | Description | +|-----------------|------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `op` | `Text` | Yes | **MUST** be `"set107feecol"`. Explicitly identifies the operation and standard within the transaction, enabling self-contained transaction records and unambiguous hashing. | +| `fee_col` | `Array` (Account) | Yes | The target fee collector account. If `variant { Array = vec {} }`, it signifies that fees should be burned. See Common Elements for Account representation. | +| `created_at_time` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) when the user created the transaction. This value **MUST** be provided by the caller to enable transaction deduplication as per ICRC-1. | +| `caller` | `Blob` | Yes | The principal of the user or canister that originated this transaction. | + + + -- **MUST** contain a field `btype` with value `"107feecol"`. -- **MUST** contain a field `icrc107_fee_col` of type `Array`, specifying the owner and, optionally, a subaccount, following the ICRC-3 standard. -- To explicitly burn fees, `icrc107_fee_col` **MUST** be set to `variant { Array = vec {}}`. #### Example Block @@ -62,13 +90,18 @@ Below is an example of a valid **fee collector configuration block** that sets t variant { Map = vec { // Block type: indicates this is a fee collector configuration block record { "btype"; variant { Text = "107feecol" }}; - // Fee collector account: specifies the account that will collect fees - record { "icrc107_fee_col"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount + // The user's intended transaction + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "107setfeecol" } }; // Operation name, as per schema + record { "fee_col"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount + }} }; + record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat } }; // Timestamp for deduplication (June 27, 2025, 15:28:47 UTC) + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Example caller principal }} }; // Timestamp: indicates when the block was created - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; // Existing block timestamp // Parent hash: links this block to the previous block in the chain record { "phash"; variant { @@ -76,15 +109,29 @@ variant { Map = vec { }}; }}; ``` +If one wanted to set the fee collector to be the default account of principal identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the "fee_col" field within `tx` should be set to variant ``{ Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } }``. + + A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): ``` variant { Map = vec { + // Block type: indicates this is a fee collector configuration block record { "btype"; variant { Text = "107feecol" }}; - record { "icrc107_fee_col"; variant { Array = vec { + // The user's intended transaction, from which the hash is computed + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "set107feecol" } }; // Operation name, as per schema + record { "fee_col"; variant { Array = vec { + // Empty array to signify burning fees }}}; - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; + record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication (incremented from previous example) + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Example caller principal + }} }; + // Timestamp: indicates when the block was created (can be derived from tx.created_at_time or ledger time) + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; // Existing block timestamp + // Parent hash: links this block to the previous block in the chain record { "phash"; variant { Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" @@ -98,30 +145,38 @@ variant { Map = vec { # ICRC-107: Methods for Setting and Getting Fee Collection -## 3. Methods for Setting and Getting Fee Collection +## Methods for Setting and Getting Fee Collection -### 3.1 `icrc107_set_fee_collection` +### `icrc107_set_fee_collection` This method allows a ledger controller to update the fee collection settings. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. -``` -icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (); ``` -#### Request Type: -``` type Account = record { owner: principal; subaccount: opt blob; }; -type SetFeeCollectionRequest = record { +type SetFeeCollectionArgs = record { icrc107_fee_col: opt Account; + created_at_time: opt nat64; }; + +type SetFeeCollectionError = variant { + AccessDenied : text; // The caller is not authorized to modify fee collection settings. + InvalidAccount : text; // The provided account for fee collection is invalid (e.g., minting account, anonymous principal, malformed). + Duplicate : record { duplicate_of : nat }; // A duplicate transaction already exists at position `duplicate_of` in the ledger. + GenericError : record { error_code : nat; message : text }; +}; + +icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (variant { Ok: empty; Err: SetFeeCollectionError }); + ``` -This method MUST only be callable by the ledger controller or authorized principals. Unauthorized calls SHOULD result in an error. +This method MUST only be callable by a controller of the ledger or some other authorized principals. Unauthorized calls SHOULD result in an error. +- The ledger MUST construct the tx field (an ICRC-3 Value of type Map as described in the previous section) from this request, place it in the generated `107feecol` block and append the block to the ledger. - If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. - If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. @@ -129,14 +184,15 @@ The `icrc107_set_fee_collection` method MUST return an error in the following ca - The caller is not authorized to modify fee collection settings. - The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount)." +- Transaction deduplication is enabled for the transaction (i.e. `created_at_time` is set) and an identical transaction already exist. -### 3.2 `icrc107_get_fee_collection` +### `icrc107_get_fee_collection` This method retrieves the currently active fee collection settings. Unless changed, these settings apply to the next block added to the ledger. ``` -icrc107_get_fee_collection: () -> (opt Account) query; +icrc107_get_fee_collection: () -> (variant { Ok: opt Account; Err: GetFeeCollectionError }) query; ``` @@ -150,11 +206,11 @@ This method strictly returns the last explicitly set value of `icrc107_fee_col`. --- -## 4. Reporting Compliance +## Reporting Compliance Compliance with the current standard is reported as follows. -### 4.1 Supported Standards +### Supported Standards Ledgers implementing ICRC-107 **MUST** indicate their compliance through the `icrc1_supported_standards` and `icrc10_supported_standards` methods, by including in the output of these methods: @@ -167,7 +223,7 @@ variant { Vec = vec { }}; ``` -### 4.2 Supported Block Types +### Supported Block Types Ledgers implementing ICRC-107 **MUST** report the new block type in response to `icrc3_supported_block_types`. The output of the call must include @@ -177,11 +233,11 @@ variant { Vec = vec { }}; ``` -## 5. Note on Legacy Fee Collection Mechanisms +## Note on Legacy Fee Collection Mechanisms The Dfinity maintained ICRC ledgers include a fee collection mechanism which, for completeness is described below. -### 5.1 Legacy Behavior (`fee_col`) +### Legacy Behavior (`fee_col`) - Until `icrc107_fee_col` is set, the ledger follows this legacy behavior, using `fee_col` only for transfers. @@ -191,7 +247,7 @@ The Dfinity maintained ICRC ledgers include a fee collection mechanism which, fo New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. -### 5.2 Handling Fee Collection for ICRC-1 and ICRC-2 Blocks +### Handling Fee Collection for ICRC-1 and ICRC-2 Blocks To determine who collects the fee in a block: @@ -205,5 +261,3 @@ To determine who collects the fee in a block: - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - If `fee_col_block` is specified use the `fee_col` from the referenced block index. - ---- From aeac0560dcc3b24b6a57431b49a28e7aa0ab55aa Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:31:21 +0200 Subject: [PATCH 28/45] fixed an error type --- ICRCs/ICRC-107/ICRC-107.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index de184272..ae7169d4 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -192,7 +192,7 @@ The `icrc107_set_fee_collection` method MUST return an error in the following ca This method retrieves the currently active fee collection settings. Unless changed, these settings apply to the next block added to the ledger. ``` -icrc107_get_fee_collection: () -> (variant { Ok: opt Account; Err: GetFeeCollectionError }) query; +icrc107_get_fee_collection: () -> (variant { Ok: opt Account; Err: record { error_code : nat; message : text } }) query; ``` From 92436c8624629b364e8954c72859ccae4c80f251 Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 30 Jun 2025 09:55:11 +0200 Subject: [PATCH 29/45] fixed example --- ICRCs/ICRC-107/ICRC-107.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index ae7169d4..29418efa 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -98,10 +98,10 @@ variant { Map = vec { 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" }; // Subaccount }} }; record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat } }; // Timestamp for deduplication (June 27, 2025, 15:28:47 UTC) - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Example caller principal + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal }} }; // Timestamp: indicates when the block was created - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; // Existing block timestamp + 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 { @@ -126,11 +126,11 @@ variant { Map = vec { record { "fee_col"; variant { Array = vec { // Empty array to signify burning fees }}}; - record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication (incremented from previous example) - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Example caller principal + record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal }} }; - // Timestamp: indicates when the block was created (can be derived from tx.created_at_time or ledger time) - record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat } }; // Existing block timestamp + // Timestamp: indicates when the block was created + 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 { From b81f01315d6c35322258a9bc387f49fad88e86ce Mon Sep 17 00:00:00 2001 From: bogwar <51327868+bogwar@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:34:33 +0200 Subject: [PATCH 30/45] final(?) pass --- ICRCs/ICRC-107/ICRC-107.md | 77 ++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 29418efa..d65fb287 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -38,20 +38,20 @@ This standard follows the conventions set by ICRC-3, inheriting key structural c ### Overview -Each block requires a fee, which a designated party must pay based on the operation type. +Each block may specify a fee which a designated party must pay based on the operation type. -The fee collection configuration controls how the ledger processes fees. This configuration consists of a single global fee collector account (`icrc107_fee_col`). When a block updates this setting, the change takes effect immediately. +The fee collection configuration controls how the ledger processes these fees. This configuration consists of a single global fee collector account (`icrc107_fee_col`). When a block updates this setting, the change takes effect immediately. -- If `icrc107_fee_col` is set to a ledger account, that account collects all subsequent fees. -- If `icrc107_fee_col` is set to the empty account (see below), the ledger burns all subsequent fees. -- An `icrc107_fee_col` block configures the fee collection for all subsequent blocks, until superseded by another `icrc107_fee_col` block. -- Until `icrc107_fee_col` is set fees are burned, unless legacy `fee_col` logic applies (see Section 5). -- A **fee collector configuration block** records these settings on-chain, ensuring transparent fee collection. +- A **fee collector configuration block** records changes to `icrc107_fee_col` value. +- If `icrc107_fee_col` is set to a ledger account, that account collects the fees in all subsequent blocks. +- If `icrc107_fee_col` is set to the empty account (see below), the ledger burns the fees in all subsequent blocks. +- An `107feecol` block configures the fee collection for all subsequent blocks, until superseded by another `107feecol` block. -Once `icrc107_fee_col` is set, it overrides any legacy fee collection logic that may be in place (See Section 5). -Fee burning is explicitly recorded on-chain by setting `icrc107_fee_col = variant { Array = vec {} }`. This ensures unambiguous representation across implementations. +### Interaction with Legacy Fee Collection +- Until the first `107feecol` block, legacy logic for fee collection applies (see the Legacy Fee Collection Mechanism section). +- Once a block `107feecol` block is added to the ledger, it overrides any legacy fee collection logic. @@ -73,10 +73,10 @@ The `tx` field for a `107feecol` block is a `Map` that contains the following fi | Field | Type (ICRC-3 `Value`) | Required | Description | |-----------------|------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------| -| `op` | `Text` | Yes | **MUST** be `"set107feecol"`. Explicitly identifies the operation and standard within the transaction, enabling self-contained transaction records and unambiguous hashing. | -| `fee_col` | `Array` (Account) | Yes | The target fee collector account. If `variant { Array = vec {} }`, it signifies that fees should be burned. See Common Elements for Account representation. | +| `op` | `Text` | Yes | **MUST** be `"107_set_fee_col"`. Explicitly identifies the operation and standard within the transaction, enabling self-contained transaction records and unambiguous hashing. | +| `icrc107_fee_col` | `Array` (Account) | Yes | The target fee collector account. If `variant { Array = vec {} }`, it signifies that fees should be burned. See Common Elements for Account representation. | | `created_at_time` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) when the user created the transaction. This value **MUST** be provided by the caller to enable transaction deduplication as per ICRC-1. | -| `caller` | `Blob` | Yes | The principal of the user or canister that originated this transaction. | +| `caller` | `Blob` | Yes | The principal of the user or canister that originated this transaction. It is the principal of the ledger itself, if the fee collector is set within a ledger upgrade. | @@ -92,8 +92,8 @@ variant { Map = vec { record { "btype"; variant { Text = "107feecol" }}; // The user's intended transaction record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "107setfeecol" } }; // Operation name, as per schema - record { "fee_col"; variant { Array = vec { + record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema + record { "icrc107_fee_col"; variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount }} }; @@ -122,14 +122,14 @@ variant { Map = vec { record { "btype"; variant { Text = "107feecol" }}; // The user's intended transaction, from which the hash is computed record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "set107feecol" } }; // Operation name, as per schema - record { "fee_col"; variant { Array = vec { + record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema + record { "icrc107_fee_col"; variant { Array = vec { // Empty array to signify burning fees }}}; record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal }} }; - // Timestamp: indicates when the block was created + // Timestamp: indicates when the block was created 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"; @@ -140,16 +140,12 @@ variant { Map = vec { ``` +# ICRC-107: Methods for Setting and Getting Fee Collector Information ---- - -# ICRC-107: Methods for Setting and Getting Fee Collection -## Methods for Setting and Getting Fee Collection +### `icrc107_set_fee_collector` -### `icrc107_set_fee_collection` - -This method allows a ledger controller to update the fee collection settings. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. +This method allows a ledger controller to update the fee collector. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. ``` @@ -158,19 +154,19 @@ type Account = record { subaccount: opt blob; }; -type SetFeeCollectionArgs = record { +type SetFeeCollectorArgs = record { icrc107_fee_col: opt Account; - created_at_time: opt nat64; + created_at_time: nat64; }; -type SetFeeCollectionError = variant { - AccessDenied : text; // The caller is not authorized to modify fee collection settings. +type SetFeeCollectorError = variant { + AccessDenied : text; // The caller is not authorized to modify fee collector. InvalidAccount : text; // The provided account for fee collection is invalid (e.g., minting account, anonymous principal, malformed). Duplicate : record { duplicate_of : nat }; // A duplicate transaction already exists at position `duplicate_of` in the ledger. GenericError : record { error_code : nat; message : text }; }; -icrc107_set_fee_collection: (SetFeeCollectionRequest) -> (variant { Ok: empty; Err: SetFeeCollectionError }); +icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: empty; Err: SetFeeCollectorError }); ``` @@ -180,23 +176,23 @@ This method MUST only be callable by a controller of the ledger or some other au - If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. - If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. -The `icrc107_set_fee_collection` method MUST return an error in the following cases: +The `icrc107_set_fee_collector` method MUST return an error in the following cases: -- The caller is not authorized to modify fee collection settings. -- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount)." -- Transaction deduplication is enabled for the transaction (i.e. `created_at_time` is set) and an identical transaction already exist. +- The caller is not authorized to modify fee collector. +- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount). +- The transaction is a duplicate (as determined by its tx hash when deduplication is enabled), resulting in a `SetFeeCollectorError::Duplicate`. -### `icrc107_get_fee_collection` +### `icrc107_get_fee_collector` -This method retrieves the currently active fee collection settings. Unless changed, these settings apply to the next block added to the ledger. +This method retrieves the currently active fee collector account. Unless changed, these settings apply to the next block added to the ledger. ``` -icrc107_get_fee_collection: () -> (variant { Ok: opt Account; Err: record { error_code : nat; message : text } }) query; +icrc107_get_fee_collector: () -> (variant { Ok: opt Account; Err: record { error_code : nat; message : text } }) query; ``` -This method should return the currently active fee collection settings: +This method should return the currently active fee collector account: - If the response is `null`, fees are burned. This corresponds to `icrc107_fee_col = variant { Array = vec {}}` on-chain. - If the response is a valid `Account`, fees are collected by that account. This corresponds to `icrc107_fee_col` being set to the ICRC-3 representation of the account on-chain. @@ -233,14 +229,15 @@ variant { Vec = vec { }}; ``` -## Note on Legacy Fee Collection Mechanisms +## Legacy Fee Collection Mechanism The Dfinity maintained ICRC ledgers include a fee collection mechanism which, for completeness is described below. ### Legacy Behavior (`fee_col`) -- Until `icrc107_fee_col` is set, the ledger follows this legacy behavior, using `fee_col` only for transfers. +Until the first block of type `107feecol` the ledger follows the following legacy behavior. + - If `fee_col`is not set, all fees are burned. - If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior as implemented in Dfinity-maintained ICRC-3 ledgers. @@ -251,7 +248,7 @@ New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_co To determine who collects the fee in a block: -1. Check for fee collection configuration +1. Check for fee collector configuration - If a previous block set `icrc107_fee_col`, the ledger uses the behavior specified by this standard. From dd064abf0e83a34125edeac084b504867d00ed5d Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 11 Sep 2025 13:02:00 +0200 Subject: [PATCH 31/45] alignment with ICRC-3 refinements --- ICRCs/ICRC-107/ICRC-107.md | 287 +++++++++++++++++++++++++------------ 1 file changed, 193 insertions(+), 94 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index d65fb287..30100a62 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,57 +1,89 @@ -# ICRC-107: Fee Collection +# ICRC-107: Fee Management ## Introduction & Motivation -Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. Some ledgers assign a designated fee collector for transfer operations, while others burn fees for approve operations—even when fee collection details appear in approve blocks. This inconsistency leads to challenges in defining fee collection rules, recording fee settings on-chain, and ensuring clear semantics for wallets and explorers. ICRC-107 builds on the ICRC-3 block schema, introducing a new block type (`107feecol`) to configure fee collection settings on-chain. This ensures compatibility with existing ICRC-3 implementations while extending functionality for fee handling. -By embedding fee collection settings on-chain, ICRC-107 provides the following benefits: +Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. +Some assign a designated fee collector for transfer operations, while others burn fees for approve operations—even when fee collection details appear in approve blocks. -- Transparency: Fee collection rules are visible and auditable on the ledger. +This inconsistency creates challenges: -- Interoperability: Wallets and explorers can rely on consistent fee handling across ICRC-based ledgers. +- Fee collection rules are not uniformly defined or visible on-chain. +- Wallets and explorers cannot reliably predict where fees go. +- Integration requires off-chain metadata to interpret fee behavior. -- Simplified Integration: Eliminates the need for off-chain metadata to determine fee collection behavior. +**ICRC-107** standardizes fee collection across all ICRC-based ledgers by introducing: + +- A **global fee collector configuration** stored in the ledger state. +- A new block type (`107feecol`) for setting the fee collector on-chain. +- A consistent fee application model integrated into the ICRC-3 evaluation model. + +This ensures **transparency**, **interoperability**, and **simplified integration**. ICRC-107 applies to all ICRC-based ledgers ensuring consistent fee collection behavior across transfers and approvals. This standard is backward-compatible with existing ICRC-based ledgers, allowing them to transition gradually to the new fee collection mechanism, while documenting legacy fee collection logic. -### ICRC-107 Proposal +## Overview + +ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity and interoperability across different ICRC-based ledgers. +It extends the ICRC-3 block model with a global fee collection configuration that determines whether fees are **credited to a designated account** or **burned**, and records all changes on-chain using a new block type (`107feecol`). + +Specifically, ICRC-107 defines: + +- A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. +- A new block type for setting `icrc107_fee_col` to designate the fee collector. +- A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. + +By embedding fee collection settings entirely on-chain, ICRC-107 ensures **transparency**, **interoperability**, and **simplified integration** for wallets, explorers, and other tools. + +### Effective Fee Application + +- **Effective fee:** For any block that charges a fee, the ledger computes the **effective fee** according to ICRC-3. +- **Fee payer:** The block type (e.g., ICRC-1 transfer, ICRC-2 approve) defines which account pays the effective fee. +- **Fee collector:** + - If the current configuration specifies an account, that account is credited with the effective fee. + - If the configuration is empty (`[]`), the effective fee is burned. + +The global fee collector configuration is changed by appending a `107feecol` block: +- `tx.icrc107_fee_col = []` → all subsequent fees are burned. +- `tx.icrc107_fee_col = ` → all subsequent fees are credited to that account. -ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity and interoperability across different ICRC-based ledgers. It defines: +This configuration applies to **all subsequent blocks** until replaced by another `107feecol`. -- A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. -- A new block type for setting `icrc107_fee_col` to designate the fee collector. -- A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. +**Legacy compatibility:** +- Until the first `107feecol` block appears, ledgers follow the legacy `fee_col` mechanism (as described in the *Legacy Fee Collection* section). +- Once a `107feecol` block is present, it overrides all legacy fee collection logic. -By embedding fee collection settings entirely on-chain, ICRC-107 ensures transparency and simplifies integration with external tools. ## 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`, the owner's principal). -- **Principals** (such as the `caller`) are represented using the ICRC-3 `Value` type as `variant { Blob = }`. -- Each block includes `phash`, a `Blob` representing the hash of the parent block, and `ts`, a `Nat` representing the timestamp of the block. +This standard follows the conventions set by ICRC-3, inheriting key structural components. +- **Accounts** + Represented using the ICRC-3 `Value` type as a + `variant { Array = vec { V1 [, V2] } }` where: + - `V1` = `variant { Blob = }` (the account owner), + - `V2` = `variant { Blob = <32-byte_subaccount_bytes> }` (optional). + If no subaccount is specified, the `Array` MUST contain only the owner’s principal. ---- +- **Principals** + Represented as `variant { Blob = }`. -## Fee Collection +- **Timestamps** + Both `ts` and `tx.created_at_time` are expressed in **nanoseconds since the Unix epoch**. + They are encoded as `Nat` in ICRC-3 `Value`, but MUST fit into a `nat64`. + - `ts` is set by the ledger when the block is created. + - `created_at_time` is provided by the caller for deduplication (per ICRC-1). -### Overview +- **Parent Hash** + Each block includes `phash : Blob`, the hash of its parent block, + unless it is the genesis block (where `phash` is omitted). -Each block may specify a fee which a designated party must pay based on the operation type. -The fee collection configuration controls how the ledger processes these fees. This configuration consists of a single global fee collector account (`icrc107_fee_col`). When a block updates this setting, the change takes effect immediately. -- A **fee collector configuration block** records changes to `icrc107_fee_col` value. -- If `icrc107_fee_col` is set to a ledger account, that account collects the fees in all subsequent blocks. -- If `icrc107_fee_col` is set to the empty account (see below), the ledger burns the fees in all subsequent blocks. -- An `107feecol` block configures the fee collection for all subsequent blocks, until superseded by another `107feecol` block. +## Fee Management -### Interaction with Legacy Fee Collection -- Until the first `107feecol` block, legacy logic for fee collection applies (see the Legacy Fee Collection Mechanism section). -- Once a block `107feecol` block is added to the ledger, it overrides any legacy fee collection logic. @@ -66,78 +98,57 @@ Each `107feecol` block consists of the following top-level fields: | `phash` | `Blob` | Yes | Hash of the parent block. | | `tx` | `Map(Text, Value)` | Yes | Encodes information about the fee collection configuration transaction. See `tx` Field Schema below. | +### Minimal `tx` Schema -### `tx` Field Schema +The minimal fields required to interpret a `107feecol` block: -The `tx` field for a `107feecol` block is a `Map` that contains the following fields: +| Field | Type (ICRC-3 `Value`) | Required | Description | +|-------------------|------------------------|----------|-------------| +| `op` | `Text` | Yes | MUST be `"107_set_fee_col"`. | +| `icrc107_fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | -| Field | Type (ICRC-3 `Value`) | Required | Description | -|-----------------|------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------| -| `op` | `Text` | Yes | **MUST** be `"107_set_fee_col"`. Explicitly identifies the operation and standard within the transaction, enabling self-contained transaction records and unambiguous hashing. | -| `icrc107_fee_col` | `Array` (Account) | Yes | The target fee collector account. If `variant { Array = vec {} }`, it signifies that fees should be burned. See Common Elements for Account representation. | -| `created_at_time` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) when the user created the transaction. This value **MUST** be provided by the caller to enable transaction deduplication as per ICRC-1. | -| `caller` | `Blob` | Yes | The principal of the user or canister that originated this transaction. It is the principal of the ledger itself, if the fee collector is set within a ledger upgrade. | +## Semantics of `107feecol` Blocks +### Core State Transition +A `107feecol` block updates the ledger’s global fee collector configuration: +- If `tx.icrc107_fee_col = []` → all subsequent fees are **burned**. +- If `tx.icrc107_fee_col` encodes an account → all subsequent fees are **credited to that account**. -#### Example Block +This configuration applies **to all subsequent blocks** until replaced by another `107feecol`. -Below is an example of a valid **fee collector configuration block** that sets the fee collector to a specific account: +--- -``` -variant { Map = vec { - // Block type: indicates this is a fee collector configuration block - record { "btype"; variant { Text = "107feecol" }}; - // The user's intended transaction - record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema - record { "icrc107_fee_col"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount - }} }; - record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat } }; // Timestamp for deduplication (June 27, 2025, 15:28:47 UTC) - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal - }} }; - // Timestamp: indicates when the block was created - 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" - }}; -}}; -``` -If one wanted to set the fee collector to be the default account of principal identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the "fee_col" field within `tx` should be set to variant ``{ Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } }``. +### Fee Application under ICRC-107 +For every block that charges a fee: + +1. Execute the **pre-fee state transition** (per ICRC-3 evaluation model). +2. Compute the **effective fee** (as defined in ICRC-3). +3. Debit the **effective fee** from the block’s **fee payer**. +4. Determine how the effective fee is handled: + + - **If at least one `107feecol` block exists:** + • If the most recent `107feecol` has `tx.icrc107_fee_col = []` → burn the effective fee. + • If the most recent `107feecol` has `tx.icrc107_fee_col = ` → credit the effective fee to that account. + + - **If no `107feecol` block exists yet (legacy mode):** + • Apply the legacy rules described in the *Legacy Fee Collection* section. + • In brief: if `fee_col` is unset, burn all fees; if `fee_col` is set, only transfer fees are credited to that account, and all other fees are burned. + +This ensures that fee handling is always well-defined, both before and after the introduction of ICRC-107. + +--- + +**Notes** + +- Invariants from ICRC-3 still apply (e.g., `effective fee ≤ amt` for mints; sufficient balance for `amt + effective fee`; allowance reductions include `amt + effective fee` where applicable). +- ICRC-107 does **not** define who the fee payer is — that comes from the semantics of each block type (e.g., ICRC-1/2 legacy rules in ICRC-3). -A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): -``` -variant { Map = vec { - // Block type: indicates this is a fee collector configuration block - record { "btype"; variant { Text = "107feecol" }}; - // The user's intended transaction, from which the hash is computed - record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema - record { "icrc107_fee_col"; variant { Array = vec { - // Empty array to signify burning fees - }}}; - record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal - }} }; - // Timestamp: indicates when the block was created - 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 "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" - }}; -}}; -``` # ICRC-107: Methods for Setting and Getting Fee Collector Information @@ -170,17 +181,39 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: empty; Err: S ``` -This method MUST only be callable by a controller of the ledger or some other authorized principals. Unauthorized calls SHOULD result in an error. +This method MUST only be callable by a controller of the ledger or other authorized principals. Unauthorized calls SHOULD result in an error. -- The ledger MUST construct the tx field (an ICRC-3 Value of type Map as described in the previous section) from this request, place it in the generated `107feecol` block and append the block to the ledger. -- If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. -- If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. +- The ledger MUST construct the `tx` field (an ICRC-3 Value of type Map) from this request, place it in the generated `107feecol` block, and append the block to the ledger. +- If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. +- If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. The `icrc107_set_fee_collector` method MUST return an error in the following cases: -- The caller is not authorized to modify fee collector. -- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount). -- The transaction is a duplicate (as determined by its tx hash when deduplication is enabled), resulting in a `SetFeeCollectorError::Duplicate`. +- The caller is not authorized to modify fee collector. +- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount). +- The transaction is a duplicate (as determined by its tx hash when deduplication is enabled), resulting in a `SetFeeCollectorError::Duplicate`. + + +### Canonical `tx` Mapping + +A successful call to `icrc107_set_fee_collector` MUST produce a `107feecol` block whose `tx` field is derived deterministically from the method arguments and the caller. The mapping is as follows: + +| Field | Type (ICRC-3 `Value`) | Source | +|-------------------|------------------------|--------| +| `op` | `Text` | Constant `"107_set_fee_col"`. | +| `icrc107_fee_col` | `Array` (Account/Empty)| From the `icrc107_fee_col` argument. If `null`, map to `Array = []`. | +| `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch, MUST fit in `nat64`). | +| `caller` | `Blob` | Principal of the caller. | + + + +#### System-Generated `107feecol` Blocks + +For blocks created by the ledger itself (e.g., during an upgrade): + +- MUST include `op` and `icrc107_fee_col`. +- MAY omit `created_at_time` and `caller`. +- If `caller` is included, it SHOULD be the ledger canister principal. ### `icrc107_get_fee_collector` @@ -199,7 +232,73 @@ This method should return the currently active fee collector account: This method strictly returns the last explicitly set value of `icrc107_fee_col`. It does not infer defaults, and if no fee collector was ever set, it returns `opt Account = null`. ---- + + + + + + +#### Example Block + +Below is an example of a valid **fee collector configuration block** that sets the fee collector to a specific account: + +``` +variant { Map = vec { + // Block type: indicates this is a fee collector configuration block + record { "btype"; variant { Text = "107feecol" }}; + // The user's intended transaction + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema + record { "icrc107_fee_col"; variant { Array = vec { + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount + }} }; + record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat } }; // Timestamp for deduplication (June 27, 2025, 15:28:47 UTC) + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal + }} }; + // Timestamp: indicates when the block was created + 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" + }}; +}}; +``` +If one wanted to set the fee collector to be the default account of principal +identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the +`"icrc107_fee_col"` field within `tx` should be set to: + +variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } } + + + + +A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): + +``` +variant { Map = vec { + // Block type: indicates this is a fee collector configuration block + record { "btype"; variant { Text = "107feecol" }}; + // The user's intended transaction, from which the hash is computed + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema + record { "icrc107_fee_col"; variant { Array = vec { + // Empty array to signify burning fees + }}}; + record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal + }} }; + // Timestamp: indicates when the block was created + 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 "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" + }}; +}}; +``` + ## Reporting Compliance From fcd51d3a24b5cd10d237634181b734e2dd9e25f3 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 29 Sep 2025 11:26:16 +0200 Subject: [PATCH 32/45] clarified the sematnics and aligned wiht ICRC-3 --- ICRCs/ICRC-107/ICRC-107.md | 87 +++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 30100a62..dae3ed3c 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -23,35 +23,39 @@ ICRC-107 applies to all ICRC-based ledgers ensuring consistent fee collection be ## Overview -ICRC-107 establishes an on-chain fee collection mechanism that ensures clarity and interoperability across different ICRC-based ledgers. -It extends the ICRC-3 block model with a global fee collection configuration that determines whether fees are **credited to a designated account** or **burned**, and records all changes on-chain using a new block type (`107feecol`). +ICRC-107 introduces a global on-chain fee collection setting recorded in the ledger via a new block type `btype = 107feecol`. This setting determines whether the effective fee of fee-charging blocks is credited to a designated account or burned. All changes are recorded on-chain for transparent, interoperable processing by wallets and explorers. Specifically, ICRC-107 defines: - A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. -- A new block type for setting `icrc107_fee_col` to designate the fee collector. +- A new block type (`btype = 107feecol`) for setting `icrc107_fee_col` to designate the fee collector. - A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. By embedding fee collection settings entirely on-chain, ICRC-107 ensures **transparency**, **interoperability**, and **simplified integration** for wallets, explorers, and other tools. -### Effective Fee Application +### Effective Fee Application -- **Effective fee:** For any block that charges a fee, the ledger computes the **effective fee** according to ICRC-3. -- **Fee payer:** The block type (e.g., ICRC-1 transfer, ICRC-2 approve) defines which account pays the effective fee. -- **Fee collector:** - - If the current configuration specifies an account, that account is credited with the effective fee. - - If the configuration is empty (`[]`), the effective fee is burned. +For any block that charges a fee, handle the fee using this **checklist in order**: -The global fee collector configuration is changed by appending a `107feecol` block: -- `tx.icrc107_fee_col = []` → all subsequent fees are burned. -- `tx.icrc107_fee_col = ` → all subsequent fees are credited to that account. +1) **Find the current 107feecol setting for this block** + Look **backwards** from this block’s index to the most recent `107feecol` block (if any). + - If you find one with `tx.icrc107_fee_col = `, the current setting is **Account**. + - If you find one with `tx.icrc107_fee_col = []`, the current setting is **Burn**. + - If you find **none**, there is **no 107feecol setting** for this block. -This configuration applies to **all subsequent blocks** until replaced by another `107feecol`. +2) **Apply the setting** + - **Account found:** credit the **effective fee** to that account. + - **Burn found (`[]`):** burn the effective fee. + - **No 107feecol setting found:** apply **legacy `fee_col` logic** (Section 5). + - If legacy config designates a collector, credit that account. + - If no legacy collector is configured, burn the fee. -**Legacy compatibility:** -- Until the first `107feecol` block appears, ledgers follow the legacy `fee_col` mechanism (as described in the *Legacy Fee Collection* section). -- Once a `107feecol` block is present, it overrides all legacy fee collection logic. +> **Important:** “No 107feecol setting found” is **not** the same as `[]`. +> `[]` means “we explicitly burn from now on.” +> “No setting found” means “use legacy rules.” +3) **Non-retroactivity** + A `107feecol` block affects only blocks at its index **and after**. Earlier blocks keep whatever applied at their own indices (legacy or prior `107feecol`). ## Common Elements @@ -104,8 +108,7 @@ The minimal fields required to interpret a `107feecol` block: | Field | Type (ICRC-3 `Value`) | Required | Description | |-------------------|------------------------|----------|-------------| -| `op` | `Text` | Yes | MUST be `"107_set_fee_col"`. | -| `icrc107_fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | + | `icrc107_fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | ## Semantics of `107feecol` Blocks @@ -177,43 +180,51 @@ type SetFeeCollectorError = variant { GenericError : record { error_code : nat; message : text }; }; -icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: empty; Err: SetFeeCollectorError }); +icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: SetFeeCollectorError }); ``` +### `icrc107_set_fee_collector` — Semantics (clarified link to `tx`) + This method MUST only be callable by a controller of the ledger or other authorized principals. Unauthorized calls SHOULD result in an error. -- The ledger MUST construct the `tx` field (an ICRC-3 Value of type Map) from this request, place it in the generated `107feecol` block, and append the block to the ledger. -- If `icrc107_fee_col` is set to an account, all subsequent fees are collected by that account. -- If `icrc107_fee_col` is not provided (or set to `null`), all subsequent fees are burned. +- On success, the ledger MUST append a new block with `btype = "107feecol"`. + **The block’s `tx` is a top-level field of this `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** below (same keys, types, and encoding). The ledger MUST embed this `tx` map into the newly appended block. +- The value in `tx.icrc107_fee_col` becomes the active fee-collection setting **from that block’s index onward** (non-retroactive). + - If `icrc107_fee_col` is provided (`Account`), all subsequent effective fees are **credited** to that account until changed by a later `107feecol`. + - If `icrc107_fee_col` is not provided (`null`), it MUST be encoded as `[]` in `tx`; all subsequent effective fees are **burned** until changed by a later `107feecol`. +- If multiple `107feecol` blocks exist, the **last one at or before** a block’s index determines the setting for that block. +- The ledger MUST perform deduplication (e.g., using `created_at_time` and implementation-defined inputs). On duplicate detection, it MUST NOT append a new block and MUST return `Err(Duplicate { duplicate_of = })`. The `icrc107_set_fee_collector` method MUST return an error in the following cases: -- The caller is not authorized to modify fee collector. -- The provided `Account` is invalid (e.g., the minting account on ledgers, anonymous principal, malformed principal or subaccount). -- The transaction is a duplicate (as determined by its tx hash when deduplication is enabled), resulting in a `SetFeeCollectorError::Duplicate`. - +- The caller is not authorized to modify the fee collector (`AccessDenied`). +- The provided `Account` is invalid (e.g., minting account, anonymous principal, malformed principal or subaccount) (`InvalidAccount`). +- A semantically identical transaction was already accepted (per deduplication rules), resulting in `SetFeeCollectorError::Duplicate` (with `duplicate_of`). +- Any other failure that prevents appending a valid `107feecol` block (`GenericError`). -### Canonical `tx` Mapping +--- -A successful call to `icrc107_set_fee_collector` MUST produce a `107feecol` block whose `tx` field is derived deterministically from the method arguments and the caller. The mapping is as follows: +### Canonical `tx` Mapping (normative and referenced above) -| Field | Type (ICRC-3 `Value`) | Source | -|-------------------|------------------------|--------| -| `op` | `Text` | Constant `"107_set_fee_col"`. | -| `icrc107_fee_col` | `Array` (Account/Empty)| From the `icrc107_fee_col` argument. If `null`, map to `Array = []`. | -| `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch, MUST fit in `nat64`). | -| `caller` | `Blob` | Principal of the caller. | +**Scope:** This mapping defines the **exact** content and encoding of the **top-level** `tx` field that MUST be embedded in every `107feecol` block created by `icrc107_set_fee_collector` (or by the ledger itself in the system-generated case). +| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | +|-------------------|-------------------------|-----------------------------------------------------------------------------------------| +| `icrc107_fee_col` | `Array` (Account/Empty) | From the `icrc107_fee_col` argument. If `null`, **encode as** `Array = []`. If present, encode the Account. | +| `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch; **MUST** fit in `nat64`). | +| `caller` | `Blob` | Principal of the caller. | +> **Conformance note:** Any `107feecol` block produced by this method **MUST** carry a top-level `tx` whose map entries **exactly match** the table above (no additional required keys; optional keys, if any, MUST follow this spec). Consumers MUST interpret fee behavior from this `tx` content. #### System-Generated `107feecol` Blocks -For blocks created by the ledger itself (e.g., during an upgrade): +For blocks created by the ledger itself (e.g., during an upgrade/migration): + +- MUST include top-level `tx.icrc107_fee_col` (encoded as above). +- MAY omit `tx.created_at_time` and `tx.caller`. +- If `tx.caller` is included, it SHOULD be the ledger canister principal. -- MUST include `op` and `icrc107_fee_col`. -- MAY omit `created_at_time` and `caller`. -- If `caller` is included, it SHOULD be the ledger canister principal. ### `icrc107_get_fee_collector` From 589b4bdf502d488aa8b9880e49b3b990f8ed861a Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 29 Sep 2025 11:38:31 +0200 Subject: [PATCH 33/45] addressed comments from revies & added clarifications --- ICRCs/ICRC-107/ICRC-107.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index dae3ed3c..6225cc6e 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -368,3 +368,4 @@ To determine who collects the fee in a block: - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - If `fee_col_block` is specified use the `fee_col` from the referenced block index. + - If netiher `fee_col` nor `fee_col_block` are specified, then the fee is burned. From 34ef399bda34a2951104382958ca4fb39efa10d4 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 29 Sep 2025 14:30:34 +0200 Subject: [PATCH 34/45] restructured the semantics section for icrc107_set_fee_collector --- ICRCs/ICRC-107/ICRC-107.md | 54 ++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 6225cc6e..c391fc00 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -46,7 +46,7 @@ For any block that charges a fee, handle the fee using this **checklist in order 2) **Apply the setting** - **Account found:** credit the **effective fee** to that account. - **Burn found (`[]`):** burn the effective fee. - - **No 107feecol setting found:** apply **legacy `fee_col` logic** (Section 5). + - **No 107feecol setting found:** apply **legacy `fee_col` logic (see Legacy Fee Collection Mechanism)**. - If legacy config designates a collector, credit that account. - If no legacy collector is configured, burn the fee. @@ -122,6 +122,8 @@ A `107feecol` block updates the ledger’s global fee collector configuration: This configuration applies **to all subsequent blocks** until replaced by another `107feecol`. +> Note: `tx.icrc107_fee_col = []` (explicit burn) is not the same as the absence of any `107feecol` block (legacy applies).” + --- ### Fee Application under ICRC-107 @@ -184,30 +186,45 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set ``` -### `icrc107_set_fee_collector` — Semantics (clarified link to `tx`) +### `icrc107_set_fee_collector` — Semantics + +- **Authorization** + The method **MUST** be callable only by a **controller** of the ledger or other explicitly authorized principals. Unauthorized calls **MUST** fail with `AccessDenied`. + +- **Effect (on success, non-retroactive)** + The ledger **MUST** append a new block with `btype = "107feecol"`. + **The block’s `tx` is a top-level field of that `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** (same keys, types, and value encodings) and embedded in the block. + The value in `tx.icrc107_fee_col` becomes the active fee-collection setting **from that block’s index onward**; earlier blocks **MUST NOT** be affected. + + - If `icrc107_fee_col` is **provided** (`Account`), all effective fees for this and later blocks **MUST** be **credited** to that account, until changed by a later `107feecol`. + - If `icrc107_fee_col` is **absent** (`null`), it **MUST** be encoded as an empty array `[]` in `tx`; all effective fees for this and later blocks **MUST** be **burned**, until changed by a later `107feecol`. -This method MUST only be callable by a controller of the ledger or other authorized principals. Unauthorized calls SHOULD result in an error. +- **Return value** + On success, the method **MUST** return `Ok(index : nat)`, where `index` is the block index of the newly appended `107feecol` block. The new configuration **MUST** apply starting **at** `index` (non-retroactive). -- On success, the ledger MUST append a new block with `btype = "107feecol"`. - **The block’s `tx` is a top-level field of this `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** below (same keys, types, and encoding). The ledger MUST embed this `tx` map into the newly appended block. -- The value in `tx.icrc107_fee_col` becomes the active fee-collection setting **from that block’s index onward** (non-retroactive). - - If `icrc107_fee_col` is provided (`Account`), all subsequent effective fees are **credited** to that account until changed by a later `107feecol`. - - If `icrc107_fee_col` is not provided (`null`), it MUST be encoded as `[]` in `tx`; all subsequent effective fees are **burned** until changed by a later `107feecol`. -- If multiple `107feecol` blocks exist, the **last one at or before** a block’s index determines the setting for that block. -- The ledger MUST perform deduplication (e.g., using `created_at_time` and implementation-defined inputs). On duplicate detection, it MUST NOT append a new block and MUST return `Err(Duplicate { duplicate_of = })`. +- **Multiple updates over time** + If multiple `107feecol` blocks exist, the setting that applies to any block **MUST** be the **last** `107feecol` **at or before** that block’s index. -The `icrc107_set_fee_collector` method MUST return an error in the following cases: +- **Deduplication & idempotency** + The ledger **MUST** perform deduplication (e.g., using `created_at_time` and any implementation-defined inputs). + If a duplicate is detected, the ledger **MUST NOT** append a new block and **MUST** return `Err(Duplicate { duplicate_of = })`. + +- **Error cases (normative)** + The method **MUST** return an error in these cases: + - **AccessDenied** — caller is not authorized to modify the fee collector. + - **InvalidAccount** — provided collector account is invalid (e.g., minting account, anonymous principal, malformed principal/subaccount). + - **Duplicate** — a semantically identical transaction was already accepted (per the ledger’s deduplication rules); response **MUST** include `duplicate_of` with the existing block index. + - **GenericError** — any other failure that prevents constructing or appending a valid `107feecol` block. + +- **Clarifications** + `tx.icrc107_fee_col = []` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). -- The caller is not authorized to modify the fee collector (`AccessDenied`). -- The provided `Account` is invalid (e.g., minting account, anonymous principal, malformed principal or subaccount) (`InvalidAccount`). -- A semantically identical transaction was already accepted (per deduplication rules), resulting in `SetFeeCollectorError::Duplicate` (with `duplicate_of`). -- Any other failure that prevents appending a valid `107feecol` block (`GenericError`). --- ### Canonical `tx` Mapping (normative and referenced above) -**Scope:** This mapping defines the **exact** content and encoding of the **top-level** `tx` field that MUST be embedded in every `107feecol` block created by `icrc107_set_fee_collector` (or by the ledger itself in the system-generated case). +**Scope:** This mapping defines the **exact** content of the **top-level** `tx` field that MUST be embedded in every `107feecol` block created by `icrc107_set_fee_collector` (or by the ledger itself in the system-generated case). | Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | |-------------------|-------------------------|-----------------------------------------------------------------------------------------| @@ -215,7 +232,10 @@ The `icrc107_set_fee_collector` method MUST return an error in the following cas | `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch; **MUST** fit in `nat64`). | | `caller` | `Blob` | Principal of the caller. | -> **Conformance note:** Any `107feecol` block produced by this method **MUST** carry a top-level `tx` whose map entries **exactly match** the table above (no additional required keys; optional keys, if any, MUST follow this spec). Consumers MUST interpret fee behavior from this `tx` content. +> **Conformance note:** Hashing uses **representation-independent hashing (RIH)**, so **field order and concrete map encoding do not affect the hash**. What **does** matter is: +> - The **presence** (or absence) of the fields listed above. +> - The **exact values** of those fields, encoded per ICRC-3 `Value`. + #### System-Generated `107feecol` Blocks From 463fdbf5737327c3029da2c2430d0ad91cfd0a14 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 29 Sep 2025 15:08:52 +0200 Subject: [PATCH 35/45] updated examples --- ICRCs/ICRC-107/ICRC-107.md | 93 ++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index c391fc00..d4bf8ecf 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -23,7 +23,8 @@ ICRC-107 applies to all ICRC-based ledgers ensuring consistent fee collection be ## Overview -ICRC-107 introduces a global on-chain fee collection setting recorded in the ledger via a new block type `btype = 107feecol`. This setting determines whether the effective fee of fee-charging blocks is credited to a designated account or burned. All changes are recorded on-chain for transparent, interoperable processing by wallets and explorers. +ICRC-107 introduces a global on-chain fee collection setting recorded in the ledger via a new block type `btype = "107feecol"`. This setting determines whether the effective fee of fee-charging blocks is credited to a designated account or burned. All changes are recorded on-chain for transparent, interoperable processing by wallets and explorers. + Specifically, ICRC-107 defines: @@ -228,6 +229,7 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set | Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | |-------------------|-------------------------|-----------------------------------------------------------------------------------------| +| `op` | `Text` | **Constant** `"icrc107_set_fee_collector"`. | | `icrc107_fee_col` | `Array` (Account/Empty) | From the `icrc107_fee_col` argument. If `null`, **encode as** `Array = []`. If present, encode the Account. | | `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch; **MUST** fit in `nat64`). | | `caller` | `Blob` | Principal of the caller. | @@ -269,65 +271,61 @@ This method strictly returns the last explicitly set value of `icrc107_fee_col`. -#### Example Block +#### Example: set fee collector to a specific account (produced by `icrc107_set_fee_collector`) Below is an example of a valid **fee collector configuration block** that sets the fee collector to a specific account: ``` variant { Map = vec { - // Block type: indicates this is a fee collector configuration block - record { "btype"; variant { Text = "107feecol" }}; - // The user's intended transaction - record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema - record { "icrc107_fee_col"; variant { Array = vec { - variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"}; // 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" }; // Subaccount - }} }; - record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat } }; // Timestamp for deduplication (June 27, 2025, 15:28:47 UTC) - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal - }} }; - // Timestamp: indicates when the block was created - 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" - }}; -}}; + // Block type + record { "btype"; variant { Text = "107feecol" }}; + + // Top-level tx (constructed per Canonical `tx` Mapping) + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "icrc107_set_fee_collector" }}; + record { "icrc107_fee_col"; variant { Array = vec { + // Account = [owner, subaccount?] + variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // owner principal bytes + 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" } // 32-byte subaccount + }}}; + record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat }}; // ns since Unix epoch + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; // caller principal bytes + }}}; + + // Standard block metadata + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; + 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" }}; +}} ``` If one wanted to set the fee collector to be the default account of principal identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the `"icrc107_fee_col"` field within `tx` should be set to: -variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } } +`variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } }` +#### Example: explicitly burn all fees from this point onward (produced by `icrc107_set_fee_collector`) A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): ``` variant { Map = vec { - // Block type: indicates this is a fee collector configuration block - record { "btype"; variant { Text = "107feecol" }}; - // The user's intended transaction, from which the hash is computed - record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "107_set_fee_col" } }; // Operation name, as per schema - record { "icrc107_fee_col"; variant { Array = vec { - // Empty array to signify burning fees - }}}; - record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat } }; // Timestamp for deduplication - record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" } }; // Caller principal - }} }; - // Timestamp: indicates when the block was created - 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 "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" - }}; -}}; + // Block type + record { "btype"; variant { Text = "107feecol" }}; + + // Top-level tx (constructed per Canonical `tx` Mapping) + record { "tx"; variant { Map = vec { + record { "op"; variant { Text = "icrc107_set_fee_collector" }}; + record { "icrc107_fee_col"; variant { Array = vec { }}}; // [] means "burn from now on" + record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat }}; + record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; + }}}; + + // Standard block metadata + record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; + record { "phash"; variant { Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" }}; +}} ``` @@ -361,15 +359,12 @@ variant { Vec = vec { ## Legacy Fee Collection Mechanism -The Dfinity maintained ICRC ledgers include a fee collection mechanism which, for completeness is described below. - -### Legacy Behavior (`fee_col`) - +The **DFINITY-maintained** ICRC ledgers include a fee collection mechanism which, for completeness, is described below. -Until the first block of type `107feecol` the ledger follows the following legacy behavior. +Until the first block of type `107feecol`, the ledger follows the following legacy behavior. -- If `fee_col`is not set, all fees are burned. -- If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) were always burned in legacy behavior as implemented in Dfinity-maintained ICRC-3 ledgers. +- If `fee_col` is not set, all fees are burned. +- If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) are burned in DFINITY-maintained ICRC-3 ledgers. New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. From b87d0dbc0a837dcdcb38ea28e93343e1e4ed158b Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 29 Sep 2025 15:22:01 +0200 Subject: [PATCH 36/45] minor typos --- ICRCs/ICRC-107/ICRC-107.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index d4bf8ecf..ec78e8db 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -29,7 +29,7 @@ ICRC-107 introduces a global on-chain fee collection setting recorded in the led Specifically, ICRC-107 defines: - A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. -- A new block type (`btype = 107feecol`) for setting `icrc107_fee_col` to designate the fee collector. +- A new block type (`btype = "107feecol"`) for setting `icrc107_fee_col` to designate the fee collector. - A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. By embedding fee collection settings entirely on-chain, ICRC-107 ensures **transparency**, **interoperability**, and **simplified integration** for wallets, explorers, and other tools. @@ -123,7 +123,7 @@ A `107feecol` block updates the ledger’s global fee collector configuration: This configuration applies **to all subsequent blocks** until replaced by another `107feecol`. -> Note: `tx.icrc107_fee_col = []` (explicit burn) is not the same as the absence of any `107feecol` block (legacy applies).” +> Note: `tx.icrc107_fee_col = []` (explicit burn) is not the same as the absence of any `107feecol` block (legacy applies). --- @@ -383,4 +383,4 @@ To determine who collects the fee in a block: - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - If `fee_col_block` is specified use the `fee_col` from the referenced block index. - - If netiher `fee_col` nor `fee_col_block` are specified, then the fee is burned. + - If neither `fee_col` nor `fee_col_block` are specified, then the fee is burned. From 968e4f1910a7a8c0c8bcedb237194ff34dad6adc Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 1 Oct 2025 17:38:26 +0200 Subject: [PATCH 37/45] icrc107_set_fee_col --> 107set_fee_col in blocks and icrc107_fee_col --> fee_collector --- ICRCs/ICRC-107/ICRC-107.md | 69 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index ec78e8db..2e3d5fbb 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -28,9 +28,9 @@ ICRC-107 introduces a global on-chain fee collection setting recorded in the led Specifically, ICRC-107 defines: -- A fee collection configuration that specifies the collector account (`icrc107_fee_col`), applying to all subsequent blocks. -- A new block type (`btype = "107feecol"`) for setting `icrc107_fee_col` to designate the fee collector. -- A backward-compatible mechanism where, until `icrc107_fee_col` is set, legacy `fee_col` logic applies. +- A fee collection configuration that specifies the collector account (`fee_collector`), applying to all subsequent blocks. +- A new block type (`btype = "107feecol"`) for setting `fee_collector` to designate the fee collector. +- A backward-compatible mechanism where, until the new mechanism is employed, the legacy collection logic applies. By embedding fee collection settings entirely on-chain, ICRC-107 ensures **transparency**, **interoperability**, and **simplified integration** for wallets, explorers, and other tools. @@ -38,11 +38,11 @@ By embedding fee collection settings entirely on-chain, ICRC-107 ensures **trans For any block that charges a fee, handle the fee using this **checklist in order**: -1) **Find the current 107feecol setting for this block** - Look **backwards** from this block’s index to the most recent `107feecol` block (if any). - - If you find one with `tx.icrc107_fee_col = `, the current setting is **Account**. - - If you find one with `tx.icrc107_fee_col = []`, the current setting is **Burn**. - - If you find **none**, there is **no 107feecol setting** for this block. +1) **Find the current `107feecol` setting for this block** + Look **backwards** from this block’s index to the most recent block with `btype = "107feecol"`. + - If you find one with `tx.fee_col = `, the current setting is **Account**. + - If you find one with `tx.fee_col = []`, the current setting is **Burn**. + - If you find **none**, then legacy collection logic applies. 2) **Apply the setting** - **Account found:** credit the **effective fee** to that account. @@ -51,7 +51,7 @@ For any block that charges a fee, handle the fee using this **checklist in order - If legacy config designates a collector, credit that account. - If no legacy collector is configured, burn the fee. -> **Important:** “No 107feecol setting found” is **not** the same as `[]`. +> **Important:** “No block with `btype = "107feecol"` is found is **not** the same as finding one with `tx.feecol = []`. > `[]` means “we explicitly burn from now on.” > “No setting found” means “use legacy rules.” @@ -109,7 +109,7 @@ The minimal fields required to interpret a `107feecol` block: | Field | Type (ICRC-3 `Value`) | Required | Description | |-------------------|------------------------|----------|-------------| - | `icrc107_fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | + | `fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | ## Semantics of `107feecol` Blocks @@ -118,12 +118,11 @@ The minimal fields required to interpret a `107feecol` block: A `107feecol` block updates the ledger’s global fee collector configuration: -- If `tx.icrc107_fee_col = []` → all subsequent fees are **burned**. -- If `tx.icrc107_fee_col` encodes an account → all subsequent fees are **credited to that account**. +- If `tx.107fee_col = []` → all subsequent fees are **burned**. +- If `tx.107fee_col` encodes an account → all subsequent fees are **credited to that account**. This configuration applies **to all subsequent blocks** until replaced by another `107feecol`. -> Note: `tx.icrc107_fee_col = []` (explicit burn) is not the same as the absence of any `107feecol` block (legacy applies). --- @@ -137,8 +136,8 @@ For every block that charges a fee: 4. Determine how the effective fee is handled: - **If at least one `107feecol` block exists:** - • If the most recent `107feecol` has `tx.icrc107_fee_col = []` → burn the effective fee. - • If the most recent `107feecol` has `tx.icrc107_fee_col = ` → credit the effective fee to that account. + • If the most recent `107feecol` has `tx.fee_collector = []` → burn the effective fee. + • If the most recent `107feecol` has `tx.fee_collector = ` → credit the effective fee to that account. - **If no `107feecol` block exists yet (legacy mode):** • Apply the legacy rules described in the *Legacy Fee Collection* section. @@ -162,7 +161,7 @@ This ensures that fee handling is always well-defined, both before and after the ### `icrc107_set_fee_collector` -This method allows a ledger controller to update the fee collector. It modifies the `icrc107_fee_col` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. +This method allows a ledger controller to update the fee collector. It modifies the `fee_collector` account, which determines where collected fees are sent. The updated settings are recorded in a new block (of type `107feecol`) added to the ledger. ``` @@ -172,7 +171,7 @@ type Account = record { }; type SetFeeCollectorArgs = record { - icrc107_fee_col: opt Account; + fee_collector: opt Account; created_at_time: nat64; }; @@ -195,10 +194,10 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set - **Effect (on success, non-retroactive)** The ledger **MUST** append a new block with `btype = "107feecol"`. **The block’s `tx` is a top-level field of that `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** (same keys, types, and value encodings) and embedded in the block. - The value in `tx.icrc107_fee_col` becomes the active fee-collection setting **from that block’s index onward**; earlier blocks **MUST NOT** be affected. + The value in `tx.fee_collector` becomes the active fee-collection setting **from that block’s index onward**; earlier blocks **MUST NOT** be affected. - - If `icrc107_fee_col` is **provided** (`Account`), all effective fees for this and later blocks **MUST** be **credited** to that account, until changed by a later `107feecol`. - - If `icrc107_fee_col` is **absent** (`null`), it **MUST** be encoded as an empty array `[]` in `tx`; all effective fees for this and later blocks **MUST** be **burned**, until changed by a later `107feecol`. + - If `fee_collector` is **provided** (`Account`), all effective fees for this and later blocks **MUST** be **credited** to that account, until changed by a later `107feecol`. + - If `fee_collector` is **absent** (`null`), it **MUST** be encoded as an empty array `[]` in `tx`; all effective fees for this and later blocks **MUST** be **burned**, until changed by a later `107feecol`. - **Return value** On success, the method **MUST** return `Ok(index : nat)`, where `index` is the block index of the newly appended `107feecol` block. The new configuration **MUST** apply starting **at** `index` (non-retroactive). @@ -218,7 +217,7 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set - **GenericError** — any other failure that prevents constructing or appending a valid `107feecol` block. - **Clarifications** - `tx.icrc107_fee_col = []` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). + `tx.fee_collector = []` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). --- @@ -229,8 +228,8 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set | Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | |-------------------|-------------------------|-----------------------------------------------------------------------------------------| -| `op` | `Text` | **Constant** `"icrc107_set_fee_collector"`. | -| `icrc107_fee_col` | `Array` (Account/Empty) | From the `icrc107_fee_col` argument. If `null`, **encode as** `Array = []`. If present, encode the Account. | +| `op` | `Text` | **Constant** `"107set_fee_collector"`. | +| `fee_collector` | `Array` (Account/Empty) | From the `fee_collector` argument. If `null`, **encode as** `Array = []`. If present, encode the Account. | | `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch; **MUST** fit in `nat64`). | | `caller` | `Blob` | Principal of the caller. | @@ -243,7 +242,7 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set For blocks created by the ledger itself (e.g., during an upgrade/migration): -- MUST include top-level `tx.icrc107_fee_col` (encoded as above). +- MUST include top-level `tx.fee_collector` (encoded as above). - MAY omit `tx.created_at_time` and `tx.caller`. - If `tx.caller` is included, it SHOULD be the ledger canister principal. @@ -260,10 +259,10 @@ icrc107_get_fee_collector: () -> (variant { Ok: opt Account; Err: record { error This method should return the currently active fee collector account: - - If the response is `null`, fees are burned. This corresponds to `icrc107_fee_col = variant { Array = vec {}}` on-chain. - - If the response is a valid `Account`, fees are collected by that account. This corresponds to `icrc107_fee_col` being set to the ICRC-3 representation of the account on-chain. + - If the response is `null`, fees are burned. This corresponds to `fee_collector = variant { Array = vec {}}` on-chain. + - If the response is a valid `Account`, fees are collected by that account. This corresponds to `fee_collector` being set to the ICRC-3 representation of the account on-chain. -This method strictly returns the last explicitly set value of `icrc107_fee_col`. It does not infer defaults, and if no fee collector was ever set, it returns `opt Account = null`. +This method strictly returns the last explicitly set value of `fee_collector`. It does not infer defaults, and if no fee collector was ever set, it returns `opt Account = null`. @@ -282,8 +281,8 @@ variant { Map = vec { // Top-level tx (constructed per Canonical `tx` Mapping) record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "icrc107_set_fee_collector" }}; - record { "icrc107_fee_col"; variant { Array = vec { + record { "op"; variant { Text = "107set_fee_collector" }}; + record { "fee_collector"; variant { Array = vec { // Account = [owner, subaccount?] variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // owner principal bytes 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" } // 32-byte subaccount @@ -299,7 +298,7 @@ variant { Map = vec { ``` If one wanted to set the fee collector to be the default account of principal identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the -`"icrc107_fee_col"` field within `tx` should be set to: +`"fee_collector"` field within `tx` should be set to: `variant { Array = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01"} } }` @@ -316,8 +315,8 @@ variant { Map = vec { // Top-level tx (constructed per Canonical `tx` Mapping) record { "tx"; variant { Map = vec { - record { "op"; variant { Text = "icrc107_set_fee_collector" }}; - record { "icrc107_fee_col"; variant { Array = vec { }}}; // [] means "burn from now on" + record { "op"; variant { Text = "107set_fee_collector" }}; + record { "fee_collector"; variant { Array = vec { }}}; // [] means "burn from now on" record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat }}; record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; }}}; @@ -367,7 +366,7 @@ Until the first block of type `107feecol`, the ledger follows the following lega - If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) are burned in DFINITY-maintained ICRC-3 ledgers. -New implementations SHOULD avoid using `fee_col` and instead use `icrc107_fee_col` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. +New implementations SHOULD avoid using `fee_col` and instead use `fee_collector` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. ### Handling Fee Collection for ICRC-1 and ICRC-2 Blocks @@ -375,9 +374,9 @@ To determine who collects the fee in a block: 1. Check for fee collector configuration - - If a previous block set `icrc107_fee_col`, the ledger uses the behavior specified by this standard. + - If a previous block set `fee_collector`, the ledger uses the behavior specified by this standard. -2. If no `icrc107_fee_col` setting exists (legacy behavior): +2. If no `fee_collector` setting exists (legacy behavior): - If the block is of type `2approve` then the fee is burned - If the block is a transfer block, i.e. of type `1xfer` or `2xfer`: From 59da7c831f793d3ff1c6c6bf0eb6311eb340457b Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 13 Nov 2025 20:32:50 +0100 Subject: [PATCH 38/45] pass to make burning recorded as no fee collector recorded --- ICRCs/ICRC-107/ICRC-107.md | 147 +++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 62 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 2e3d5fbb..96592c73 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -36,27 +36,36 @@ By embedding fee collection settings entirely on-chain, ICRC-107 ensures **trans ### Effective Fee Application -For any block that charges a fee, handle the fee using this **checklist in order**: +For any block that charges a fee, handle the fee using the following checklist, **in order**: -1) **Find the current `107feecol` setting for this block** - Look **backwards** from this block’s index to the most recent block with `btype = "107feecol"`. - - If you find one with `tx.fee_col = `, the current setting is **Account**. - - If you find one with `tx.fee_col = []`, the current setting is **Burn**. - - If you find **none**, then legacy collection logic applies. +1. **Find the current `107feecol` setting for this block** -2) **Apply the setting** - - **Account found:** credit the **effective fee** to that account. - - **Burn found (`[]`):** burn the effective fee. - - **No 107feecol setting found:** apply **legacy `fee_col` logic (see Legacy Fee Collection Mechanism)**. - - If legacy config designates a collector, credit that account. - - If no legacy collector is configured, burn the fee. + Look **backwards** from this block’s index to the most recent block with `btype = "107feecol"`. -> **Important:** “No block with `btype = "107feecol"` is found is **not** the same as finding one with `tx.feecol = []`. -> `[]` means “we explicitly burn from now on.” -> “No setting found” means “use legacy rules.” + - If you find **none**, then **legacy collection logic applies** (see *Legacy Fee Collection Mechanism*). + - If you find one, inspect its `tx`: + - If `tx` contains a `fee_collector` field, the current setting is **that Account**. + - If `tx` does **not** contain a `fee_collector` field, the current setting is **“burn all fees”**. -3) **Non-retroactivity** - A `107feecol` block affects only blocks at its index **and after**. Earlier blocks keep whatever applied at their own indices (legacy or prior `107feecol`). +2. **Apply the setting** + + - **Account found (`fee_collector` present):** + Credit the **effective fee** to that account. + - **Explicit burn (`fee_collector` absent in the latest `107feecol`):** + Burn the effective fee. + - **No `107feecol` block exists yet:** + Apply **legacy `fee_col` logic (see Legacy Fee Collection Mechanism)**. + In brief, for DFINITY-maintained ledgers: if `fee_col` is unset, burn all fees; if `fee_col` is set, only transfer fees are credited to that account, and all other fees are burned. + +> **Important:** +> - “No block with `btype = "107feecol"` is found” is **not** the same as having a `107feecol` block whose `tx` omits `fee_collector`. +> - **No `107feecol`** → legacy rules. +> - **Latest `107feecol` present, but `fee_collector` field missing in `tx`** → “fees are explicitly burned from now on.” + +3. **Non-retroactivity** + + A `107feecol` block affects only blocks at its index **and after**. + Earlier blocks keep whatever applied at their own indices (legacy or earlier `107feecol` settings). ## Common Elements @@ -87,30 +96,26 @@ This standard follows the conventions set by ICRC-3, inheriting key structural c ## Fee Management - - - - - ### ICRC-107 Transaction and Block Schema Each `107feecol` block consists of the following top-level fields: -| Field | Type (ICRC-3 `Value`) | Required | Description | -|-------------------|------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `btype` | `Text` | Yes | **MUST** be `"107feecol"`. | -| `ts` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) 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 fee collection configuration transaction. See `tx` Field Schema below. | +| Field | Type (ICRC-3 `Value`) | Required | Description | +|--------|------------------------|----------|-----------------------------------------------------------------------------| +| `btype`| `Text` | Yes | **MUST** be `"107feecol"`. | +| `ts` | `Nat` | Yes | Timestamp (in nanoseconds since the Unix epoch) when the block was added. | +| `phash`| `Blob` | Yes | Hash of the parent block. | +| `tx` | `Map(Text, Value)` | Yes | Encodes information about the fee collection configuration transaction. | ### Minimal `tx` Schema The minimal fields required to interpret a `107feecol` block: -| Field | Type (ICRC-3 `Value`) | Required | Description | -|-------------------|------------------------|----------|-------------| - | `fee_col` | `Array` (Account/Empty)| Yes | Target fee collector account, or empty (`[]`) to burn fees. | +| Field | Type (ICRC-3 `Value`) | Required | Description | +|----------------|------------------------|----------|-------------| +| `fee_collector`| Account | No | If present, designates the fee collector account. If **absent**, this `107feecol` block explicitly sets all fees to be **burned** from its index onward. | +(Other fields such as `op`, `created_at_time`, and `caller` are defined in the **Canonical `tx` Mapping**.) ## Semantics of `107feecol` Blocks @@ -118,11 +123,14 @@ The minimal fields required to interpret a `107feecol` block: A `107feecol` block updates the ledger’s global fee collector configuration: -- If `tx.107fee_col = []` → all subsequent fees are **burned**. -- If `tx.107fee_col` encodes an account → all subsequent fees are **credited to that account**. +- If `tx` contains a `fee_collector` field encoding an account → all subsequent fees are **credited to that account**. +- If `tx` does **not** contain a `fee_collector` field → all subsequent fees are **burned**. This configuration applies **to all subsequent blocks** until replaced by another `107feecol`. +> **Note:** +> The presence or absence of the `fee_collector` field in the **latest** `107feecol` block is what determines the behavior. +> The absence of any `107feecol` blocks means “use legacy behavior”. --- @@ -193,14 +201,22 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set - **Effect (on success, non-retroactive)** The ledger **MUST** append a new block with `btype = "107feecol"`. - **The block’s `tx` is a top-level field of that `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** (same keys, types, and value encodings) and embedded in the block. - The value in `tx.fee_collector` becomes the active fee-collection setting **from that block’s index onward**; earlier blocks **MUST NOT** be affected. + **The block’s `tx` is a top-level field of that `107feecol` block** and **MUST** be constructed **exactly** as defined in **Canonical `tx` Mapping** (same keys, types, and value encodings) and embedded in the block. - - If `fee_collector` is **provided** (`Account`), all effective fees for this and later blocks **MUST** be **credited** to that account, until changed by a later `107feecol`. - - If `fee_collector` is **absent** (`null`), it **MUST** be encoded as an empty array `[]` in `tx`; all effective fees for this and later blocks **MUST** be **burned**, until changed by a later `107feecol`. + The `fee_collector` argument determines how `tx` is constructed and how fees are handled from the new block’s index onward: + + - If `fee_collector` is **provided** (`?Account`): + - `tx` **MUST** contain a `fee_collector` field whose value encodes that Account using the ICRC-3 Account representation. + - All effective fees for this and later blocks **MUST** be **credited** to that account, until changed by a later `107feecol`. + + - If `fee_collector` is **absent** (`null`): + - The `tx` map **MUST NOT** contain a `fee_collector` field. + - All effective fees for this and later blocks **MUST** be **burned**, until changed by a later `107feecol`. + + The new configuration **MUST** apply starting **at** the returned block index (non-retroactive). Earlier blocks **MUST NOT** be affected. - **Return value** - On success, the method **MUST** return `Ok(index : nat)`, where `index` is the block index of the newly appended `107feecol` block. The new configuration **MUST** apply starting **at** `index` (non-retroactive). + On success, the method **MUST** return `Ok(index : nat)`, where `index` is the block index of the newly appended `107feecol` block. - **Multiple updates over time** If multiple `107feecol` blocks exist, the setting that applies to any block **MUST** be the **last** `107feecol` **at or before** that block’s index. @@ -217,37 +233,40 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set - **GenericError** — any other failure that prevents constructing or appending a valid `107feecol` block. - **Clarifications** - `tx.fee_collector = []` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). - + A `107feecol` block whose `tx` omits `fee_collector` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). --- +### Canonical `tx` Mapping (normative) -### Canonical `tx` Mapping (normative and referenced above) +The `tx` field of every `107feecol` block **MUST** be constructed exactly according to the following canonical mapping. +All fields marked “Always included” MUST appear in the `tx` map of the block. +The presence of `fee_collector` is the only conditional component. -**Scope:** This mapping defines the **exact** content of the **top-level** `tx` field that MUST be embedded in every `107feecol` block created by `icrc107_set_fee_collector` (or by the ledger itself in the system-generated case). +| Field | Type (ICRC-3 `Value`) | Canonical Encoding Rule | +|-------------------|------------------------|--------------------------| +| `op` | `Text` | Always included. **MUST** be `"107set_fee_collector"`. | +| `fee_collector` | Account | **Included iff** the `fee_collector` argument is `?Account`. +| | | **If the argument is `null`, the `fee_collector` field MUST be omitted** from the `tx` map. | +| `created_at_time` | `Nat` | Always included. Nanoseconds since Unix epoch. **MUST** fit in `nat64`. | +| `caller` | `Blob` | Always included. Principal bytes of the caller. | -| Field | Type (ICRC-3 `Value`) | Source / Encoding Rule | -|-------------------|-------------------------|-----------------------------------------------------------------------------------------| -| `op` | `Text` | **Constant** `"107set_fee_collector"`. | -| `fee_collector` | `Array` (Account/Empty) | From the `fee_collector` argument. If `null`, **encode as** `Array = []`. If present, encode the Account. | -| `created_at_time` | `Nat` | From the `created_at_time` argument (ns since Unix epoch; **MUST** fit in `nat64`). | -| `caller` | `Blob` | Principal of the caller. | +**Notes:** -> **Conformance note:** Hashing uses **representation-independent hashing (RIH)**, so **field order and concrete map encoding do not affect the hash**. What **does** matter is: -> - The **presence** (or absence) of the fields listed above. -> - The **exact values** of those fields, encoded per ICRC-3 `Value`. +- The **absence** of the `fee_collector` field in the `tx` map means: + **“burn all fees from this block onward.”** #### System-Generated `107feecol` Blocks For blocks created by the ledger itself (e.g., during an upgrade/migration): -- MUST include top-level `tx.fee_collector` (encoded as above). -- MAY omit `tx.created_at_time` and `tx.caller`. +- MUST include `tx` with: + - `op` set to `"107set_fee_collector"`; + - `fee_collector` present or absent according to the intended configuration (collect vs burn). +- MAY omit `tx.created_at_time` and `tx.caller`. - If `tx.caller` is included, it SHOULD be the ledger canister principal. - ### `icrc107_get_fee_collector` This method retrieves the currently active fee collector account. Unless changed, these settings apply to the next block added to the ledger. @@ -257,12 +276,15 @@ icrc107_get_fee_collector: () -> (variant { Ok: opt Account; Err: record { error ``` -This method should return the currently active fee collector account: +This method returns the **currently active** fee collector account: - - If the response is `null`, fees are burned. This corresponds to `fee_collector = variant { Array = vec {}}` on-chain. - - If the response is a valid `Account`, fees are collected by that account. This corresponds to `fee_collector` being set to the ICRC-3 representation of the account on-chain. +- If the response is `opt Account = ?account`, fees are collected by that account. This corresponds to the ledger’s current configuration and, if a `107feecol` block exists, the most recent one having a `fee_collector` field in its `tx`. +- If the response is `opt Account = null`, then **no account currently collects fees**: + - If one or more `107feecol` blocks exist, this corresponds to the most recent such block **omitting** the `fee_collector` field in its `tx`, meaning “burn all fees”. + - If no `107feecol` block exists yet, the ledger is still in **legacy mode** and fees are handled as described in the *Legacy Fee Collection Mechanism*. + +This method strictly reflects the ledger’s current internal configuration and does not retroactively interpret past blocks. -This method strictly returns the last explicitly set value of `fee_collector`. It does not infer defaults, and if no fee collector was ever set, it returns `opt Account = null`. @@ -306,9 +328,9 @@ identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the #### Example: explicitly burn all fees from this point onward (produced by `icrc107_set_fee_collector`) -A block that explicitly sets fee burning by removing the fee collector (i.e., all fees are burned from this point onward): +A block that explicitly sets fee burning by **omitting** the fee collector (i.e., all fees are burned from this point onward): -``` +```text variant { Map = vec { // Block type record { "btype"; variant { Text = "107feecol" }}; @@ -316,7 +338,8 @@ variant { Map = vec { // Top-level tx (constructed per Canonical `tx` Mapping) record { "tx"; variant { Map = vec { record { "op"; variant { Text = "107set_fee_collector" }}; - record { "fee_collector"; variant { Array = vec { }}}; // [] means "burn from now on" + // NOTE: there is intentionally NO "fee_collector" entry here. + // Its absence means "burn all fees from this block onward". record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat }}; record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; }}}; @@ -325,7 +348,7 @@ variant { Map = vec { record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; record { "phash"; variant { Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" }}; }} -``` + From 7b68b8b263084b90e984831bb035d29915e144ea Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 14 Nov 2025 16:12:12 +0100 Subject: [PATCH 39/45] alignement with ICRC-3 --- ICRCs/ICRC-107/ICRC-107.md | 76 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 96592c73..e6a7b4b5 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -3,13 +3,12 @@ ## Introduction & Motivation Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. -Some assign a designated fee collector for transfer operations, while others burn fees for approve operations—even when fee collection details appear in approve blocks. +Some assign a designated fee collector for transfer operations, while burning fees for approve operations—even when fee collection details appear in approve blocks. This inconsistency creates challenges: - Fee collection rules are not uniformly defined or visible on-chain. - Wallets and explorers cannot reliably predict where fees go. -- Integration requires off-chain metadata to interpret fee behavior. **ICRC-107** standardizes fee collection across all ICRC-based ledgers by introducing: @@ -17,7 +16,7 @@ This inconsistency creates challenges: - A new block type (`107feecol`) for setting the fee collector on-chain. - A consistent fee application model integrated into the ICRC-3 evaluation model. -This ensures **transparency**, **interoperability**, and **simplified integration**. +This ensures transparency, interoperability, and simplified integration. ICRC-107 applies to all ICRC-based ledgers ensuring consistent fee collection behavior across transfers and approvals. This standard is backward-compatible with existing ICRC-based ledgers, allowing them to transition gradually to the new fee collection mechanism, while documenting legacy fee collection logic. @@ -32,8 +31,6 @@ Specifically, ICRC-107 defines: - A new block type (`btype = "107feecol"`) for setting `fee_collector` to designate the fee collector. - A backward-compatible mechanism where, until the new mechanism is employed, the legacy collection logic applies. -By embedding fee collection settings entirely on-chain, ICRC-107 ensures **transparency**, **interoperability**, and **simplified integration** for wallets, explorers, and other tools. - ### Effective Fee Application For any block that charges a fee, handle the fee using the following checklist, **in order**: @@ -82,15 +79,12 @@ This standard follows the conventions set by ICRC-3, inheriting key structural c - **Principals** Represented as `variant { Blob = }`. -- **Timestamps** - Both `ts` and `tx.created_at_time` are expressed in **nanoseconds since the Unix epoch**. - They are encoded as `Nat` in ICRC-3 `Value`, but MUST fit into a `nat64`. - - `ts` is set by the ledger when the block is created. - - `created_at_time` is provided by the caller for deduplication (per ICRC-1). +- **Block Timestamp** + The block timestamp (`ts`) is expressed in nanoseconds since the Unix epoch and is encoded as `Nat` in ICRC-3 `Value`, but MUST fit into a `nat64`. + - **Parent Hash** - Each block includes `phash : Blob`, the hash of its parent block, - unless it is the genesis block (where `phash` is omitted). + Each block includes `phash : Blob`, the hash of its parent block, unless it is the genesis block (where `phash` is omitted). @@ -115,7 +109,7 @@ The minimal fields required to interpret a `107feecol` block: |----------------|------------------------|----------|-------------| | `fee_collector`| Account | No | If present, designates the fee collector account. If **absent**, this `107feecol` block explicitly sets all fees to be **burned** from its index onward. | -(Other fields such as `op`, `created_at_time`, and `caller` are defined in the **Canonical `tx` Mapping**.) +(Other fields such as `op`, `ts`, and `caller` are defined in the **Canonical `tx` Mapping**.) ## Semantics of `107feecol` Blocks @@ -144,12 +138,12 @@ For every block that charges a fee: 4. Determine how the effective fee is handled: - **If at least one `107feecol` block exists:** - • If the most recent `107feecol` has `tx.fee_collector = []` → burn the effective fee. - • If the most recent `107feecol` has `tx.fee_collector = ` → credit the effective fee to that account. + - If the most recent `107feecol` omits the `fee_collector` field in `tx` → burn the effective fee. + - If the most recent `107feecol` has `tx.fee_collector = ` → credit the effective fee to that account. - **If no `107feecol` block exists yet (legacy mode):** - • Apply the legacy rules described in the *Legacy Fee Collection* section. - • In brief: if `fee_col` is unset, burn all fees; if `fee_col` is set, only transfer fees are credited to that account, and all other fees are burned. + - Apply the legacy rules described in the *Legacy Fee Collection* section. + - In brief: if `fee_col` is unset, burn all fees; if `fee_col` is set, only transfer fees are credited to that account, and all other fees are burned. This ensures that fee handling is always well-defined, both before and after the introduction of ICRC-107. @@ -236,35 +230,43 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set A `107feecol` block whose `tx` omits `fee_collector` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). --- + ### Canonical `tx` Mapping (normative) -The `tx` field of every `107feecol` block **MUST** be constructed exactly according to the following canonical mapping. -All fields marked “Always included” MUST appear in the `tx` map of the block. -The presence of `fee_collector` is the only conditional component. +This mapping defines the exact `tx` structure that **MUST** appear in every +`107feecol` block produced by a successful invocation of that method. + +System-generated `107feecol` blocks (e.g., created by the ledger during an upgrade or +migration) **do not** need to include all the fields that appear in this mapping. +They MUST follow the structural rules in Section *System-Generated `107feecol` Blocks*. + +For all user-initiated calls, the `tx` map **MUST** contain exactly the following fields, +with the specified encoding: | Field | Type (ICRC-3 `Value`) | Canonical Encoding Rule | |-------------------|------------------------|--------------------------| | `op` | `Text` | Always included. **MUST** be `"107set_fee_collector"`. | -| `fee_collector` | Account | **Included iff** the `fee_collector` argument is `?Account`. -| | | **If the argument is `null`, the `fee_collector` field MUST be omitted** from the `tx` map. | -| `created_at_time` | `Nat` | Always included. Nanoseconds since Unix epoch. **MUST** fit in `nat64`. | +| `fee_collector` | Account | Included **iff** the `fee_collector` argument is `?Account`. If the argument is `null`, this field **MUST be omitted** from `tx`. | +| `ts` | `Nat` | Always included. MUST equal the `created_at_time` argument (nanoseconds since Unix epoch, fitting in `nat64`). | | `caller` | `Blob` | Always included. Principal bytes of the caller. | -**Notes:** -- The **absence** of the `fee_collector` field in the `tx` map means: - **“burn all fees from this block onward.”** +#### System-Generated `107feecol` Blocks +Ledgers MAY create `107feecol` blocks outside of a direct user call, for example +during an upgrade, migration, or recovery procedure. -#### System-Generated `107feecol` Blocks +System-generated blocks MUST conform to the following rules: -For blocks created by the ledger itself (e.g., during an upgrade/migration): +- MUST include a `tx` map. +- `tx.op` MUST be `"107set_fee_collector"`. +- `tx.fee_collector` MUST be present or omitted depending on the intended + configuration (collect vs burn). +- `tx.caller` MAY be omitted. + If included, it SHOULD be set to the ledger canister principal. -- MUST include `tx` with: - - `op` set to `"107set_fee_collector"`; - - `fee_collector` present or absent according to the intended configuration (collect vs burn). -- MAY omit `tx.created_at_time` and `tx.caller`. -- If `tx.caller` is included, it SHOULD be the ledger canister principal. +System-generated blocks **are not required** to include all fields that appear in the canonical mapping for user-initiated calls. +They only need to include the fields listed above. ### `icrc107_get_fee_collector` @@ -309,7 +311,7 @@ variant { Map = vec { variant { Blob = blob "\00\00\00\00\02\00\01\0d\01\01" }; // owner principal bytes 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" } // 32-byte subaccount }}}; - record { "created_at_time"; variant { Nat = 1_750_951_727_000_000_000 : nat }}; // ns since Unix epoch + record { "ts"; variant { Nat = 1_750_951_727_000_000_000 : nat }}; // ns since Unix epoch record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; // caller principal bytes }}}; @@ -338,9 +340,9 @@ variant { Map = vec { // Top-level tx (constructed per Canonical `tx` Mapping) record { "tx"; variant { Map = vec { record { "op"; variant { Text = "107set_fee_collector" }}; - // NOTE: there is intentionally NO "fee_collector" entry here. - // Its absence means "burn all fees from this block onward". - record { "created_at_time"; variant { Nat = 1_750_951_728_000_000_000 : nat }}; + // NOTE: there is intentionally NO "fee_collector" entry here because + // the method argument was `fee_collector = null`. + record { "ts"; variant { Nat = 1_750_951_728_000_000_000 : nat }}; record { "caller"; variant { Blob = blob "\00\00\00\00\00\00\00\00\01\01" }}; }}}; From 0096436602521499e9cce673b3f201830d8d0b09 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Fri, 14 Nov 2025 17:01:31 +0100 Subject: [PATCH 40/45] fixed formatting --- ICRCs/ICRC-107/ICRC-107.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index e6a7b4b5..c5fc7be7 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -332,7 +332,7 @@ identified by `"\00\00\00\00\02\00\01\0d\01\01"`, then the A block that explicitly sets fee burning by **omitting** the fee collector (i.e., all fees are burned from this point onward): -```text +``` variant { Map = vec { // Block type record { "btype"; variant { Text = "107feecol" }}; @@ -350,7 +350,7 @@ variant { Map = vec { record { "ts"; variant { Nat = 1_741_312_737_184_874_392 : nat }}; record { "phash"; variant { Blob = blob "\2d\86\7f\34\c7\2d\1e\2d\00\84\10\a4\00\b0\b6\4c\3e\02\96\c9\e8\55\6f\dd\72\68\e8\df\8d\8e\8a\ee" }}; }} - +``` From 5edf1ae0eb40e50cfd82f8426476b50e41b3e523 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 19 Nov 2025 14:07:29 +0100 Subject: [PATCH 41/45] removed reference to legacy --- ICRCs/ICRC-107/ICRC-107.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index c5fc7be7..43b34b2f 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -152,7 +152,7 @@ This ensures that fee handling is always well-defined, both before and after the **Notes** - Invariants from ICRC-3 still apply (e.g., `effective fee ≤ amt` for mints; sufficient balance for `amt + effective fee`; allowance reductions include `amt + effective fee` where applicable). -- ICRC-107 does **not** define who the fee payer is — that comes from the semantics of each block type (e.g., ICRC-1/2 legacy rules in ICRC-3). +- ICRC-107 does **not** define who the fee payer is — that comes from the semantics of each block type (e.g., ICRC-1/2 rules in ICRC-3). @@ -226,8 +226,8 @@ icrc107_set_fee_collector: (SetFeeCollectorArgs) -> (variant { Ok: nat; Err: Set - **Duplicate** — a semantically identical transaction was already accepted (per the ledger’s deduplication rules); response **MUST** include `duplicate_of` with the existing block index. - **GenericError** — any other failure that prevents constructing or appending a valid `107feecol` block. -- **Clarifications** - A `107feecol` block whose `tx` omits `fee_collector` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies **legacy behavior** applies until the first ICRC-107 setting appears). +- **Note** + A `107feecol` block whose `tx` omits `fee_collector` (explicit **burn**) is **not** the same as “no `107feecol` has ever been set” (which implies legacy behavior applies until the first ICRC-107 setting appears). --- From c4837cf640ce2ba1551b9bd39230dc11635913dd Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 19 Nov 2025 16:38:13 +0100 Subject: [PATCH 42/45] removed the system generated blocks section --- ICRCs/ICRC-107/ICRC-107.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 43b34b2f..0682d2a5 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -238,7 +238,6 @@ This mapping defines the exact `tx` structure that **MUST** appear in every System-generated `107feecol` blocks (e.g., created by the ledger during an upgrade or migration) **do not** need to include all the fields that appear in this mapping. -They MUST follow the structural rules in Section *System-Generated `107feecol` Blocks*. For all user-initiated calls, the `tx` map **MUST** contain exactly the following fields, with the specified encoding: @@ -251,23 +250,6 @@ with the specified encoding: | `caller` | `Blob` | Always included. Principal bytes of the caller. | -#### System-Generated `107feecol` Blocks - -Ledgers MAY create `107feecol` blocks outside of a direct user call, for example -during an upgrade, migration, or recovery procedure. - -System-generated blocks MUST conform to the following rules: - -- MUST include a `tx` map. -- `tx.op` MUST be `"107set_fee_collector"`. -- `tx.fee_collector` MUST be present or omitted depending on the intended - configuration (collect vs burn). -- `tx.caller` MAY be omitted. - If included, it SHOULD be set to the ledger canister principal. - -System-generated blocks **are not required** to include all fields that appear in the canonical mapping for user-initiated calls. -They only need to include the fields listed above. - ### `icrc107_get_fee_collector` From fda84fed9b272214f69d5139e9c8eb0f28c3ede3 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Wed, 19 Nov 2025 17:48:12 +0100 Subject: [PATCH 43/45] author & draft badge --- ICRCs/ICRC-107/ICRC-107.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 0682d2a5..70dccb04 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,5 +1,8 @@ # ICRC-107: Fee Management +![DRAFT] + +**Authors:** [Bogdan Warinschi](https://github.com/bogwar) ## Introduction & Motivation Different ICRC-based ledgers (e.g., ckBTC) handle fee collection inconsistently. @@ -390,3 +393,10 @@ To determine who collects the fee in a block: - If `fee_col` is specified in the block the fee is collected by `fee_col`. - If `fee_col_block` is specified use the `fee_col` from the referenced block index. - If neither `fee_col` nor `fee_col_block` are specified, then the fee is burned. + + +[DRAFT]: https://img.shields.io/badge/STATUS-DRAFT-f25a24.svg + +[APPROVED]: https://img.shields.io/badge/STATUS-APPROVED-ed1e7a.svg + +[EXTENDS 3]: https://img.shields.io/badge/EXTENDS-ICRC--3-ed1e7a.svg \ No newline at end of file From 5580f0dea7426e9b9cdd4789ec31cd4b9a72adf6 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Thu, 20 Nov 2025 16:25:25 +0100 Subject: [PATCH 44/45] extends badge --- ICRCs/ICRC-107/ICRC-107.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index 70dccb04..deafb132 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -1,6 +1,6 @@ # ICRC-107: Fee Management -![DRAFT] +![DRAFT] [![EXTENDS3]](https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-3/README.md) **Authors:** [Bogdan Warinschi](https://github.com/bogwar) ## Introduction & Motivation @@ -399,4 +399,4 @@ To determine who collects the fee in a block: [APPROVED]: https://img.shields.io/badge/STATUS-APPROVED-ed1e7a.svg -[EXTENDS 3]: https://img.shields.io/badge/EXTENDS-ICRC--3-ed1e7a.svg \ No newline at end of file +[EXTENDS3]: https://img.shields.io/badge/EXTENDS-ICRC--3-ed1e7a.svg \ No newline at end of file From b81fb3ed1989b8977712a667ec42fca68c7ce735 Mon Sep 17 00:00:00 2001 From: Bogdan Warinschi Date: Mon, 1 Dec 2025 13:40:05 +0100 Subject: [PATCH 45/45] minor fix --- ICRCs/ICRC-107/ICRC-107.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICRCs/ICRC-107/ICRC-107.md b/ICRCs/ICRC-107/ICRC-107.md index deafb132..f7486c79 100644 --- a/ICRCs/ICRC-107/ICRC-107.md +++ b/ICRCs/ICRC-107/ICRC-107.md @@ -376,7 +376,7 @@ Until the first block of type `107feecol`, the ledger follows the following lega - If `fee_col` is set in a block, the designated account collects only transfer fees (`1xfer`, `2xfer`). Fees for all other operations (e.g., `2approve`) are burned in DFINITY-maintained ICRC-3 ledgers. -New implementations SHOULD avoid using `fee_col` and instead use `fee_collector` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future versions of this standard. +New implementations SHOULD avoid using `fee_col` and instead use `fee_collector` for all fee collection settings. Legacy behavior is provided for backward compatibility only and MAY be deprecated in future iterations of this standard. ### Handling Fee Collection for ICRC-1 and ICRC-2 Blocks