How to Implement XRC1400 in XDC Network?
Blockchain technology is one of the promising technologies that has proven its potential in not only the crypto world but also in the trade finance sector. It has opened up new opportunities in the financial services marketplace.
If we look at the data shared by Statista, the market size of blockchain solutions for banking and financial firms was USD 0.28 billion in 2018. The advancing developments are forecasted to reach a market size of USD 22.5 billion in 2026.
A considerable share indeed! Not to mention the recent advancement in the trade finance sector that has marked the beginning of an entirely new era.
To be honest, I feel delighted to share how the technology is progressing and what the technology has in store for us.
In my previous blogs, I have given a brief overview of the critical concepts of blockchain technology — tokens, asset tokenization, smart contracts, P2P lending, etc. Adding one more to the list is STO — security token offering.
Let’s explore.
What is Security Token Offering?
A security token offering is a public offering where security tokens are sold in the security token exchanges. These tokens can be used to trade real financial assets like equities, profit shares, dividends, voting, buy-back rights, etc.
For your information, when a token gets its value from any external, tradable asset and is subjected to federal security regulations, it is referred to as a security token. Thus, it represents assets with the rights of ownership.
There has been an increasing demand for the regulatory frameworks associated with token issuance. Therefore, it is advisable for the ICO launchers to follow legal regulations that give investors more credibility and security.
The process looks a bit complicated but it comes with innumerous benefits. For example, one can easily increase the investors’ engagement by tokenizing securities because it will improve the liquidity of underlying assets. Other benefits include enhanced market efficiency, reduced issuance fees, fractional ownership of larger assets, etc.
Let’s dive deeper into the implementation details.
XRC1400: Implementing STO in XDC Network
Recently we have been working on a few client projects to tokenize financial assets as ‘Security Token Offering(s)’. As defined above, Security Tokens represent ownership underlying ‘assets,’ e.g. equities, debt, real estate. And, they require ownership models and controls to be defined in the smart contracts governing the security tokens.
Some of the basic requirements of the project include -
a) Secured Issuance, Redemption, and Transfer.
b) Capability of restricting usage to a potential investor(s).
c) Partitioning/Tranching of tokens based on business rules.
d) Document support to provide token-related information and other requisite documents.
e) Capability of integration with crypto-wallets for trading i.e., following standard Token Standards.
We had to use the XRC1400 standard in our code. This XRC-1400 is an umbrella standard consisting of 4 different standards:
- XRC 1594 (A controlled XRC20 token with ‘on-chain’ checking and off-chain data injection.
- XRC1643 — Document support
- XRC1410 — Differentiated (partitioned/tranched) ownership and transfer restrictions
- XRC1644 — Controlling forced transfer.
The article further details how to create and implement an XRC1400 (XRC1594 and XRC1643) token, with code samples for the implementation of core requirements, as mentioned above.
Implementation of XRC1410 and XRC1644 functionalities will be separately published in my upcoming blogs.
XRC 1594
An XRC 1594 token can be implemented using enhancements to a basic XRC20 token (as the standard enforces compatibility to XRC20); the below functions are implemented –
function totalSupply() external view returns (uint256);function balanceOf(address who) external view returns (uint256);function allowance(address owner, address spender) external view returns (uint256);function transfer(address to, uint256 value) external returns (bool);function approve(address spender, uint256 value) external returns (bool);function transferFrom(address from, address to, uint256 value) external returns (bool);
You can also read the article on How to create an XRC 20 token in Simple Way? to enhance your knowledge and understanding.
Additionally, as Security Tokens need to control investors with proper KYC, appropriate whitelisting functions are added to the base XRC20 token.
Certain restrictions also need to be incorporated in the base XRC20 token, e.g., ownable, pausable, etc.
A standard ownable implementation includes -
constructor() public {_owner = msg.sender;}function owner() public view returns(address) {return _owner;}modifier onlyOwner() {require(isOwner());_;}function renounceOwnership() public onlyOwner {emit OwnershipRenounced(_owner);_owner = address(0);}function _transferOwnership(address newOwner) internal {require(newOwner != address(0));emit OwnershipTransferred(_owner, newOwner);_owner = newOwner;}
A whitelist is kept on-chain in a structure as follows -
mapping (address => bool) private whitelists;
The functions to incorporate whitelists are -
// whitelist an addressfunction permit(address _whitelist) external onlyOwner {whitelists[_whitelist] = true;}//whitelist a batch of addressfunction permitBatch(address[] calldata _whitelistBatch) external onlyOwner {for (uint256 i = 0; i < _whitelistBatch.length; ++i) {address _whitelist = _whitelistBatch[i];whitelists[_whitelist] = true;}}// revoke whitelistingfunction revoke(address _whitelist) external onlyOwner {whitelists[_whitelist] = false;}
The next step is the implementation of XRC1594 which is extended from the base XRC20 token above.
The extended transfer functions include -
function transferWithData(address _to, uint256 _value, bytes calldata _data) external {// Add a function to validate the `_data` parameter_transfer(msg.sender, _to, _value);}
// Operator transfer
function transferFromWithData(address _from, address _to, uint256 _value, bytes calldata _data) external {// Add a function to validate the `_data` parameter_transferFrom(msg.sender, _from, _to, _value);}
Now, the extra ‘data’ element in the above functions can be used on specific business use cases, and additional validation/processing can be implemented based on the same.
Pre-Transfer checks
Similar to a balanceof() check for XRC20, as a pre-transfer check for validating a transfer, will succeed/fail, XRC1594 introduces two additional functions which return success/failure as a pre-transfer check, including a ‘business error code’ for different failure conditions. The implementation of the same can be as below -
function canTransfer(address _to, uint256 _value, bytes calldata _data) external view returns (bool, byte, bytes32) {// Add a function to validate the `_data` parameterif (!isOwner())return (false, 0x60,bytes32(0));else if (_balances[msg.sender] < _value)return (false, 0x52, bytes32(0));else if (_to == address(0))return (false, 0x57, bytes32(0));else if (!KindMath.checkAdd(_balances[_to], _value))return (false, 0x50, bytes32(0));return (true, 0x51, bytes32(0));}//Operator transfer pre-check
function canTransferFrom(address _from, address _to, uint256 _value, bytes calldata _data) external view returns (bool, byte, bytes32) {// Add a function to validate the `_data` parameterif (!isOwner())return (false, 0x60,bytes32(0));else if (_value > _allowed[_from][msg.sender])return (false, 0x53, bytes32(0));else if (_balances[_from] < _value)return (false, 0x52, bytes32(0));else if (_to == address(0))return (false, 0x57, bytes32(0));else if (!KindMath.checkAdd(_balances[_to], _value))return (false, 0x50, bytes32(0));return (true, 0x51, bytes32(0));}
Similar to the ‘transfer functions, the canTransferFrom functions also have a ‘data’ element and are used similarly.
Issuance and Redemption
Issuance and Redemption in XRC1594iuses base XRC20 mint and burn functions and implemented as follows -
function issue(address _tokenHolder, uint256 _value, bytes calldata _data) external onlyOwner {// Add a function to validate the `_data` parameterrequire(issuance, "Issuance is closed");_mint(_tokenHolder, _value);emit Issued(msg.sender, _tokenHolder, _value, _data);}
function redeem(uint256 _value, bytes calldata _data) external onlyOwner {// Add a function to validate the `_data` parameter_burn(msg.sender, _value);emit Redeemed(address(0), msg.sender, _value, _data);}//Operator Redemption
function redeemFrom(address _tokenHolder, uint256 _value, bytes calldata _data) external onlyOwner {// Add a function to validate the `_data` parameter_burnFrom(_tokenHolder, _value);emit Redeemed(msg.sender, _tokenHolder, _value, _data);}
Issuance Restriction
Also, issuance is controlled by a flag ‘issuable’, which once set to false, further issuance of the token is restricted.
function finalizeIssuance() external onlyOwner {require(issuance, “Issuance already closed”);issuance = false;emit IssuanceFinalized();}
XRC 1643
One of the basic requirements in any STO is having on-chain document references such as prospectus, terms and conditions of offerings, investor KYC documents, etc.
XRC1643 as a component of XRC1400 provides the functionalities of the same.
Documents are stored in a structure as below -
struct Document {bytes32 docHash; // Hash of the documentuint256 lastModified; // Timestamp at which document details was last modifiedstring uri; // URI of the document that exist off-chain}// mapping to store the documents details in the documentmapping(bytes32 => Document) internal _documents;// mapping to store the document name indexesmapping(bytes32 => uint256) internal _docIndexes;// Array use to store all the document name present in the contractsbytes32[] _docNames;
Documents are identified/indexed by their names. The document hash is for verification so as to prove provenance and also ensures a document is not tampered with.
The main document functions are implemented as below –
function setDocument(bytes32 _name, string calldata _uri, bytes32 _documentHash) external onlyOwner {require(_name != bytes32(0), "Zero value is not allowed");require(bytes(_uri).length > 0, "Should not be a empty uri");if (_documents[_name].lastModified == uint256(0)) {_docNames.push(_name);_docIndexes[_name] = _docNames.length;}_documents[_name] = Document(_documentHash, now, _uri);emit DocumentUpdated(_name, _uri, _documentHash);}
function getDocument(bytes32 _name) external view returns (string memory, bytes32, uint256) {return (_documents[_name].uri,_documents[_name].docHash,_documents[_name].lastModified);}function getAllDocuments() external view returns (bytes32[] memory) {return _docNames;}function removeDocument(bytes32 _name) external onlyOwner {require(_documents[_name].lastModified != uint256(0), "Document should be existed");uint256 index = _docIndexes[_name] - 1;if (index != _docNames.length - 1) {_docNames[index] = _docNames[_docNames.length - 1];_docIndexes[_docNames[index]] = index + 1;}_docNames.length--;emit DocumentRemoved(_name, _documents[_name].uri, _documents[_name].docHash);delete _documents[_name];}
Wrapping Up
The basic implementation of XRC 1400 and the use case have been in the production stage with a release of a few features. Although it is limited in scope, it meets all the business requirements for the STO.
Hope this article suffices you with a basic understanding of the implementation of XRC1400 on the XDC Network.
My next article will be on XRC721, which is a non-fungible token standard to be implemented while developing applications on the Xinfin blockchain platform. Stay tuned!