Automated Regulatory Compliance with Security Token Standards

A crypto token that passes the Howey Test is deemed a Security token. Security tokens are designed to represent complete or fractional ownership in an asset. While utility tokens have no limitations on who can send or receive the token, security tokens are subject to many restrictions based on identity and jurisdiction.

In the same way that the ERC20 token standard helped to create a boom in utility tokens, a security token standard would help drive the adoption of security tokens.

In this article, let’s look at the current state of security token standards in the Ethereum ecosystem and each of their approaches to build a self-regulatory governance mechanism for these tokens.

📬 Get updates straight to your inbox.

Subscribe to my newsletter so you don't miss new content.

The Case for Tokenization

Security tokens offer the benefit of bringing transparency over traditional paper shares through the use of the blockchain and its associated public ledger. Security token structure, distribution, or changes that could affect investors ca be publicly accessible to all on the blockchain.

Tokenizing private securities also creates opportunities for greater efficiency. Decentralized exchanges enable trades to occur securely between two parties without a middleman. The process and timeline for settlement and clearing of transactions can be condensed significantly, and any reconciliation processes can be greatly simplified. Tokenizing private securities has the potential to significantly reduce costs, increase speed of settlement, and improve security.

Securities Regulations

Private securities must follow any applicable laws to avoid demanding public filing requirements. These laws can require limiting the number of total investors, only allowing specific types of investors (e.g. accredited investors), implementing a holding period, and applying many other rules.

Restrictions differ by jurisdiction, and compliance with both the issuer’s jurisdiction as well as each investor’s jurisdiction is required. Furthermore, these restrictions apply not only to the initial offering (where much of the responsibility lies on the issuer), but to all secondary trades where responsibility is also placed on the seller.

Security Tokens

Enforcing regulatory requirements has been a significant roadblock in the adoption of blockchain-powered securities. Moving the issuance, trading and lifecycle events of a security onto a public ledger requires having a standard way of modeling securities, their ownership, and their properties on-chain.

In order for tokens to comply with securities regulations, tokens need a mechanism to impose transfer restrictions on Token Holders. For example, we may want to:

  • Require only Token Holders who have successfully gone through KYC/AML to be able to buy tokens from the issuer during the initial offering, and prevent them from being able to subsequently buy or sell to other parties unless they both have been cleared to do so.
  • Restrict transfers based on legal status, jurisdiction, sanctioned countries, global watchlists, and other rules.
  • Limit the number of investors.
  • Limit the maximum cap of each investor’s holdings.
  • Potentially other requirements.

Token standards such as ERC20 does not come with a governance mechanism that ensures token transfers comply with these requirements. A minimum viable compliance layer is to have whitelists of addresses who are validated Token Holders, and only allow transfers between whitelisted addresses. However, for the security token ecosystem to be interoperable with each other a predefined set of standards is necessary.

To solve this problem, we want to devise a self-regulatory mechanism for tokens that is administered by the network and enforced on-chain, allowing parties that trust each other to transact securely. Ideally, we also want this governance mechanism to become more and more decentralized over time.

Security Token Standards

Token Standards such as ERC20 has gained widespread adoption and allowed the interoperability of all kinds of tokens on different exchanges. Security tokens standards builds on top of standards like ERC20 to add the ability to enforce transfer restrictions at the token level. For your reference, here is the ERC20 Standard Token interface that all of the security token standards builds on:

contract ERC20 {
  function totalSupply() public view returns (uint256);
  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);

  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

At a high-level, adding transfer restrictions to tokens is straightforward. Consider the ERC20 transfer() function that moves tokens between two addresses:

function transfer(address _to, uint256 _value) public returns (bool) {
  require(_to != address(0));
  require(_value <=   balances[msg.sender]);
  balances[msg.sender] = balances[msg.sender].sub(_value);
  balances[_to] = balances[_to].add(_value);
  emit Transfer(msg.sender, _to, _value);
  return true;
}

We can add a single line to add transfer restrictions:

