Skip to content

ERC 1410: Partially Fungible Token Standard #1410

Closed
@adamdossa

Description

@adamdossa

eip: ERC-1410
title: Partially Fungible Token Standard (part of the ERC-1400 Security Token Standards)
author: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep)
discussions-to: #1411
status: Draft
type: Standards Track
category: ERC
created: 2018-09-13
require: ERC-1066 (#1066)


Simple Summary

A standard interface for organising an owners tokens into a set of partitions.

Abstract

This standard sits under the ERC-1400 (#1411) umbrella set of standards related to security tokens.

Describes an interface to support an owners tokens being grouped into partitions, with each partition being represented by an identifying key and a balance.

Tokens are operated upon at a partition granularity, but data about the overall supply of tokens and overall balances of owners is also tracked.

This standard can be combined with ERC-20 (#20) or ERC-777 (#777) to provide an additional layer of granular transparency as to the behaviour of a token contract on different partitions of a token holders balance.

Motivation

Being able to associate metadata with individual fungible tokens is useful when building functionality associated with those tokens.

For example, knowing when an individual token was minted allows vesting or lockup logic to be implemented for a portion of a token holders balance.

Tokens that represent securities often require metadata to be attached to individual tokens, such as restrictions associated with the share.

Being able to associate arbitrary metadata with groups of tokens held by users is useful in a variety of use-cases. It can be used for token provenance (i.e. recording the previous owner(s) of tokens) or to attach data to a token which is then used to determine any transfer restrictions of that token.

In general it may be that whilst tokens are fungible under some circumstances, they are not under others (for example in-game credits and deposited balances). Being able to define such groupings and operate on them whilst maintaining data about the overall distribution of a token irrespective of this is useful in modelling these types of assets.

Having a standard way to identify groupings of tokens within an overall balance helps provides token holders transparency over their balances.

Rationale

A Partially-Fungible Token allows for attaching metadata to a partial balance of a token holder. These partial balances are called partitions and are indexed by a bytes32 _partition key which can be associated with metadata on-chain or off-chain.

The specification for this metadata, beyond the existence of the _partition key to identify it, does not form part of this standard. The token holders address can be paired with the partition to use as a metadata key if data varies across token holders with the same partition (e.g. a "restricted" partition may be associated with different lock up dates for each token holder).

For an individual owner, each token in a partition therefore shares common metadata.

Token fungibility includes metadata so we have:

  • for a specific user, tokens within a given partition are fungible
  • for a specific user, tokens from different partitions may not be fungible

Note - partitions with the same bytes32 key across different users may be associated with different metadata depending on the implementation.

Backwards Compatibility

This standard is un-opinionated on ERC-20 vs. ERC-777. It can be easily combined with either standard, and we expect this to usually be the case. We don't define the standard token view functions (name, symbol, decimals) as a consequence.

In order to remain backwards compatible with ERC-20 / ERC-777 (and other fungible token standards) it is necessary to define what partition or partitions are used when a transfer / send operation is executed (i.e. when not explicitly specifying the partition). However this is seen as an implementation detail (could be via a fixed list, or programatically determined). One option is to simple iterate over all partitionsOf for the token holder, although this approach needs to be cognisant of block gas limits.

Specification

Token Information

balanceOf

Aggregates a token holders balances across all partitions. Equivalent to balanceOf in the ERC-20/777 specification.

MUST count the sum of all partition balances assigned to a token holder.

function balanceOf(address _tokenHolder) external view returns (uint256);

balanceOfByPartition

As well as querying total balances across all partitions through balanceOf there may be a need to determine the balance of a specific partition.

For a given token holder, the sum of balanceOfByPartition across partitionsOf MUST be equal to balanceOf.

function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);

partitionsOf

A token holder may have their balance split into several partitions (partitions) - this function will return all of the partitions that could be associated with a particular token holder address. This can include empty partitions, but MUST include any partitions which have a non-zero balance.

function partitionsOf(address _tokenHolder) external view returns (bytes32[]);

totalSupply

Returns the total amount of tokens issued across all token holders and partitions.

MUST count all tokens tracked by this contract.

function totalSupply() external view returns (uint256);

Tokens Transfers

Token transfers always have an associated source and destination partition, as well as the usual amounts and sender / receiver addresses.

As an example, a permissioned token may use partition metadata to enforce transfer restrictions based on:

  • the _partition value
  • any additional data associated with the _partition value (e.g. a lockup timestamp that may be associated with _partition)
  • any details associated with the sender or receiver of tokens (e.g. has their identity been established)
  • the amount of tokens being transferred (e.g. does it respect any daily or other period-based volume restrictions)
  • the _data parameter allows the caller to supply any additional authorisation or details associated with the transfer (e.g. signed data from an authorised entity who is permissioned to authorise the transfer)

Other use-cases include tracking provenance of tokens by associating previous holders with destination partitions.

transferByPartition

This function MUST throw if the transfer of tokens is not successful for any reason.

When transferring tokens from a particular partition, it is useful to know on-chain (i.e. not just via an event being fired) the destination partition of those tokens. The destination partition will be determined by the implementation of this function and will vary depending on use-case.

The function MUST return the bytes32 _partition of the receiver.

The bytes _data allows arbitrary data to be submitted alongside the transfer, for the token contract to interpret or record. This could be signed data authorising the transfer (e.g. a dynamic whitelist), or provide some input for the token contract to determine the receivers partition.

This function MUST emit a TransferByPartition event for successful transfers.

function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);

operatorTransferByPartition

Allows an operator to transfer security tokens on behalf of a token holder, within a specified partition.

This function MUST revert if called by an address lacking the appropriate approval as defined by isOperatorForPartition or isOperator.

This function MUST emit a TransferByPartition event for successful token transfers, and include the operator address.

The return data is interpreted consistently with transferByPartition.

function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);

