Skip to content

Conversation

@bogwar
Copy link
Contributor

@bogwar bogwar commented Aug 6, 2025

Proposed change to ICRC-3 based on this proposal

@bogwar bogwar requested a review from Copilot August 6, 2025 18:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR updates the ICRC-3 specification to clarify how ICRC-1 and ICRC-2 blocks should be handled, specifically establishing that these legacy blocks must not include a btype field and providing concrete examples of account representation.

  • Explicitly requires ICRC-1/ICRC-2 blocks to omit the btype field, maintaining backward compatibility
  • Adds detailed examples of account representation as arrays with owner principal and optional subaccount
  • Updates the burn block schema to align with the legacy format requirements

@bogwar bogwar changed the title account examples, no btype for legacy blocks ICRC-3 refinment Sep 2, 2025
@bogwar bogwar changed the title ICRC-3 refinment ICRC-3 refinement Sep 2, 2025
Copy link

@q-uint q-uint left a comment

Choose a reason for hiding this comment

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

We're currently writing some (internal) libraries for ICRC3, would be great to have this merged!

Request: would be great to have a test suite which can just be a list of valid blocks.
I.e. burn, 1burn, mint, 1mint, etc. (maybe separated by generic/legacy).

Thanks for the work!

@bogwar
Copy link
Contributor Author

bogwar commented Sep 18, 2025

We're currently writing some (internal) libraries for ICRC3, would be great to have this merged!

Request: would be great to have a test suite which can just be a list of valid blocks. I.e. burn, 1burn, mint, 1mint, etc. (maybe separated by generic/legacy).

Thanks for the work!

Thanks for the comments!

Could you be a bit more specific about the form of such a test suite?

According to the current version of the standard there should be no 1burn btype, but rather this type would need to be inferred from the form of the generic blocks.

@q-uint
Copy link

q-uint commented Sep 18, 2025

Hi @bogwar, sorry the 1burn was a typo.

It seems that currently we are going to need to support both legacy and the new block log types. As this gets somewhat confusing, it would be nice to have a test suite with examples of each of these Value block types.

Personally I don't need these anymore, but it was a lot of work to construct tests which cover all possible flavors of block types. In the end we just ran the indexer against an SNS ledger to battle test it.

@q-uint
Copy link

q-uint commented Oct 9, 2025

Thanks for your time and work on this @bogwar! We're working toward supporting ICRC3 on non-ledger canisters, and your updates make the event log design clearer and more suitable for generic use cases.

@q-uint
Copy link

q-uint commented Oct 13, 2025

I don't want to block this PR from being merged but while applying ICRC-3 to our internal event log I encountered some 'blockers'.

Value type

  • tuple support?
  • bool support?
  • null support?
  • (Optional) principal support?

This is how I currently circumvent this:

impl ICRC3Value for Principal {
    fn to_value(&self) -> Value {
        // This is ok, since principal is a "blob".
        Value::Blob(self.as_slice().to_vec().into())
    }
}

impl ICRC3Value for bool {
    fn to_value(&self) -> Value {
        Value::Nat(Nat::from(*self as u8))
    }
}

Currently we can only support optional values in Map since this allows you to leave them out, yet we can not do the same in, i.e., Array, to represent tuples.

@bogwar
Copy link
Contributor Author

bogwar commented Oct 13, 2025

Thanks Quint!

On extending Value:
Value was intentionally minimal (the design was entirely ledger-driven). I’d prefer to keep it that way unless there’s a strong, recurring need.

Principals / bools:
Encoding principals as Blob is fine; bool as Nat 0/1 is also reasonable.

Tuples with optionals:
The tricky case is tuples containing opt. Two workable patterns:

  • Prefer Maps over tuples when fields can be optional (omit absent fields).

  • If you must keep array/tuple semantics, use a tagged option per slot: None = [Nat 0], Some(x) = [Nat 1, x].

I’d suggest we keep Value unchanged and add a non-normative “Common Encodings” section with these recommendations (principal→Blob, bool→Nat 0/1, options in tuples via tags). That preserves minimalism while giving interoperable guidance. WDYT?

@bogwar
Copy link
Contributor Author

bogwar commented Oct 16, 2025

@q-uint Hi Quint, can you please take a look? I've added a non-normative section with encodings.

- "2approve" for `icrc2_approve` blocks


### Account Type
Copy link
Contributor

Choose a reason for hiding this comment

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

i still think it would be valuable to have a Principal variant in the Value. this way there is no ambiguity on how to interpret arrays

Copy link

@gregorydemay gregorydemay left a comment

Choose a reason for hiding this comment

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

Thanks @bogwar For revamping this! Some understanding questions and minor comments. Happy to discuss offline if that helps.

| Block i | | Block i+1 |
├─────────────────────────┤ ├─────────────────────────┤
◄──| phash = hash(Block i-1) |◄─────────| phash = hash(Block i) |
| ... | | ... |

Choose a reason for hiding this comment

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

Should the diagram maybe mention the other mandatory fields (like ts)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The focus of the diagram is on the chaining aspect; I would keep it like this.


Typed blocks that **do not** represent method calls (e.g., upgrade markers, maintenance events, migration records, or system actions) MAY omit the `tx` field entirely and therefore MAY omit `tx.op`.

Legacy ICRC-1/2 blocks continue to use their historical operation names (`"xfer"`, `"mint"`, `"burn"`, `"approve"`), and are exempt from namespacing requirements.

Choose a reason for hiding this comment

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

I don't understand this part. Those names come from our implementation of the ICRC ledgers, but AFAICT they are not mentioned anywhere in the ICRC-1 or ICRC-2 standards? In fact ICRC-1 only mentions the notion of block as being excluded, while ICRC-2 does not talk about blocks at all.

If a block represents the result of a **standardized user-initiated method call**, then:

- The block **SHOULD** include a `tx` field.
- For user-initiated blocks, a namespaced `tx.op` **SHOULD** be included (see above).

Choose a reason for hiding this comment

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

Understanding question: what's the difference betweentx.op and btype? Is-it because a btype block could be produced by several user endpoints?

which fields affect the meaning of the block and how extra fields are to be
treated (e.g., ignored by generic interpreters).

3. Derive pre-fee state transition

Choose a reason for hiding this comment

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

I don't really understand this part (also apply fee below), which seems ledger specific (it talks about fees) but that whole section ( Semantics of Blocks: Evaluation Model) should be domain-agnostic way, no?


### `icrc3_get_blocks`
Returns blocks for one or more requested ranges from the canonical block log.
Blocks that are no longer stored locally by the producer are returned indirectly via archive callbacks.

Choose a reason for hiding this comment

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

is-it correct that the callback is not-necessarily an ICRC-3 compliant method?

Comment on lines +586 to +587
- pre-fee state transition,
- fee payer (if applicable),

Choose a reason for hiding this comment

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

This seems ledger specific

Comment on lines +591 to +599
- **Describe how fees are handled**, if the block type involves fees:
- The standard **SHOULD** specify the **effective fee** (the fee that is
actually charged), where this is well-defined.
- The standard **SHOULD** specify the **fee payer**, as an expression
resolvable from fields in the block, where this is possible.
- If applicable, the standard **MAY** reference ICRC-107 to specify **where
the fee goes** (e.g., burned, sent to a collector, etc.).
- If some fee-related aspects are intentionally left implementation- or
ledger-defined, the standard SHOULD say so explicitly.

Choose a reason for hiding this comment

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

This seems ledger specific

Comment on lines +672 to +686
### Note on Fees

ICRC-3 standardizes how fees are recorded in blocks, but it does not prescribe how fees are
calculated or collected.

- Every standard that introduces a block type involving fees **SHOULD** specify who the
fee payer is and, where possible, how the effective fee is determined, so that
responsibility is unambiguous.
- Where this cannot be expressed generically (for example, because the policy is
intentionally ledger-defined), the standard SHOULD state that the fee payer and/or
fee amount are implementation- or ledger-specific.
- The rules for interpreting the amount and destination of fees MAY be delegated to
ICRC-107 (Fee Handling in Blocks). Ledgers that do not yet implement ICRC-107 MAY
still produce valid ICRC-3 blocks, but their fee behavior will remain
implementation-specific until aligned with ICRC-107.

Choose a reason for hiding this comment

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

Seems ledger specific

An ICRC-3 compatible producer canister MUST expose an endpoint listing all the supported block types via the endpoint `icrc3_supported_block_types`.

- For **typed** blocks, the producer canister MUST only produce blocks whose `"btype"` value is included in this list.
- For **legacy** ICRC-1/2 blocks (no `"btype"`), the producer canister MUST include the conventional identifiers (e.g., `"1xfer"`, `"2approve"`) in this list to advertise support, even though the blocks themselves do not carry a `"btype"` field.

Choose a reason for hiding this comment

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

Similar comment as above, I don't see in which standard those are specified.




## [ICRC-1](../ICRC-1/README.md) and [ICRC-2](../ICRC-2/README.md) Block Schema

Choose a reason for hiding this comment

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

Understanding question: why are we trying to retrofit our ICRC-1 and ICRC-2 implementations into the new ICRC-3 standard? Why not just say that the ledger X is ICRC-3 compliant starting block b and verifying past blocks < b involves knowing the implementation (which is needed anyway and describe din this section but should not maybe be part of ICRC-3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants