---
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.
``` solidity
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`.
``` solidity
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.
``` solidity
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.
``` solidity
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.
``` solidity
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`.
``` solidity
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`.
``` solidity
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.
``` solidity
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.
``` solidity
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`.
``` solidity
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.
``` solidity
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.
``` solidity
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.
``` solidity
function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);
```
## Interface
``` solidity
/// @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);
}
```