function transfer(address _to, uint256 _value) public returns (bool) {
  require(canTransfer(msg.sender, _to, _value), "Transfer is not allowed.");

  require(_to != address(0));
  require(_value <=   balances[msg.sender]);
  balances[msg.sender] = balances[msg.sender].sub(_value);
  balances[_to] = balances[_to].add(_value);
  emit Transfer(msg.sender, _to, _value);
  return true;
}

We defined a canTransfer() function that performs the authorization check of a transfer and require it to return successfully for the transfer to succeed. At minimum, the check would allow all transfers:

function canTransfer(address _from, address _to, uint256 _amount) public view returns(bool) {
  return true;
}

We could choose to disallow transfers beyond a certain volume:

function canTransfer(address _from, address _to, uint256 _amount) public view returns(bool) {
  if(_amount > 100)
    return false;
  else
    return true;
}

The logic within the canTransfer() function will depend on your specific use cases and securities regulations.

As of October 2018, there are a handful of distinct security token standards proposed and implemented by different parties. In this article we’ll look at each of these standards and see how each compares.

We will examine the following Ethereum security token standards:

ST-20

An ST-20 token is an Ethereum-based token implemented on top of the ERC-20 protocol that adds the ability for tokens to control transfers based on specific rules. ST-20 tokens rely on Transfer Managers to determine the ruleset the token should apply in order to allow or deny a transfer, be it between the issuer and investors, in a peer to peer exchange, or a transaction with an exchange.

Below is the IST20 token contract interface:

pragma solidity ^0.4.24;

import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol";

/**
 * @title Interface for the ST20 token standard
 */
contract IST20 is StandardToken, DetailedERC20 {

    // off-chain hash
    string public tokenDetails;

    //transfer, transferFrom must respect use respect the result of verifyTransfer
    function verifyTransfer(address _from, address _to, uint256 _amount) public returns (bool success);

    /**
     * @notice mints new tokens and assigns them to the target _investor.
     * Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet)
     */
    function mint(address _investor, uint256 _amount) public returns (bool success);

    /**
     * @notice Burn function used to burn the securityToken
     * @param _value No. of token that get burned
     */
    function burn(uint256 _value) public;

    event Minted(address indexed to, uint256 amount);
    event Burnt(address indexed _burner, uint256 _value);

}

Source: IST20.sol

In ST-20, the verifyTransfer() function calls external TransferManager contracts that contains business rules related to transfer restrictions. This approach makes it possible for developers to upgrade these rules over time.

function verifyTransfer(address _from, address _to, uint256 _amount) public view returns (bool success) {
  if (modules[TRANSFERMANAGER_KEY].length == 0) {
    return true;
  }
  for (uint8 i = 0; i < modules[TRANSFERMANAGER_KEY].length; i++) {
    if (ITransferManager(modules[TRANSFERMANAGER_KEY][i].moduleAddress).verifyTransfer(_from, _to, _amount)) {
      return true;
    }
  }
  return false;
}

In the above function, the token’s verifyTransfer() calls ITransferManager().verifyTransfer() to find out if a transfer is allowed or disallowed. This method will loop through all the transfer managers it contains and the transfer will be allowed if a transfer manager approves the transaction. Transfer managers implement the following interface:

/**
 * @title Interface to be implemented by all Transfer Manager modules
 */
contract ITransferManager {
    //If verifyTransfer returns:
    //  FORCE_VALID, the transaction will always be valid, regardless of other TM results
    //  INVALID, then the transfer should not be allowed regardless of other TM results
    //  VALID, then the transfer is valid for this TM
    //  NA, then the result from this TM is ignored
    enum Result {INVALID, NA, VALID, FORCE_VALID}

    function verifyTransfer(address _from, address _to, uint256 _amount, bool _isTransfer) public returns(Result);
}

TransferManager contracts such as Polymath’s GeneralTransferManager can maintain a whitelist of allowed addresses and any timelocks assigned to that address. This effectively allows transfer restrictions and token holding periods to be implemented at the token level:

struct TimeRestriction {
    uint256 fromTime;
    uint256 toTime;
    uint256 expiryTime;
    bool canBuyFromSTO;
}

mapping (address => TimeRestriction) public whitelist;