canTransferByPartition

Transfers of partially fungible tokens may fail for a number of reasons, relating either to the token holders partial balance, or rules associated with the partition being transferred.

The standard provides an on-chain function to determine whether a transfer will succeed, and return details indicating the reason if the transfer is not valid.

These rules can either be defined using smart contracts and on-chain data, or rely on _data passed as part of the transferByPartition function which could represent authorisation for the transfer (e.g. a signed message by a transfer agent attesting to the validity of this specific transfer).

The function will return both a ESC (Ethereum Status Code) following the EIP-1066 standard, and an additional bytes32 parameter that can be used to define application specific reason codes with additional details (for example the transfer restriction rule responsible for making the transfer operation invalid).

It also returns the destination partition of the tokens being transferred in an analogous way to transferByPartition.

function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);

Operators

Operators can be authorised by individual token holders for either all partitions, or a specific partition.

  • a specific token holder and all partitions (authorizeOperator, revokeOperator, isOperator)
  • a specific token holder for a specific partition (authorizeOperatorByPartition, revokeOperatorByPartition, isOperatorForPartition)

authorizeOperator

Allows a token holder to set an operator for their tokens across all partitions.

MUST authorise an operator for all partitions of msg.sender

This function MUST emit the event AuthorizedOperator every time it is called.

function authorizeOperator(address _operator) external;

revokeOperator

Allows a token holder to revoke an operator for their tokens across all partitions.

NB - it is possible the operator will retain authorisation over this token holder and some partitions through authorizeOperatorByPartition.

MUST revoke authorisation of an operator previously given for all partitions of msg.sender

This function MUST emit the event RevokedOperator every time it is called.

function revokeOperator(address _operator) external;

isOperator

Returns whether a specified address is an operator for the given token holder and all partitions.

This should return TRUE if the address is an operator under any of the above categories.

MUST query whether _operator is an operator for all partitions of _tokenHolder.

function isOperator(address _operator, address _tokenHolder) external view returns (bool);

authorizeOperatorByPartition

Allows a token holder to set an operator for their tokens on a specific partition.

This function MUST emit the event AuthorizedOperatorByPartition every time it is called.

function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;

revokeOperatorByPartition

Allows a token holder to revoke an operator for their tokens on a specific partition.

NB - it is possible the operator will retain authorisation over this token holder and partition through either defaultOperatorsByPartition or defaultOperators.

This function MUST emit the event RevokedOperatorByPartition every time it is called.

function revokeOperatorByPartition(bytes32 _partition, address _operator) external;

isOperatorForPartition

Returns whether a specified address is an operator for the given token holder and partition.

This should return TRUE if the address is an operator under any of the above categories.

function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);

Interface

/// @title ERC-1410 Partially Fungible Token Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec

interface IERC1410 {

    // Token Information
    function balanceOf(address _tokenHolder) external view returns (uint256);
    function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);
    function partitionsOf(address _tokenHolder) external view returns (bytes32[]);
    function totalSupply() external view returns (uint256);

    // Token Transfers
    function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);
    function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);
    function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);    

    // Operator Information
    function isOperator(address _operator, address _tokenHolder) external view returns (bool);
    function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);

    // Operator Management
    function authorizeOperator(address _operator) external;
    function revokeOperator(address _operator) external;
    function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;
    function revokeOperatorByPartition(bytes32 _partition, address _operator) external;

    // Issuance / Redemption
    function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external;
    function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external;
    function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external;

    // Transfer Events
    event TransferByPartition(
        bytes32 indexed _fromPartition,
        address _operator,
        address indexed _from,
        address indexed _to,
        uint256 _value,
        bytes _data,
        bytes _operatorData
    );

    // Operator Events
    event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
    event RevokedOperator(address indexed operator, address indexed tokenHolder);
    event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
    event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);

    // Issuance / Redemption Events
    event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
    event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData);

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions