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

ICRC-106 #195

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ This repository contains standards for the [Internet Computer](https://internetc
* [ICRC-1: base fungible token standard](/standards/ICRC-1/README.md)
* [ICRC-2: approve and transfer_from](/standards/ICRC-2/README.md)
* [ICRC-3: block log](/standards/ICRC-3/README.md)
* [ICRC-xx: list oustanding allowances](/standard/ICRC-xx/README.md)
224 changes: 224 additions & 0 deletions standards/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.

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:

```candid
type Tokens = nat;

type BlockIndex = nat;

type SubAccount = blob;

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;
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;
amount : Tokens;
spender : opt Account;
};

type Approve = record {
fee : opt Tokens;
from : Account;
memo : opt blob;
created_at_time : opt nat64;
amount : Tokens;
expected_allowance : opt nat;
expires_at : opt nat64;
spender : Account;
};

type Transaction = record {
burn : opt Burn;
kind : text;
mint : opt Mint;
approve : opt Approve;
timestamp : nat64;
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 : {
get_account_transactions: (GetAccountTransactionsArgs) -> (GetAccountTransactionsResult) query;
ledger_id: () -> (principal) query;
list_subaccounts : (ListSubaccountsArgs) -> (vec SubAccount) query;
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.
- `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.
- **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

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.




## 4. Implementation Considerations

Implementers should ensure that:
- The `icrc106:index_principal` metadata entry accurately reflects the principal of the associated index canister.
- 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.
90 changes: 90 additions & 0 deletions standards/ICRC-191/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
| Status |
|:------:|
|Draft|

# ICRC-191: Enhanced Allowance Query Mechanism with Pagination

## 1. Introduction

Although calls to the `icrc2_approve` and `icrc2_transfer_from` methods are recorded in the ledger, it is not possible to determine the allowances that are in effect at some point in time, except by traversing the entire ledger. This standard introduced an endpoint which will return this information thus making the management of allowances feasible.

ICRC-191 is an extension of the ICRC-2 standard.
ICRC-191 specifies a way to list outstanding allowances.


## 2. Metadata

A ledger that implements ICRC-191 MUST must include `record {name = "ICRC-191"; url = "https://github.com/dfinity/ICRC-1/standards/ICRC-191"}` as part of the output of `icrc1_supported_standards`.

The endpoint introduced in this standard operates in two ways. In the public version any principal can obtain the outstanding allowances of any other principal. In the private version, the allowances returned by the endpoint must have been issued by the caller (i.e. the caller is the principal controlling the source account of an allowance.)
Which version of the standard is implemented by a ledger is specified through metadata which can be retrieved using `icrc1_metadata`.

A ledger that implements ICRC-191 MUST return metadata `icrc191:public_allowances` of type Text (optional). The possible values are "true" if the allowance data is public and "false" otherwise.


## 3. Methods

Some of the types used are shared with standards ICRC-1 and ICRC-2; we restate their definition for completeness.

```candid
icrc191_list_allowances : (ListAllowancesArgs) -> (ListAllowancesResult) quey

type ListAllowancesArgs = record {
from_account : opt Account;
prev_spender : opt Account;
take : opt nat;
}

ListAllowanceResult = vec record {
from_account : Account;
to_spender : Account;
allowance : Allowance;
}

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

type Allowance = record {
allowance : nat;
expires_at : opt nat64;
}
```

The endpoint returns up to `taken` allowances of the from_account.owner, starting with the allowance between `from_account` and `to_account`.


## 4. Semantics

Outstanding allowances, as specified in the ICRC-2 standard, are represented as a map from pairs of accounts to allowances. To specify the behavior of `icrc191_list_allowances`, the set of pairs `(Account, Account)` is ordered lexicographically. Let `first_principal` be the lexicographically first principal, and `first_subaccount` be the lexicographically first subaccount (the default subaccount, i.e., the all-0 32-byte string). Let `caller_principal` be the principal of the caller.

The `icrc191_list_allwances` method behaves as follows:

* If `from_account` is not provided, it is instantiated as `Account{caller_principal, first_subaccount}`.
* If `from_account.subaccount` is not provided, it is instantiated with `first_subaccount`.
* If `prev_spender` is not provided, it is instantiated with `Account{first_principal, first_subaccount}`.

If the ledger implements the private version of the standard, then the endpoint returns the empty list if `from_account.owner ≠ caller_principal`.

Otherwise, the endpoint returns a list of records of the form `(account_1, account_2, allowance)` in lexicographic order, starting with the allowance of `(from_account, prev_spender)` (if present), and where `account_1.owner = from_account.owner`. The list is limited to at most `taken` entries, or some maximum number of entries (that is an internal constant of the ledger).



## 5. Example Using Symbolic Values

Assume that allowances stored at some point by the ledger are, in lexicographic order:

- A1 = ((p0,s0), (p1,s1), a1)
- A2 = ((p0,s0), (p2,s2), a2)
- A3 = ((p0,s1), (p3,s3), a3)
- A4 = ((p1,s1), (p4,s4), a4)
- A5 = ((p1,s2), (p5,s5), a5)

Then:

- If `p0` calls the list allowances endpoint, with `from_account = (p0, s0)`, `prev_spender = None` and `take=4` the endpoint returns ``(A1, A2, A3)``, i.e. the endpoint only returns allowances of accounts belonging to `p0`. It is limited to allowances of `p0`, but not only to those having `(p0,s0)` as source of the allowance.

- If `p0` calls the list allowances endpoint with `from_account = (p1,s0)`, `p0 ≠ p1`, and the ledger implements the private version of the endpoint, then the endpoint returns the empty array.

- If `p0` calls the list allowances endpoint with `from_account = (p0,s0)`, `prev_spender = (p2,s)` for some `s1 < s < s2`, and `take = 2` the endpoint returns `(A2, A3)`. Since `(p2,s)` is not in the list, the allowances returned start with the first existing allowance between a pair of accounts greater than `(from_account, prev_spender)`.
Loading