Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard for index discovery from ledgers #106

Closed
wants to merge 22 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions ICRCs/ICRC-106/ICRC-106.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
| Status |
|:------:|
|Draft|

# ICRC-106: Standard for Associating Index Canisters with ICRC-1 Tokens

## 1. Introduction

Wallet applications and token management tools often need to retrieve both token metadata and transaction history for a given principal. However, identifying an associated index canister for ICRC-1 tokens is currently unstandardized, leading to inconsistencies in wallet integrations.

Standard **ICRC-106** :
1. Introduces a standard approach for indicating the presence of an index canister for ICRC-1 tokens through ledger metadata.
2. Defines a minimal interface for the associated index canister to facilitate querying transaction history in a consistent manner.

Copy link

@aterga aterga Nov 26, 2024

Choose a reason for hiding this comment

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

Suggested change
**Which canisters are in scope of this standard?**
The standard is specifying the behavior of an Index canister. Additionally, it suggests, but does not prescribe, how to design the API of the corresponding Ledger canister.

Copy link
Author

Choose a reason for hiding this comment

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

This is more accurate:

Standard ICRC-106 :

  1. Introduces a standard approach for indicating the presence of an index canister for ICRC-1 tokens through ledger metadata.
  2. Defines a minimal interface for the associated index canister to facilitate querying transaction history in a consistent manner.

Since the index is not standardized anywhere, here we prescribe the API one should expect, since otherwise discovering the index is not very useful

Copy link

Choose a reason for hiding this comment

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

Thanks, this is better.

I suppose it's still a bit implicit what canisters are restricted by this standard.

To make it concrete, which of the following statements would make sense:

  1. Our Index complies with ICRC-106, but our Ledger does not support that standard yet.
  2. Our Ledger complies with ICRC-106, but our Index does not support that standard yet.
  3. We have an ICRC-106-compliant Ledger suite.

Copy link
Author

Choose a reason for hiding this comment

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

Right now 1 is true. Once we make the changes to the ledger 3 will be true.

This draft standard aims to improve interoperability, simplify wallet integrations, and enable token-related applications to reliably access transaction histories. It acts as a placeholder and documentation source until a more comprehensive standard for index canisters will be developed.


## 2. Metadata

A ledger implementing ICRC-106 MUST include the following entry in the output of the `icrc1_supported_standards` method:

```candid
record { name = "ICRC-106"; url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-106" }
```

Additionally, the ledger MUST provide the following metadata entry retrievable via the `icrc1_metadata` method:

- `icrc106:index_principal` (text): The textual representation of the principal of the associated index canister.

These metadata entries allow clients to discover and interact with the index canister associated with a ledger and can be retrieved using method `icrc1_metadata` defined by the ICRC-1 standard.


Compliant ledgers MUST also implement the following endpoint for programmatically retrieving the index principal:

```candid
icrc106_get_index_principal: () -> (principal) query;
```

The metadata entry `icrc106:index_principal` and the `icrc106_get_index_principal` method MUST provide consistent information. Specifically:

- The `icrc106:index_principal` metadata entry MUST represent the textual form of the principal returned by the `icrc106_get_index_principal` method.
- The `icrc106_get_index_principal` method MUST return the principal corresponding to the index canister associated with the ledger, as specified in the `icrc106:index_principal` metadata entry.

This requirement ensures that both mechanisms reliably point to the same index canister.


## 3. Index Canister Interface

The index canister associated with the ledger SHOULD implement the following minimal Candid interface:
bogwar marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

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

for me it's a bit confusing that we're saying the index SHOULD implement the candid interface, but then further down suggest that they are definitely there and somehow part of this standard

The index canister provides methods to facilitate querying of transaction history and metadata associated with accounts. Below is a description of the relevant methods specified in this standard, including their purpose, input, output, and typical use case.

While the methods defined in this standard are sufficient for compliance with ICRC-106, certain implementations of the index canister may include additional methods to extend functionality. These methods are not required by ICRC-106 but may be present for advanced use cases:

  • Any changes to the index canister interface should maintain backward compatibility.

