eip | title | author | discussions-to | status | type | category | created | require |
---|---|---|---|---|---|---|---|---|
ERC-1400 |
Security Token Standards |
Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep) |
Draft |
Standards Track |
ERC |
2018-09-09 |
ERC-1410 (#1410), ERC-1594 (#1594), ERC-1644 (#1644), ERC-1643 (#1643), ERC-20 (#20), ERC-1066 (#1066) |
Represents a library of standards for security tokens on Ethereum.
In aggregate provides a suite of standard interfaces for issuing / redeeming security tokens, managing their ownership and transfer restrictions and providing transparency to token holders on how different subsets of their token balance behave with respect to transfer restrictions, rights and obligations.
Standards should be backwards compatible with ERC-20 (#20) and easily extended to be compatible with ERC-777 (#777).
ERC-1410 (#1410): differentiated ownership / transparent restrictions ERC-1594 (#1594): on-chain restriction checking with error signalling, off-chain data injection for transfer restrictions and issuance / redemption semantics ERC-1643 (#1643): document / legend management ERC-1644 (#1644): controller operations (force transfer)
Accelerate the issuance and management of securities on the Ethereum blockchain by specifying standard interfaces through which security tokens can be operated on and interrogated by all relevant parties.
Taken together, these security token standards provide document management, error signalling, gate keeper (operator) access control, off-chain data injection, issuance / redemption semantics and expose partially fungible subsets of a token holders balance.
Moving the issuance, trading and lifecycle events of a security onto a public ledger requires having a standard way of modelling securities, their ownership and their properties on-chain.
The following requirements have been compiled following discussions with parties across the Security Token ecosystem.
- MUST have a standard interface to query if a transfer would be successful and return a reason for failure.
- MUST be able to perform forced transfer for legal action or fund recovery.
- MUST emit standard events for issuance and redemption.
- MUST be able to attach metadata to a subset of a token holder's balance such as special shareholder rights or data for transfer restrictions.
- MUST be able to modify metadata at time of transfer based on off-chain data, on-chain data and the parameters of the transfer.
- MUST support querying and subscribing to updates on any relevant documentation for the security.
- MAY require signed data to be passed into a transfer transaction in order to validate it on-chain.
- SHOULD NOT restrict the range of asset classes across jurisdictions which can be represented.
- MUST be ERC-20 compatible.
- COULD be ERC-777 compatible.
Transfers of securities can fail for a variety of reasons in contrast to utility tokens which generally only require the sender to have a sufficient balance.
These conditions could be related to metadata of the securities being transferred (i.e. whether they are subject to a lock-up period), the identity of the sender and receiver of the securities (i.e. whether they have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at the token level (i.e. the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor).
For ERC-20 tokens, the balanceOf
and allowance
functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be executed both on and off-chain.
For tokens representing securities the standard introduces a function canTransfer
/ canTransferByPartition
which provides a more general purpose way to achieve this when the reasons for failure are more complex; and a function of the whole transfer (i.e. includes any data sent with the transfer and the receiver of the securities).
In order to provide a richer result than just true or false, a byte return code is returned. This allows us to give a reason for why the transfer failed, or at least which category of reason the failure was in. The ability to query documents and the expected success of a transfer is included in Security Token section.
In order to support off-chain data inputs to transfer functions, transfer functions are extended to transferWithData
/ transferFromWithData
which can optionally take an additional bytes _data
parameter.
There are many types of securities which, although they represent the same underlying asset, need to have differentiating data tied to them.
This additional metadata implicitly renders these securities non-fungible, but in practice this data is usually applied to a subset of the security rather than an individual security. The ability to partition a token holder's balance into partitions, each with separate metadata is addressed in the Partially Fungible Token section.
For example a token holder's balance may be split in two: those tokens issued during the primary issuance, and those received through secondary trading.
Security token contracts can reference this metadata in order to apply additional logic to determine whether or not a transfer is valid, and determine the metadata that should be associated with the tokens once transferred into the receiver's balance.
Alternatively a security token can use this mechanism simply to be able to transparently display to investors how different subsets of their tokens will behave with respect to transfer restrictions. In this case, the balances could be determined programatically.
Security tokens usually have documentation associated with them. This could be an offering document, legend details and so on.
Being able to set / remove and retrieve these documents, and having events associated with these actions allows investors to stay up to date with documentation on their investments.
This standard does not provide any way for an investor to signal on-chain that they have read, or agree, with any of these documents.
Since security tokens are subject to regulatory and legal oversight (the details of which will vary depending on jurisdiction, regulatory framework and underlying asset) in many instances the issuer (or a party delegated to by the issuer acting as a controller, e.g. a regulator or transfer agent) will need to retain the ability to force transfer tokens between addresses.
These controller transfers should be transparent (emit events that flag this as a forced transfer) and the token contract itself should be explicit as to whether or not this is possible.
Examples of where this may be needed is to reverse fraudulent transactions, resolve lost private keys and responding to a court order.
This standard does not specify any additional functions, but references ERC-1410 (#1410), ERC-1594 (#1594), ERC-1643 (#1643) and ERC-1655 (#1644) as an underlying library of security token standards, each covering a different aspect of security token functionality.
In order to combine these two standards, the additional constraints are specified.
If the token is controllable (isControllable
returns TRUE
) then the controller may use operatorTransferByPartition
without being explicitly authorised by the token holder.
In this instance, the operatorTransferByPartition
MUST also emit a ControllerTransfer event.
Correspondingly, if isControllable
returns FALSE
then the controller cannot call operatorTransferByPartition
unless explicitly authorised by the token holder.
If the token is controllable (isControllable
returns TRUE
) then the controller may use operatorRedeemByPartition
without being explicitly authorised by the token holder.
In this instance, the operatorRedeemByPartition
MUST also emit a ControllerRedemption event.
Correspondingly, if isControllable
returns FALSE
then the controller cannot call operatorRedeemByPartition
unless explicitly authorised by the token holder.
In order for transfer
and transferWithData
to operate on partially fungible tokens, there needs to be some notion of default partitions that these functions apply to. The details for how these are determined (e.g. either a fixed list, dynamically, or using partitionsOf
) is left as an implementation detail rather than defined as part of the standard.
When transferring tokens as part of a transfer
or transferWithData
operation, these transfers should respect the invariant of partially fungible tokens, namely that the sum of the balances across all partitions should equal to the total balance of a token holder.
/// @title IERC1400 Security Token Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec
interface IERC1400 is IERC20 {
// Document Management
function getDocument(bytes32 _name) external view returns (string, bytes32);
function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external;
// Token Information
function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);
function partitionsOf(address _tokenHolder) external view returns (bytes32[]);
// Transfers
function transferWithData(address _to, uint256 _value, bytes _data) external;
function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external;
// Partition 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);
// Controller Operation
function isControllable() external view returns (bool);
function controllerTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external;
function controllerRedeem(address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external;
// 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;
// Operator Information
function isOperator(address _operator, address _tokenHolder) external view returns (bool);
function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);
// Token Issuance
function isIssuable() external view returns (bool);
function issue(address _tokenHolder, uint256 _value, bytes _data) external;
function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external;
// Token Redemption
function redeem(uint256 _value, bytes _data) external;
function redeemFrom(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 Validity
function canTransfer(address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);
function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);
function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);
// Controller Events
event ControllerTransfer(
address _controller,
address indexed _from,
address indexed _to,
uint256 _value,
bytes _data,
bytes _operatorData
);
event ControllerRedemption(
address _controller,
address indexed _tokenHolder,
uint256 _value,
bytes _data,
bytes _operatorData
);
// Document Events
event Document(bytes32 indexed _name, string _uri, bytes32 _documentHash);
// Transfer Events
event TransferByPartition(
bytes32 indexed _fromPartition,
address _operator,
address indexed _from,
address indexed _to,
uint256 _value,
bytes _data,
bytes _operatorData
);
event ChangedPartition(
bytes32 indexed _fromPartition,
bytes32 indexed _toPartition,
uint256 _value
);
// 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 Issued(address indexed _operator, address indexed _to, uint256 _value, bytes _data);
event Redeemed(address indexed _operator, address indexed _from, uint256 _value, bytes _data);
event IssuedByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _to, uint256 _value, bytes _data, bytes _operatorData);
event RedeemedByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _from, uint256 _value, bytes _operatorData);
}
The rules determining if a security token can be sent may be self-executing (e.g. a rule which limits the maximum number of investors in the security) or require off-chain inputs (e.g. an explicit broker approval for the trade). To facilitate the latter, the transferByPartition
, transferWithData
, transferFromWithData
, canTransferByPartition
and canTransfer
functions accept an additional bytes _data
parameter which can be signed by an approved party and used to validate a transfer.
The specification for this data is outside the scope of this standard and would be implementation specific.
Under many jurisdictions, whether a party is able to receive and send security tokens depends on the characteristics of the party's identity. For example, most jurisdictions require some level of KYC / AML process before a party is eligible to purchase or sell a particular security. Additionally, a party may be categorized into an investor qualification category (e.g. accredited investor, qualified purchaser), and their citizenship may also inform restrictions associated with their securities.
There are various identity standards (e.g. ERC-725 (#725), Civic, uPort) which can be used to capture the party's identity data, as well as other approaches which are centrally managed (e.g. maintaining a whitelist of addresses that have been approved from a KYC perspective). These identity standards have in common to key off an Ethereum address (which could be a party's wallet, or an identity contract), and as such the canTransfer
function can use the address of both the sender and receiver of the security token as a proxy for identity in deciding if eligibility requirements are met.
Beyond this, the standard does not mandate any particular approach to identity.
To improve the token holder experience, canTransfer
MUST return a reason byte code on success or failure based on the EIP-1066 application-specific status codes specified below. An implementation can also return arbitrary data as a bytes32
to provide additional information not captured by the reason code.
Code | Reason |
---|---|
0x50 |
transfer failure |
0x51 |
transfer success |
0x52 |
insufficient balance |
0x53 |
insufficient allowance |
0x54 |
transfers halted (contract paused) |
0x55 |
funds locked (lockup period) |
0x56 |
invalid sender |
0x57 |
invalid receiver |
0x58 |
invalid operator (transfer agent) |
0x59 |
|
0x5a |
|
0x5b |
|
0x5a |
|
0x5b |
|
0x5c |
|
0x5d |
|
0x5e |
|
0x5f |
token meta or info |
These codes are being discussed at:
https://ethereum-magicians.org/t/erc-1066-ethereum-status-codes-esc/283/24
- EIP 1410: Partially Fungible Token Standard
- EIP 1594: Core Security Token Standard
- EIP 1643: Document Management Standard
- EIP 1644: Controller Token Operation Standard
- EIP Draft
Copied from original issue: ethereum/EIPs#1400