Tokenization is the process of bringing ownership of tangible and intangible “real-world assets” (RWAs) onto a blockchain. In this context, assets are represented digitally, making them accessible, exchangeable, and manageable on distributed, often decentralized, platforms. The logic is typically achieved through a program running on the blockchain, commonly known as a smart contract.
In Ethereum, smart contracts implementing the ERC-20 standard, with some specific functionalities, are often used to enable this digital representation. Examples of related standards include ERC-3643, ERC-1400, and CMTAT, which all have extended functionalities.
The ERC-20 standard includes two main functions for transferring tokens:
-
transfer(): Transfers tokens from the caller's account to another address.
-
transferFrom(): A smart contract transfers tokens from one specified address to another.
By default, ERC-20 does not impose restrictions on who can hold tokens.
When tokenizing securities or debt, issuers may want to enforce restrictions on token ownership and transfer. For instance, they might want to:
-
Allow only addresses on a whitelist to hold tokens.
-
Forbid addresses on a sanction list from holding tokens.
This article explains how to implement such restrictions on a smart contract used for tokenization or on other cases requiring a fungible token (e.g., stablecoins).
We tested our setup using Taurus-CAPITAL, a platform for deploying and managing smart contracts, and Taurus-PROTECT, for custody. We ran the tests on an Ethereum testnet (Sepolia).
By following this guide, you will learn how to effectively implement and manage token restrictions on the blockchain.
Components
CMTAT
The CMTAT is an open standard suitable for the tokenization of various financial instruments and used by multiple institutional actors, including large Swiss banks. The standard is an extension of the well-known ERC-20 standard. We used the reference Solidity implementation of CMTAT (v2.4.0). You will find more information in another article on our blog: Security Token Standards: A Closer Look at CMTAT.
To stay informed about updates, you can “star” the project and follow the next commits that modify its code. The project is under the license Mozilla Public License Version 2.0, which is a permissive license, in contrast to, for example, the GPL 3.0 license.
RuleEngine
The RuleEngine is an external contract used to apply transfer restrictions to the CMTAT. Acting as a controller, it can call different contract rules and apply these rules on each transfer.
Why use a dedicated contract with rules instead of implementing it directly in the CMTAT?
-
Flexibility: These different features are not standard and common to all tokens. From an implementation perspective, using a rule engine with custom rules allows for each issuer or contract user to decide which rules to apply.
-
Code efficiency: The CMTAT token is currently "heavy," meaning its contract code size is close to the maximum limit, especially when the snapshot module is included. This makes it challenging to add new features directly inside the CMTAT contract.
-
Reusability: We can use the RuleEngine inside other contracts besides CMTAT. For instance, we have used it in our contract to distribute dividends.
As for CMTAT, this smart contract is also under the license Mozilla Public License Version 2.0. We used the version v2.0.2.
ERC-1404
The ERC-1404 standard is built on top of the ERC-20 interface and adds two main functionalities:
-
detectTransferRestriction(): Identifies if there are any restrictions on a token transfer by returning a specific code.
-
messageForTransferRestriction(): Provides a message explaining the reason for any detected transfer restrictions based on the code returned by the first function.
This standard is, for example, used by Tokensoft.
From ERC-1404: Simple restricted token standard
For more information, you can visit the project website or the issue on GitHub.
Even though it is called ERC-1404, this standard has never reached the status of an official ERC. We have used it because the defined functions are very useful.
This standard is used in CMTAT, which offers the two public functions messageForTransferRestriction() and detectTransferRestriction(). If the RuleEngine is set, CMTAT will call the corresponding functions from the RuleEngine.
CMTAT v2.4.0
CMTAT v2.4.0
RuleEngine deep dive
This section describes in more detail how our RuleEngine is implemented and how it works.
Rules
There are two types of rules in our RuleEngine implementation: validation and operation rules.
Validation rule
This first type includes read-only rules. These rules cannot update the state of the blockchain during a transfer call; they can only read information from the blockchain.
Currently, there are four validation rules: whitelist, whitelistWrapper, blacklist, and sanctionlist.
Whitelist
With a whitelist rule, only whitelisted addresses can hold the token. Thus, a transfer will fail if either the origin (current token holder) or the destination is not in the list.
During a transfer, this rule, called by the RuleEngine, will check if the address concerned is in the list, applying a read operation on the blockchain.
Whitelist wrapper
This rule can be used to restrict transfers only from/to addresses inside a group of whitelist rules managed by different operators. Thus, each operator can manage independently their own whitelist. Here is an explanation schema:
Blacklist
The blacklist rule works similarly to the whitelist rule but in the opposite way. With this rule, the addresses must not be on the list to allow the transfer to succeed.
Sanction list with Chainalysis
The purpose of this contract is to use the oracle contract from Chainalysis to forbid transfers from/to an address included in a sanctions designation (US, EU, or UN). Documentation and the contracts addresses are available here: Chainalysis oracle for sanctions screening.
During a transfer, if either address (from or to) is in the sanction list of the Oracle, the rule will return false, and the transfer will be rejected by the CMTAT.
Operation rule
The second type of rules, known as operation rules, can also modify the state of the blockchain (write) during a transfer call. Currently, we only have one operation rule: ConditionalTransfer. This rule requires that transfers must be approved before being executed by the token holders.
During the transfer call, the rule will check if the transfer has been approved. If it has, the approval will be removed since the transfer has been processed, applying a write operation on the blockchain.
For the moment, there is only one operation rule available: ConditionalTransfer.
Conditional transfer
This rule requires that transfers be approved by the token holders before being executed.
Initially, this rule was designed to implement a specific requirement in Swiss law (Vinkulierung), but it has since been generalized to be more flexible.
According to Swiss law, if a transfer is not approved or denied within three months, the request is considered approved. This option can be activated by setting the option AUTOMATIC_APPROVAL in the rule.
We have added another option, not required by Swiss law, to automatically perform a transfer if the transfer request is approved. This option can be activated by setting the option AUTOMATIC_TRANSFER in the rule.
How it works
This diagram illustrates how a transfer of CMTAT with a RuleEngine works:
-
The token holders initiate a transfer transaction on CMTAT contract.
-
The validation module inside the CMTAT calls the function operateOnTransfer from the RuleEngine if set with the following parameters inside: from, to, amount.
-
The Rule Engine calls each rule separately.
-
If the rule returns the value 0, the RuleEngine considers the transfer authorized. If one rule returns a different value, the RuleEngine considers the transfer as not authorized and returns false to the CMTAT.
-
The validation module inside CMTAT checks the returned value. If it is true, the rest of the function is performed. If the value is false, the transaction is rejected.
How to include it
While the RuleEngine has been designed for the CMTAT, it can be used with other contracts to apply transfer restrictions.
For that, the only thing to do is to import in your contract the interface IRuleEngine, which declares the function operateOnTransfer. This interface can be found here.
Before each transfer, your contract must call the function operateOnTransfer(), which is the entry point for the RuleEngine.
For example, CMTAT defines the interaction with the RuleEngine inside a specific module, ValidationModuleInternal.
CMTAT v2.4.0 - ValidationModuleInternal
This function _operateOnTransfer() is called before each transfer/burn/mint through the internal function _update() defined in CMTAT_BASE.
CMTAT 2.4.0 - CMTAT_BASE
Proof of concept execution
In our proof of concept (PoC), we will perform the following tasks:
-
Deploy CMTA
-
Deploy RuleEngine
-
Set the RuleEngine in CMTAT
-
Add the rule whitelist
-
Add the different authorized addresses to the whitelist
The tests were conducted on the Sepolia Ethereum testnet.
CMTAT
We have deployed our CMTAT contract at the address 0x80a14113d3092f896bcc1756ff0d7a90513476a8. This is the same contract address used for our previous article, Equity Tokenization: How to Pay Dividend On-Chain Using CMTAT.
RuleEngine deployment
As argument, the admin is the EOA 0x3f77cf5501540484410c0bce488f223feaeba800.
The token contract is our CMTAT, previously deployed at 0x80a14113d3092f896bcc1756ff0d7a90513476a8
We set the zero address in the forwarder field since we are not using our gasless system for this PoC.
We deployed the rule Engine at the address 0xe8f4a5cb5cd8b53c862685e6fcd00551938c8feb.
Taurus-CAPITAL
Whitelist deployment
We deployed our whitelist contract at the address 0x02c974e084df5d1eabeb75186fd66e57841d5ef8.
Taurus-CAPITAL
Configuration
CMTAT
We will now set the rule Engine by calling the function setRuleEngine() defined in the Validation module.
CMTAT v2.4.0 - ValidationModule
Taurus-CAPITAL
See the transaction on Etherscan.
RuleEngine
We add our rule Whitelist to the list of rules. Since it is a validation rule, we call the function addRuleValidation().
RuleEngine v2.0.2
Taurus-CAPITAL
See the transaction on Etherscan.
Whitelist
We will add several addresses to the whitelist by calling the function addAddressesToTheList().
RuleEngine v2.0.2
Taurus-CAPITAL
See the transaction on Etherscan.
Transfer validation
Before RuleEngine
Before setting the RuleEngine, we call the function validateTransfer() from CMTAT to see the difference when no ruleEngine is set.
In this case, since there are no restrictions, the function returns true, indicating that the transfer is valid.
Taurus-CAPITAL
If we call the function detectTransferRestriction() from ERC-1404, it returns the code 0.
Taurus-CAPITAL
The message associated with this code can be retrieved by calling the function messageForTransferRestriction() from ERC-1404.
Taurus-CAPITAL
AfterRuleEngine
Invalid transfer
After setting the RuleEngine, we can call again the function detectTransferRestriction().
In this case, the from address is in the whitelist, but not the to address.
As a result, we receive the restriction code 10.
Taurus-CAPITAL
Again, we call the function messageForTransferRestriction() from ERC-1404 to know the associated message.
The message is clear: “The recipient is not in the whitelist”.
Taurus-CAPITAL
Valid transfer
We will now perform a second test with both the origin and recipient in the whitelist. By calling the function detectTransferRestriction, we can see the returned code is 0, indicating a valid transfer.
Taurus-CAPITAL
We will now try to perform the transfer.
Taurus-CAPITAL
Since the two addresses are whitelisted, the transfer is a success. You can see the transaction on Etherscan.
Etherscan
We don’t try to perform an invalid transfer since the request will be directly rejected by our system, without being transmitted to the blockchain. This protection is put in place to avoid paying unnecessary transaction fees on transactions that will be invalidated by the blockchain.
Conclusion
The RuleEngine provides a flexible and comprehensive option to add rules to transfers as needed by the issuer.
If the available rules do not correspond exactly to the need, it is entirely possible to create new rules tailored to those needs.
The ERC-1404 integration provides a simple and convenient way to determine if any restrictions are present before completing the transaction.
Finally, if the issuer prefers to offer transfer flexibility to holders, they can use the CMTAT without configuring a RuleEngine. This flexibility is a significant difference compared to other tokenization standards, like ERC-3643, which enforces identity management as a core component of the standard.