KYC/AML processes to verify these addresses are performed off-chain. Once the KYC passes, the results are set on the TransferManager’s on-chain whitelist to be enforced in both initial and secondary transfers. Only authorized staff addresses are allowed to modify this whitelist of addresses.

R-Token

R-Token is a permissioned token on the Ethereum blockchain, enabling token transfers to occur if and only if they are approved by an on-chain Regulator Service. The Regulator Service can be configured to meet relevant securities regulations, Know Your Customer (KYC) policies, Anti-Money Laundering (AML) requirements, tax laws, and more. Implemented with the correct configurations, the R-Token standard makes compliant transfers possible, both on exchanges and person to person, in ICOs and secondary trades, and across jurisdictions. R-Token enables ERC-20 tokens to be used for regulated securities.

Here is a reference RegulatedToken implementation:

contract RegulatedToken {
  /**
   * @notice Triggered when regulator checks pass or fail
   */
  event CheckStatus(uint8 reason, address indexed spender, address indexed from, address indexed to, uint256 value);

  /**
   * @notice Address of the `ServiceRegistry` that has the location of the
   *         `RegulatorService` contract responsible for checking trade
   *         permissions.
   */
  ServiceRegistry public registry;

  /**
   * @notice Constructor
   *
   * @param _registry Address of `ServiceRegistry` contract
   * @param _name Name of the token: See DetailedERC20
   * @param _symbol Symbol of the token: See DetailedERC20
   */
  function RegulatedToken(ServiceRegistry _registry, string _name, string _symbol) public
    DetailedERC20(_name, _symbol, RTOKEN_DECIMALS)
  {
    require(_registry != address(0));

    registry = _registry;
  }

  /**
   * @notice ERC-20 overridden function that include logic to check for trade validity.
   *
   * @param _to The address of the receiver
   * @param _value The number of tokens to transfer
   *
   * @return `true` if successful and `false` if unsuccessful
   */
  function transfer(address _to, uint256 _value) public returns (bool) {
    if (_check(msg.sender, _to, _value)) {
      return super.transfer(_to, _value);
    } else {
      return false;
    }
  }

  /**
   * @notice ERC-20 overridden function that include logic to check for trade validity.
   *
   * @param _from The address of the sender
   * @param _to The address of the receiver
   * @param _value The number of tokens to transfer
   *
   * @return `true` if successful and `false` if unsuccessful
   */
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    if (_check(_from, _to, _value)) {
      return super.transferFrom(_from, _to, _value);
    } else {
      return false;
    }
  }

  /**
   * @notice Performs the regulator check
   *
   * @dev This method raises a CheckStatus event indicating success or failure of the check
   *
   * @param _from The address of the sender
   * @param _to The address of the receiver
   * @param _value The number of tokens to transfer
   *
   * @return `true` if the check was successful and `false` if unsuccessful
   */
  function _check(address _from, address _to, uint256 _value) private returns (bool) {
    var reason = _service().check(this, msg.sender, _from, _to, _value);

    CheckStatus(reason, msg.sender, _from, _to, _value);

    return reason == 0;
  }

  /**
   * @notice Retreives the address of the `RegulatorService` that manages this token.
   *
   * @dev This function *MUST NOT* memoize the `RegulatorService` address.  This would
   *      break the ability to upgrade the `RegulatorService`.
   *
   * @return The `RegulatorService` that manages this token.
   */
  function _service() constant public returns (RegulatorService) {
    return RegulatorService(registry.service());
  }
}

Similar to the previous ST-20 standard, the R-Token standard has a _check(address _from, address _to, uint256 _value) function that is finds out if there are any transfer restrictions for the parties involved:

function transfer(address _to, uint256 _value) public returns (bool) {
  if (_check(msg.sender, _to, _value)) {
    return super.transfer(_to, _value);
  } else {
    return false;
  }
}

RegulatorService

The _check function calls an external RegulatorService.check() function in a separate regulator contract. This is similar to the ST-20 TransferManager contract:

function _check(address _from, address _to, uint256 _value) private returns (bool) {
  var reason = _service().check(this, msg.sender, _from, _to, _value);
  CheckStatus(reason, msg.sender, _from, _to, _value);
  return reason == 0;
}

function _service() constant public returns (RegulatorService) {
  return RegulatorService(registry.service());
}

