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

Add EIP-1238: Non-transferable Token Standard #5617

Closed
wants to merge 11 commits into from
211 changes: 211 additions & 0 deletions EIPS/eip-1238.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
---
eip: 1238
title: Non-transferable Tokens
description: An interface for fungible and non-fungible, non-transferable tokens.
author: Raphael Roullet (@ra-phael), Chris Chung (@0xpApaSmURf), Nicola Greco (@nicola)
discussions-to: https://ethereum-magicians.org/t/eip-1238-non-transferable-tokens/9044
status: Draft
type: Standards Track
category: ERC
created: 2022-09-07
requires: 165, 712
---



## Abstract
A _badge_ or non-transferable token (_NTT_) is a token that cannot be transferred once assigned. Badges can be accumulated through time and put at stake. Simply speaking, badges are statements about a public key: they can be quantitative (e.g. reputation, experience) or qualitative (badges, titles).
Copy link
Contributor

Choose a reason for hiding this comment

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

Badges can be accumulated through time and put at stake.

I am not sure what "put at stake" means here. Is this some kind of staking mechanism, or are you talking about the badge issuer's reputation, or something else entirely?


The Non-Transferable Token standard defines a set of standard APIs allowing the identification of statements (called badges) attributed to a public key, such that different dapps and smart contract can use to filter users or to provide user with different badges different experiences. More importantly, this standard defines a way for which users can put their badges at stake. Badges cannot be transferred but can be lost (after staking) or can expire.
Copy link
Contributor

Choose a reason for hiding this comment

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

Couple grammar and stylistic changes:

Suggested change
The Non-Transferable Token standard defines a set of standard APIs allowing the identification of statements (called badges) attributed to a public key, such that different dapps and smart contract can use to filter users or to provide user with different badges different experiences. More importantly, this standard defines a way for which users can put their badges at stake. Badges cannot be transferred but can be lost (after staking) or can expire.
The Non-Transferable Token standard defines a set of APIs that identify statements (called badges) attributed to a public key, such that different dapps and smart contracts can filter users or tailor experiences based on what badges a user has. More importantly, this standard defines a way for users to put their badges at stake. Badges cannot be transferred but can be lost (after staking) or can expire.


## Motivation
The idea is to have tokens that once assigned cannot be transferred (like reputation) and that can be used by websites, or contracts to make users perform some actions. For example, if a user accumulates paper submissions at conferences, then they can use their paper badges to request grants. It's important that they can never share these badges.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think a badge can force a user to do something :P

Suggested change
The idea is to have tokens that once assigned cannot be transferred (like reputation) and that can be used by websites, or contracts to make users perform some actions. For example, if a user accumulates paper submissions at conferences, then they can use their paper badges to request grants. It's important that they can never share these badges.
The idea is to have tokens that once assigned cannot be transferred (like reputation) and that can be used by websites, or contracts to allow users to perform some actions. For example, if a user accumulates paper submissions at conferences, then they can use their paper badges to request grants. It's important that they can never share these badges.


This is the equivalent of a variety of other use cases
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
This is the equivalent of a variety of other use cases
This is the equivalent of a variety of other use cases:


- Accumulating badges: degrees from academic institutions, paper publications for conferences, badges that allow for access in a building or in a special chat.
- Experience points: points accumulated in a game, points accumulated by being honest in a decentralized system for some time or a DAO.
- Statements: more broadly, any statement that is issued or signed by a contract, a dao, a single user that requires to be on-chain.
- Subscription: badges can represent the validity of a paid subscription.

Additionally, this standard could be used as a primitive for Soulbound tokens, whereby the recovery of a Soul would be done by obtaining signatures from co-owners of tokens with the same id.
Copy link
Contributor

Choose a reason for hiding this comment

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

Since there has been at least 4 attempts to propose a soul-bound / non-transferable NFT, I'd also suggest you add to your motivation and rationale on what this ERC has better merit than other competing ERCs e.g. https://eips.ethereum.org/EIPS/eip-5114
https://eips.ethereum.org/EIPS/eip-5633

Copy link
Author

Choose a reason for hiding this comment

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

Good point @xinbenlv! I'm familiar with the other EIPs, but they either build on previous standards like EIP-1155 as extensions which means unecessary transfer functions or propose different designs leading to a different set of features. I added more in motivation: a7f94be


## Specification
Every contract compliant with this EIP must implement the following interface:

```solidity
interface IERC1238 is IERC165 {
/**
* @dev Emitted when `amount` tokens of token type `id` are minted to `to` by `minter`.
*/
event MintSingle(address indexed minter, address indexed to, uint256 indexed id, uint256 amount);

/**
* @dev Equivalent to multiple {MintSingle} events, where `minter` and `to` is the same for all token types.
*/
event MintBatch(address indexed minter, address indexed to, uint256[] ids, uint256[] amounts);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you have both MintSingle and MintBatch? Couldn't MintBatch handle both cases?

If there's a reason, please add it to the EIP's rationale section.

Copy link
Author

Choose a reason for hiding this comment

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

Good point, I like how it makes the interface simpler. I also applied the same reasoning to the events related to burning.


/**
* @dev Emitted when `amount` tokens of token type `id` owned by `owner` are burned by `burner`.
*/
event BurnSingle(address indexed burner, address indexed owner, uint256 indexed id, uint256 amount);

/**
* @dev Equivalent to multiple {BurnSingle} events, where `owner` and `burner` is the same for all token types.
*/
event BurnBatch(address indexed burner, address indexed owner, uint256[] ids, uint256[] amounts);

/**
* @dev Returns the amount of tokens of token type `id` owned by `account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
SamWilsn marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming back to this, I think you should be more specific about what behaviour you expect. Perhaps:

Suggested change
* - `account` cannot be the zero address.
* - `balanceOf(address(0x0))` MUST revert.

*/
function balanceOf(address account, uint256 id) external view returns (uint256);

/**
* @dev Returns the balance of `account` for a batch of token `ids`.
*
Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to repeat the non-zero requirement here.

*/
function balanceOfBatch(address account, uint256[] calldata ids) external view returns (uint256[] memory);

/**
* @dev Returns the balance of multiple `accounts` for a batch of token `ids`.
* This is equivalent to calling {balanceOfBatch} for several accounts in just one call.
*
* Requirements:
* - `accounts` and `ids` must have the same length.
*
*/
function balanceOfBundle(address[] calldata accounts, uint256[][] calldata ids)
external
view
returns (uint256[][] memory);
}
```

In addition, in order for a contract to be compliant with this EIP, it MUST also abide by the following:

- Implementers MUST NOT enable token transfers, i.e any update of the balances of an address must either come from burning the tokens they hold or being minted new tokens.
- Implementers MUST NOT allow tokens to be transferred between addresses after they have been minted.
- Implementers MUST allow token recipients to burn any token they receive.
- Implementers MUST only allow tokens to be minted with the consent of the recipient.
- Implementers MAY enable token issuers to burn the tokens they issued.

**Smart contracts MUST implement all of the functions in the `ERC1238TokenReceiver` interface to accept tokens being minted to them.**

The **URI Storage extension** is OPTIONAL for smart contracts that implement this EIP. This allows your contract to associate a unique URI for each token id.

```solidity
interface IERC1238URIStorage is IERC1238 {
/**
* @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
*/
event URI(uint256 indexed id, string value);

/**
* @dev Returns the Uniform Resource Identifier (URI) for `id` token.
*/
function tokenURI(uint256 id) external view returns (string memory);
}
```

The **Expirable extension** is OPTIONAL for smart contracts that implement this EIP. This allows your contract to associate a unique URI for each token id.

```solidity
interface IERC1238Expirable is IERC1238 {
/**
* @dev Returns the expiry date for tokens with a given `id`.
*/
function expiryDate(uint256 id) external view returns (uint256);

/**
* @dev Returns whether tokens are expired by comparing their expiry date with `block.timestamp`.
*/
function isExpired(uint256 id) external view returns (bool);

/**
* @dev Sets the expiry date for the tokens with id `id`.
*/
function setExpiryDate(uint256 id, uint256 date) external;

/**
* @dev [Batched] version of {setExpiryDate}.
*/
function setBatchExpiryDates(uint256[] memory ids, uint256[] memory dates) external;
}
```

The **Holdable extension** is OPTIONAL for smart contracts that implement this EIP. This enables token owners to let their tokens be held by another address or to stake them in a smart contract.

```solidity
interface IERC1238Holdable is IERC1238 {
/**
* @dev Event emitted when `from` entrusts `to` with `amount` of tokens with token `id`.
*/
event Entrust(address from, address to, uint256 indexed id, uint256 amount);

/**
* @dev Event emitted when tokens are burnt and the holder fails to acknowledge the burn.
*/
event BurnAcknowledgmentFailed(address holder, address burner, address from, uint256 indexed id, uint256 amount);

/**
* @dev Returns the balance of a token holder for a given `id`.
*/
function heldBalance(address holder, uint256 id) external view returns (uint256);

/**
* @dev Lets sender entrusts `to` with `amount`
* of tokens which gets transferred between their respective balances
* of tokens held.
*/
function entrust(
address to,
uint256 id,
uint256 amount
) external;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

This holdable extension needs a lot more explanation. You should define the concepts, the expected flow, etc.

Copy link
Author

Choose a reason for hiding this comment

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

I added more explanations in a17e69d



## Rationale
### Fungibility

The proposed interface and implementation is heavily inspired from [EIP-1155](./eip-1155.md) which paved the way for managing multiple token types in just one smart contract. It draws from the lessons and prior discussions that emerged with this Multi Token standard and therefore also inherits from the design decisions that were made for it. For instance, `name` and `symbol` were left out from the smart contract interface in favour of more expressive definitions in a token metadata.

This proposed interface and implementation chooses to stay agnostic regarding the fungibility of non-transferable tokens. What’s more, it recognises that in some cases it would make sense to have both fungible and non-fungible non-transferable tokens managed in the same contract. Let’s consider an online game issuing tokens. That game would issue both a non-transferable NFT when completing a level and non-transferable fungible tokens as experience points at the same time, in one transaction.

EIP-1155 presents several interesting features that are also applicable to these tokens:

1. There is no need to deploy multiple contracts for each token collection as with previous standards, this saves deployment gas cost
2. ERIP-1155 is fungibility-agnostic: the same smart contract can track both fungible tokens and NFTs
3. Batch operations are possible such as minting or querying the balance for multiple token ids in just one call
4. No more tokens stuck in contracts because they were sent by mistake. For transfers and batch transfers, if the recipient is a contract, the transaction will revert if it does not implement the `ERC1155TokenReceiver` interface
5. Smart contracts which implement the `ERC1155TokenReceiver` interface may reject an increase in balance

EIP-1155 conveniently supports **batch** operations, where a batch is represented by an array of token ids and an amount for each token id.
However a batch often times only concerns one address. While minting a batch of tokens to an address in one transaction is convenient, we felt the need to support minting to multiple addresses in one transaction.
As a result and keeping the strict definition that a batch is only for one address, this standard introduces the notion of a **bundle***.* A bundle is simply a collection of batches for multiple addresses.

### Consent

Discussions within the community have highlighted the risk of having unwanted, non-transferable tokens being issued to recipients without their consent, which could damage their reputation.

In order to remedy to this, implementations of this standard must not let tokens being minted without approval from the recipient. In the case of a smart contract recipient, this is achieved via the `IERC1238Receiver` interface and returning a specific value to approve a minting. For recipients that are Externally Owned Accounts (EOAs), they must provide a [EIP-712](./eip-712.md) signature to approve token minting which gets passed to the smart contract when calling a mint function.

## Backwards Compatibility
Because of the inspiration drawn from EIP-1155, many concepts and methods remain identical, namely the concept of minting and burning tokens, a batch, the `ERC1238TokenReceiver`, the `balanceOf` and `balanceOfBatch` functions and to some extent the base extension for URI storage.

Transfers events have been replaced by the MintSingle, MintBatch, BurnSingle and BurnBatch events whose names are less misleading in the context of this standard.

## Security Considerations
Given that the tokens are non-transferable, it becomes impossible to transfer them to another address in case of a suspected compromised account. Issuers should remain open to perform necessary checks on their end and allow tokens to be burnt and re-issued to someone.

Additionally, the use of a smart contract wallet where key rotation for ownership is possible is highly recommended.

Regarding privacy, even though this standard can be used for identity claims, issuers should refrain from using tokens to store any sensitive or personally identifiable information (given and family name, date of birth etc...) on-chain as this action cannot be undone. Issuers should warn users about the immutable and public nature of receiving a token, what it represents and must obtain the informed consent from recipients before minting a badge to them.

## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).