By adhering to ICRC-106, ledger canisters provide a standardized mechanism for clients to discover and interact with their associated index canisters, improving integration and user experience within the Internet Computer ecosystem.

why not standardise the index canisters interface first and then how it can be discovered from a ledger?

Copy link
Author

Choose a reason for hiding this comment

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

There's two reasons why we proceed this way:

  • standardized interfaces need to have name spaced methods, so a standardised index interface would have methods like icrc143_get_transactions().

  • There's many different opinions about index canisters and converging on a standard will take time. We don't want to postpone adding this metadata until we have a proper standard for index canisters.

So this standard, which we don't plan to vote on really plays the role of documentation for: discovering the current index & the relevant part of the API of the current index.

Choose a reason for hiding this comment

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

Ok, that makes sense. I would still try to change the wording in the current standard to avoid giving the impression there is some sort of standardised interface when in reality there is not.

Below is a description of the relevant methods specified in this standard [...]

While the methods defined in this standard are sufficient for compliance with ICRC-106 [...]

By adhering to ICRC-106, ledger canisters provide a standardized mechanism for clients to discover and interact with their associated index canisters [...]

Choose a reason for hiding this comment

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

Maybe also make it explicit that the interface of the index is subject to further discussions and another ICRC

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the suggestion @letmejustputthishere. I've added something along these lines in the introduction of the standard.


```candid
type Tokens = nat;

type BlockIndex = nat;

type SubAccount = blob;
bogwar marked this conversation as resolved.
Show resolved Hide resolved

type Account = record {
owner: principal;
subaccount: opt SubAccount;
};

type GetAccountTransactionsArgs = record {
account: Account;
start: opt BlockIndex; // The block index of the last transaction seen by the client.
max_results: nat; // Maximum number of transactions to fetch.
};

type Burn = record {
from : Account;
memo : opt blob;
created_at_time : opt nat64;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
amount : Tokens;
spender : opt Account;
};

type Mint = record {
to : Account;
memo : opt blob;
created_at_time : opt nat64;
amount : Tokens;
};

type Transfer = record {
to : Account;
fee : opt Tokens;
from : Account;
memo : opt blob;
created_at_time : opt nat64;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
amount : Tokens;
spender : opt Account;
};

type Approve = record {
fee : opt Tokens;
from : Account;
memo : opt blob;
created_at_time : opt nat64;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
amount : Tokens;
expected_allowance : opt nat;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
expires_at : opt nat64;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
spender : Account;
};

type Transaction = record {
burn : opt Burn;
kind : text;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
mint : opt Mint;
approve : opt Approve;
timestamp : nat64;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
transfer : opt Transfer;
};

type TransactionWithId = record {
id : BlockIndex;
transaction : Transaction;
};

type GetTransactions = record {
balance : Tokens;
transactions : vec TransactionWithId;
// The txid of the oldest transaction the account has
oldest_tx_id : opt BlockIndex;
};

type GetTransactionsErr = record {
message : text;
};

type GetAccountTransactionsResult = variant {
Ok : GetTransactions;
Err : GetTransactionsErr;
};

type ListSubaccountsArgs = record {
owner: principal;
start: opt SubAccount;
};

type Status = record {
num_blocks_synced : BlockIndex;
};

service : {
bogwar marked this conversation as resolved.
Show resolved Hide resolved
get_account_transactions: (GetAccountTransactionsArgs) -> (GetAccountTransactionsResult) query;
ledger_id: () -> (principal) query;
list_subaccounts : (ListSubaccountsArgs) -> (vec SubAccount) query;
bogwar marked this conversation as resolved.
Show resolved Hide resolved
status : () -> (Status) query;
}
```



# Methods Provided by the Index Canister

The index canister provides methods to facilitate querying of transaction history and metadata associated with accounts. Below is a description of the relevant methods specified in this standard, including their purpose, input, output, and typical use case.

---

## get_account_transactions
- **Purpose**: Retrieves transactions associated with a specific account, starting from a specified block index and returning up to a maximum number of results. Transactions are returned in **reverse chronological order** (newest to oldest).
- **Input**:
- `account`: The account (principal and optional subaccount) for which transactions are to be fetched.
- `start`: *(Optional)* The block index of the most recent transaction the client has already seen. If provided, only transactions with block indices **less than** this value will be returned.
- `max_results`: The maximum number of transactions to return.
- **Output**:
- **`Ok`**: Returns a record containing:
- `balance`: The current token balance of the account.
- `transactions`: A vector of `TransactionWithId`, each containing:
- `id`: The block index of the transaction.
- `transaction`: Details of the transaction (burn, transfer, approval, and timestamp).
- `oldest_tx_id`: *(Optional)* The block index of the oldest transaction for the account, or `None` if no transactions exist.
- **Typical Use Case**: This method is often used by wallets to display transaction history and update account balances. It also supports paginated transaction retrieval for efficient history browsing.

---

## list_subaccounts
- **Purpose**: Lists all subaccounts associated with a specified principal.
- **Input**:
- `owner`: The principal for which to list subaccounts.
bogwar marked this conversation as resolved.
Show resolved Hide resolved
- `start`: *(Optional)* Specifies the subaccount to start listing from. Only subaccounts lexicographically greater than `start` will be included. If start is omitted, the method will return all subaccounts from the beginning of the list, ordered lexicographically.
bogwar marked this conversation as resolved.
Show resolved Hide resolved
- **Output**: A vector of `SubAccount`, each representing a subaccount under the specified principal. The list will be empty if the principal has not used subaccounts, or if there are no subaccounts lexicographically higher than `start`.
- **Typical Use Case**: Useful for wallets or tools that need to enumerate *all* subaccounts associated with a user. To get all subaccounts, start with no start parameter and repeatedly call the method, updating start with the last subaccount from each response, until the returned list is empty.


---

## ledger_id
- **Purpose**: Retrieves the principal of the ledger canister that the index is linked to.
- **Input**: None.
- **Output**: The `principal` of the ledger canister.
- **Typical Use Case**: This method is primarily used for validating the relationship between the index and the ledger, ensuring they are correctly linked, and facilitating integrations requiring the ledger’s identity.


---

## status
- **Purpose**: Retrieves the synchronization and operational status of the index canister.
- **Input**: None.
- **Output**: A `Status` record containing:
- `num_blocks_synced`: The total number of blocks that have been successfully synchronized by the index canister.
- **Typical Use Case**: Used for monitoring the health and synchronization status of the index, this method is helpful for determining whether the index has fully caught up with the ledger and is operational.


## Optional Methods
bogwar marked this conversation as resolved.
Show resolved Hide resolved

While the methods defined in this standard are sufficient for compliance with ICRC-106, certain implementations of the index canister may include additional methods to extend functionality. These methods are not required by ICRC-106 but may be present for advanced use cases:

- **`get_blocks`**: Fetches raw block data for a specified range of indices. This is useful for applications requiring detailed historical data.
- **`get_fee_collectors_ranges`**: Provides detailed information about fee collection, including accounts and associated block ranges.
- **`icrc1_balance_of`**: Queries the token balance of specific accounts. This method is commonly used for token management in wallets and tools.

These methods, while potentially helpful, are outside the scope of ICRC-106 and are not guaranteed to be present in all index canisters. Developers should refer to the documentation of the specific implementation they are working with for details on these optional methods.
bogwar marked this conversation as resolved.
Show resolved Hide resolved




## 4. Implementation Considerations

Implementers should ensure that:
- The `icrc106:index_principal` metadata entry accurately reflects the principal of the associated index canister.
bogwar marked this conversation as resolved.
Show resolved Hide resolved
- Any changes to the index canister interface should maintain backward compatibility.

By adhering to ICRC-106, ledger canisters provide a standardized mechanism for clients to discover and interact with their associated index canisters, improving integration and user experience within the Internet Computer ecosystem.