Here is the RegulatorService contract interface:

contract RegulatorService {
  function check(address _token, address _spender, address _from, address _to, uint256 _amount) public returns (uint8);
}

Note that the RegulatorService.check() returns a uint8 status code instead of a bool.

The token stores a ServiceRegistry contract address in a public variable registry, and uses this registry contract to retrieve the regulator contract address.

ServiceRegistry

ServiceRegistry public registry;

function _service() constant public returns (RegulatorService) {
  return RegulatorService(registry.service());
}

To upgrade RegulatorService, you can deploy a new version and change the address stored in the ServiceRegistry.

The initial implementation of the Regulator Service relies on an off-chain Trade Controller to update its permissions as well as add newly approved participants.

The Trade Controller determines the status of the permissions. It maps participant data against relevant securities regulations, KYC/AML policies, and tax laws to determine whether each participant can send and/or receive the token. It also determines whether the token is locked/unlocked and whether partial trading is allowed/disallowed. These token-level permissions are then updated in the Regulator Service.

Only an approved Trade Controller with the correct private/public key pair can make changes to the Regulator Service.

ERC-1400

The ERC1400 standard is split into several modular sub-standards:

  • ERC1410 which defines partially fungible tokens where balances of tokens can have an associated metadata.
  • ERC1594 which defines transfer restrictions and core security token functionality.
  • ERC1643 which splits out document management functionality.
  • ERC1644 which splits out controller operation functionality.

Taken together, the ERC1400 security token standards provide document management, error signalling, transfer restrictions, forced transfers, off-chain data injection, issuance / redemption semantics and expose partially fungible subsets of a token holders balance.

interface IERC1594 is IERC20 {

    // Transfers
    function transferWithData(address _to, uint256 _value, bytes _data) external;
    function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external;

    // Token Issuance
    function isIssuable() external view returns (bool);
    function issue(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;

    // Transfer Validity
    function canTransfer(address _to, uint256 _value, bytes _data) external view returns (bool, byte, bytes32);
    function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (bool, byte, bytes32);

    // 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);

}

The ERC-1400 standard defines the following notable features:

  • Stores legal documents on-chain for reference.
  • Most of its token transfer functions accepts a bytes _data parameter which allows off-chain data to be entered to pass authorization. Functions also returns a sucess bool, reason byte code with information about the success or failure of the transaction, and a bytes32 application-level response code.
  • Unlike previous standards where the validator functions are only available internally, ERC-1400 exposes a standard interface to query if a transfer would be successful and return a reason for failure (with canTransfer().)
  • Partially fungible token: able to attach metadata to a subset of a token holder’s balance such as special shareholder rights or data for transfer restrictions.
  • It defines forced transfers via ERC1644’s controllerTransfer and controllerRedeem to allow issuers to reclaim tokens from token holders for legal action or fund recovery.
  • The standard uses EIP-1066 compliant application-specific status codes.

ERC-1404

Token issuers need a way to restrict transfers of ERC-20 tokens to be compliant with securities laws and other contractual obligations.

ERC-1404 Simple Restricted Token Standard is a simple and interoperable standard for issuing tokens with transfer restrictions. The following draws on input from top issuers, law firms, relevant US regulatory bodies, and exchanges.

contract ERC1404 is ERC20 {
  function detectTransferRestriction (address from, address to, uint256 value) public view returns (uint8);
  function messageForTransferRestriction (uint8 restrictionCode) public view returns (string);
}

Similar to other standards, ERC-1404 has a detectTransferRestriction() function that returns a uint status code such as ALLOWED and DISALLOWED. This function is called at the start of token movement functions such as transfer().

It also has a messageForTransferRestriction() function which returns details about the uint status code.

ERC-1462

ERC-1462 is an extension to ERC-20 standard token that provides compliance with securities regulations and legal enforceability.

interface IERC1462 /* is ERC-20 */ {
    // Checking functions
    function checkTransferAllowed (address from, address to, uint256 value) public view returns (byte);
    function checkTransferFromAllowed (address from, address to, uint256 value) public view returns (byte);
    function checkMintAllowed (address to, uint256 value) public view returns (byte);
    function checkBurnAllowed (address from, uint256 value) public view returns (byte);

    // Documentation functions
    function attachDocument(bytes32 _name, string _uri, bytes32 _contentHash) external;
    function lookupDocument(bytes32 _name) external view returns (string, bytes32);
}

Similar to other standards, ERC-1462 defines:

  • Transfer restriction functions such as checkTransferAllowed()
  • Legal documentation functions such as attachDocument()

The four new check*Allowed() functions are used to check that the actions are allowed for the provided inputs. The implementation details of each function are left for the token issuer, it is the issuer’s responsibility to add all necessary checks that will validate an operation in accordance with KYC/AML policies and legal requirements set for a specific token asset.

Each function returns a status code from the common set of Ethereum status codes (ESC), according to ERC-1066.

Bonus: Transaction Permission Layer (TPL)

As we’ve seen in previous standards, aside from the ability to moderate transfers of securities using either an on-chain codified rule set or off-chain approvals, we also want to associate some kind of public data to the addresses involved in the transaction.

However, the identity aspects of transfer restrictions are often outside the scope of a security token standard. This is because there are various identity standards (e.g. ERC725, 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). One such identity standard is TPL.

The Transaction Permission Layer protocol (TPL) is a method for assigning metadata (herein referred to as “attributes”) to Ethereum addresses. These attributes then form the basis for designing systems that enforce permissions when performing certain transactions. Security token transfers are one such transaction, where compliance with various laws and regulations will compel tokens to only permit a transfer once a set of conditions (e.g. identity verification, KYC/AML, or other attributes) have been met.

TPL allows for the creation of digital “jurisdictions” with requirements that are administered and regulated on-chain by one or more projects. Within these jurisdictions, third-party organizations operate as “validators” which identify whether a proposed token transfer is compliant with the requirements of the jurisdiction. Projects can tailor the TPL to limit transfers of their tokens on an ongoing basis, using any criteria that can be recorded in a registry by a validator.

At the core of TPL is the jurisdiction — a single smart contract that links attributes to addresses. It implements an AttributeRegistry interface, where attributes are registered to addresses as a uint256 => uint256 key-value pair, defined as follows:

Permissioned tokens and other contracts can then use this interface to identify and confirm attributes recognized by a jurisdiction without needing to take on the additional technical overhead of managing attribute assignment and revocation themselves.

To sum up, TPL allows contracts such as security tokens to verify claims from independent Cerificate Validators. While not a security token standard, TPL offers a key building block to implement decentralized address verifications for transfer restrictions.

Common Security Token Features

To summarize, here are some features that are common across the different security tokens we’ve examined.

KYC / Whitelisting

In the regulated world, almost every investor needs to be qualified with KYC/AML before you send them a security. All of the security token standards that described here can do whitelisting, which means that they keep a list of blockchain addresses that represent investors that are qualified to receive a security. Then, they prevent the transfer of the token to anyone that is not on the list.

Transfer Rules

If you are selling a private security, you will need to enforce transfer rules so that you send only to qualified investors, according to the different rules of various jurisdictions. You probably also want to enforce lockups, such as the one year lockup for private securities sold in the US. You may have other rules that are typically part of a “subscription agreement” signed by a shareholder.

Upgradability

During the multi-year life of a security, the rules change. The sets of qualified investors change. You may want to provide new features for compatibility with emerging exchanges. You may want to fix your implementation. It’s important to be able to update the source code of an existing token to adapt with evolving requirements.

Decentralized Identity Registries

Some standards use identity contracts that hold investor information and permissions for purposes of enforcing transfer restrictions. This aspect is usually handled by a separate standard such as ERC725 or ERC1484.

In Summary

In this article, we learned about security tokens. Security tokens has requirements that are fundamentally different from the requirements of the typical cryptocurrency.

We’ve also looked at several security token standards. The open source ecosystem is full of activity, with new standards being published by many different organizations. More than a handful of teams are working towards the goal of regulated security tokens and tokenized assets.

Standards adoption amongst token issuers has the potential to evolve into a dynamic and interoperable landscape of automated compliance. Be sure to keep an eye on this space for new developments.