T-REX : Token for Regulated EXchanges
The following document describes the technical details of the T-REX standard.
The T-REX standard was developed to provide a fully compliant environment for the issuance and use of tokenized securities.
The project is fully described in the T-REX White Paper available here
Table of contents
Abstract
Following the emergence of Bitcoin and other so-called crypto-currencies, the last two years have seen through a wave of ICOs (Initial Coins Offerings), leveraging on the DLT technology underpinning most cryptocurrencies to support the issuance of other types of instruments. This wave has seen mainly the issuance of utility tokens in a completely unregulated environment. More recently, we have seen a new type of tokens emerging in the form of security (or investment) tokens which, in essence - and a number of regulators have started to confirm that - should be assimilated to securities i.e. equivalents to traditional securities but which are issued, maintained and transferred on a DLT infrastructure. One of the most important features that security tokens bear is, contrary to utility tokens, the fact that existing securities laws and practices should be considered as applying to them and, among others, all requirements in terms of KYC and AML regulations which, essentially, aim at controlling who holds a security and transacts in it in order to detect and prevent money-laundering, terrorism financing and other illegal or fraudulent activities.
The main goal of the T-REX standard is to create a set of global tools, fully based on blockchain technologies, to allow frictionless and compliant issuance and use of tokenized securities on a peer to peer basis or through marketplaces but in full compliance with regulations and issuers requirements, by embedding controls mechanisms in the tokens themselves. With T-REX, we are implementing a “Compliance by Design” approach where it is simply impossible for an investor to buy a security without being compliant. The regulator itself can verify the compliance of the Issuer through the auditing of the smart contracts that support the Security Token life cycle.
The management of compliant transactions through T-REX backed permission tokens will be based on 3 main pillars creating a decentralized Validator:
- A blockchain based identity management system, allowing the creation of a globally accessible identity for every stakeholder;
- A set of claims, as described in the ERC-734 and ERC-735 standards.
- A transfer manager whose role is to act as a filter of all the transactions of tokenized securities and which will check the claims of the stakeholders, essentially it will check that the receiver has the rights to receive the tokens following the specific compliance rules and issuer requirements applicable for this specific asset. The transfer manager will block the transaction if the receiver misses a mandatory claim and will notify him about the reason of the failure.
These 3 key elements allow issuers to use a decentralized Validator to control transfers and enforce compliance on the holders of the security token he has issued. The Validator includes rules for the whole offering (e.g. managing the max number of holders allowed in a specific markets, when such rule apply), and rules for each investors (e.g. KYC or issuer-defined eligibility criteria) thanks to the identity management system.
Motivation
Constraints for Tokenized Securities
Although, so far, the rules applicable to issuing and holding utility tokens were largely undefined - or at least very vague - in most countries, an STO consists in the issuance of a security that uses the blockchain technology as its registry, proof of ownership and transfer infrastructure. Such instrument is regulated in every country and, as a consequence, STOs have to comply with the related regulations of the country where the security token is issued as well as those of the countries where it is distributed (sold).
Characteristics | Utility Token | Security Token |
---|---|---|
Purpose | Usage | Investment |
Regulation | Non existing or vague in most cases | Stringent as existing securities laws should be taken as reference |
Lifecycle | Simple | As complex as a security |
Secondary Market | Nearly no constraints | As complex as a security |
Another significant difference between ICOs and STOs is related to the token lifecycle. ICOs - dealing with utility tokens - result in the issuance of tokens having a relatively simple life cycle: once the token is shared among a decentralized network, its governance is mostly the results of its token economics. As to security tokens, it is quite different, as the issuer - or its appointed agent - remains generally liable for applying a number of controls to his token after issuance and during the entire “life” of its security token. In addition, he might need to apply a number of corporate actions (dividend/interests payments, … ) or corporate events (calling for an AGM/EGM, …) to its token which further increase the need for the issuer to keep in touch with (keep some control on) the investors in his token.
One could identify two main types of control requirements related to the issuance, the holding and the transfer of security tokens :
- One relates to regulations applicable to the security considered, that are independent of the security token itself (i.e. general rules). For example, the need to identify the investor, to collect a proof of his identity, to check his name against blacklists, i.e. generally speaking, control requirements related to AML/KYC, or other applicable regulatory rules.
- Then some controls might be related specifically to the security that is issued, for example, restrictions about the investor type and location or about the amount of money that can be invested on a certain period. These might be linked to the regulatory environment under which the issuer has decided to issue his token or simply linked to eligibility criteria defined by the issuer for instance, for commercial reasons (e.g. restricting the access of a certain share class, having specific fees characteristics, to investors of a specific country).
Addressing these different control requirements will require a high level of reusability and flexibility when designing the token. This is the reason why we have designed the T-REX standard. It provides a set of generic tools helping token issuers to apply and manage the necessary controls and permissions to security tokens through a flexible decentralized validation system (the transfer manager), allowing them to add all the rules that need to be addressed to approve holding and transacting in their tokens.
Necessity of Permissioned Tokens
In our opinion, only permissioned tokens are suitable to issue security tokens because there cannot be a total, uncontrolled, freedom of the transaction in such instruments and, investors need to comply with a number of criteria - either by regulation or imposed by the issuer himself in order to be eligible for holding the tokens. The main technical difference between standard ERC-20 tokens and T-REX permissioned tokens resides in the transfer function of T-REX tokens being made conditional, the condition for a transaction to be executed being that the transfer manager approves it according to the governance criteria defined for the the token in question. However, despite this modification of the transfer function of the token, it is to be highlighted that, because the token structure is based on the ERC-20 standard, it remains fully compatible with with it and all the available exchanges and tools based on ERC-20 tokens.
Most of the “Security token protocols” promoted in the industry so far are permissioned tokens. The transfer function is modified and requests a transfer approval from an external validator service to control the transfer of tokens. T-REX involves an on-chain identity management system allowing issuers to control the transfer of ownership directly on-chain.
On-chain identity management
As mentioned before, by essence, a security token being subject to a stringent governance, its distribution has to follow all the applicable regulations and, in particular, those aspects related to KYC rules. In that respect, we believe that identity management is key to implement such compliance on the blockchain.
As the ownership of a security token is registered on the blockchain, we believe it is necessary to have a way to track the token ownership and to prohibit illicit transactions directly on the blockchain. This is why there is a need need to make a link between wallet addresses and identities and to manage rights through an identity contract directly on the blockchain. In addition, we also need to ensure privacy of those identities in order to comply with personal data related regulation. For this reason, personal data should not be stored directly on the blockchain but only the validation certificates (claims) issued by trusted third parties (KYC provider, government, lawyer,…) having checked these data. Those certificates (claims), stored in the identities of parties to a transaction will be used by the transfer manager to validate whether those parties are hold and transact a specific security token, or not.
Linking an investor’s wallet and to his identity can bring significant added value to stakeholders in the nascent security tokens market. For example, it will allow a token issuer to replace the tokens of an investor if the investor loses access to his wallet (which happens pretty often and generally results in the loss of the owner’s assets ), by verifying that his on-chain identity fits with off-chain data linked to the identity contract corresponding to the lost wallet. After the identity of the investor is confirmed, the issuer can burn the lost tokens and mint new tokens on the new wallet of the investor.
Also, on-chain identities and the certificates (claims) they store can potentially be re-used for passing KYC’s for other security tokens than the one for which those claims were originally provided or even for other purposes than investments (e.g. account opening at an exchange, identification with compatible web services, …). If Google and Facebook accounts are the identities of most people on the internet of information, on-chain identities can be the ones of the internet of value. They are really owned and controlled by their owner.
Definitions
-
claim
: For more details aboutclaims
andclaim
related issues (claim topic
,claim issuer
, ...), take a look at ERC-735 -
keys
: For more details aboutkeys
andkeys
related issues, take a look at ERC-734 -
Identity Contract
: This is a smart contract deployed by a user to record and manage his identity on blockchain. In the context of T-REX, investor’s (but also the issuer’s and issuer’s provider’s) identities are used to interact with the security token (obviously, this onchain identity is also to be used for other activities where the identification of the identity holder might be relevant) . The identity holdskeys
andclaims
. The identity contract is based on the ERC-734 and ERC-735 standards and it includes all the necessary functions to managekeys
andclaims
related to that specific identity. TheIdentity Contract
is not linked to a specific token and it only needs to be deployed once by each user. It can then be used for whatever purpose where the use of an onchain identity might be relevant). -
Identity Registry
: This smart contract stores the identity addresses of all the authorized investors in the issuer’s security token i.e. all identities of investors who have been authorized to hold and transact in the token after having gone through the appropriate KYC and eligibility checks. It is actually a dynamic whitelist of identities for a specific token. It also contains a function calledisVerified()
which returns a status based on the validity ofclaims
(as per the security token requirements) in the user’sIdentity Contract
. TheIdentity Registry
is managed by the issuer (or his agent) i.e. only the issuer (or his agent) can add or remove identities in the registry (note: this is the basic configuration but can be customized depending on the requirements of the token issuer in terms of administering his token). There is a specificIdentity Registry
for each security token. -
Claim Verifier
: Is a smart contract that verifies theclaims
attached to an investor’sIdentity Contract
for the issued security token. For example, if a security token requires thatclaims
are signed by a specificclaim issuer
(e.g. the entity responsible for KYC checks for this specific token) and that the investor identity should contain the KYCclaim topic
(user has undergone successful KYC verification for the token considered), then theClaim Verifier
simply checks whether the investor’sIdentity Contract
has aclaim
signed by the relevantclaim issuer
and it contains the KYCclaim topic
needed. Based on the results, the contract returns atrue
orfalse
value. When theIdentity Registry
makes a call to itsisVerified()
function it makes calls to theclaimIsValid()
function from theClaim Verifier
for eachclaim
that needs to be verified, and for each call, theClaim Verifier
returnstrue
orfalse
. The transfer of ownership of the token is only possible if all the calls required toclaimIsValid()
return atrue
value. -
Trusted Claim Issuers Registry
: This smart contract stores the contract addresses(identities) of all the trustedclaim issuers
for a specific security token. TheIdentity Contract
of token owners (the investors) must haveclaims
signed by theclaim issuers
stored in this smart contract in order to be able to hold the token. The ownership of this contract is given to the token issuer allowing him to manage this registry as per his requirements. -
Trusted Claim Topics Registry
: This smart contract stores all the trustedclaim topics
for the security token. TheIdentity Contract
of token owners must haveclaims
of theclaim topics
stored in this smart contract. The ownership of this contract is given to the token issuer allowing them to manage this registry as per their requirements. -
Permissioned tokens
: T-REX permissioned tokens are based on a standardERC-20
structure but with some functions being added to it in order to ensure compliance of the transactions in the security tokens. The functionstransfer()
andtransferFrom()
are implemented in a conditional way, allowing them to proceed with a transfer onlyIF
theTransfer Manager
approves the transaction. All the functions that can be added to a standardERC-20
token can also be added to the T-REX permissioned tokens (Mintable
,Burnable
,Pausable
, …). The permissioned tokens are allowed to be transferred only to valid counterparts (i.e. whose identity is in the token identity registry) , in order to avoid tokens being held in wallets/identity contracts of ineligible/unauthorized investors. The T-REX standard also supports the re-issuance of security tokens in case an investor loses his/her wallet private key. A history of tokens re-issuance of is maintained on the blockchain for transparency reasons. -
Transfer Manager
: TheTransfer Manager
is the last piece of the puzzle. It is the contract that will make the link between all the collectible data and verify the compliance of a transaction. This is the contract thatcheck()
for the validity of atransfer()
. By interacting with theIdentity Registry
about the validity ofclaims
in theIdentity Contracts
of the seller, theTransfer Manager
may or may not allow the transfer of security tokens (depending on the status returned by theIdentity Registry
to theTransfer Manager
in response to thecheck()
initiated). Apart from investor identity eligibility, theTransfer Manager
will also validate more general token (or issuer) restrictions e.g. maintaining a max investor cap or a max tokens cap (as it might be needed for certain securities in certain specific countries of distribution). The contract is modular to support the addition of multiple general compliance rules as per the requirement of the token issuer or the regulatory framework under which the token is operated.
Specifications
Identity Contract
ERC-734
Complete specifications on the ERC-734 standard description.
ERC-735
Complete specifications on the ERC-735 standard description.
ClaimHolder
The ClaimHolder
is implementing the ERC-735
and the KeyHolder
contracts and add the notion of ownership
contract ClaimHolder is KeyHolder, ERC735 {
mapping (bytes32 => Claim) claims;
mapping (uint256 => bytes32[]) claimsByTopic;
address owner;
constructor() public {
owner = msg.sender;
}
}
- getOwner
Returns the address of the owner of a claim
function getOwner() public view returns(address);
KeyHolder
The KeyHolder
is implementing the ERC-734
contract.
Identity Registry
- registerIdentity
Register an Identity Contract
corresponding to a user address.
Requires that the user address should be the owner
of the Identity Contract
.
Requires that the user doesn't have an Identity Contract
already deployed.
Only the owner
(i.e. the token issuer) can call this function.
function registerIdentity(address _user, ClaimHolder _identity);
Triggers identityRegistered(_identity)
event
- updateIdentity
Updates an Identity Contract
corresponding to a user address.
Requires that the user address should be the owner
of the Identity Contract
.
Requires that the user should have an Identity Contract
already deployed that will be replaced.
Only the owner
(i.e. the token issuer) can call this function.
function updateIdentity(address _user, ClaimHolder _identity);
Triggers identityUpdated(identity[_user], _identity)
event
- deleteIdentity
Removes an user from the Identity Registry
.
Requires that the user have an Identity Contract
already deployed that will be deleted.
Only the owner
(i.e. the token issuer) can call this function.
function deleteIdentity(address _user);
Triggers identityRemoved(identity[msg.sender])
event
- isVerified
This function checks whether an Identity Contract
corresponding to the provided user address has the required claims
or not based on the security token.
return TRUE
if the address is verified, FALSE
if not.
function isVerified(address _userAddress) public view returns (bool);
- setClaimTopicsRegistry
Registry setter for Trusted Claim Topics Registry
Only the owner
(i.e. the token issuer) can call this function.
function setClaimTopicsRegistry(address _claimTopicsRegistry);
Triggers claimTopicsRegistrySet(_claimTopicsRegistry)
event
- setTrustedIssuerRegistry
Registry setter for Trusted Claim Issuers Registry
.
Only the owner
(i.e. the token issuer) can call this function.
function setTrustedIssuerRegistry(address _trustedIssuersRegistry);
Triggers trustedIssuersRegistrySet(_trustedIssuersRegistry)
event
Events
- identityRegistered
MUST be triggered when an registerIdentity
was successfully called.
event identityRegistered(ClaimHolder indexed identity);
- identityRemoved
MUST be triggered when deleteIdentity
was successfully called.
event identityRemoved(ClaimHolder indexed identity);
- identityUpdated
MUST be triggered when updateIdentity
was successfully called.
event identityUpdated(ClaimHolder indexed old_identity, ClaimHolder indexed new_identity);
- claimTopicsRegistrySet
MUST be triggered when setClaimTopicsRegistry
was successfully called.
event claimTopicsRegistrySet(address indexed _claimTopicsRegistry);
- trustedIssuersRegistrySet
MUST be triggered when setTrustedIssuerRegistry
was successfully called.
event trustedIssuersRegistrySet(address indexed _trustedIssuersRegistry);
Claim Verifier
- claimIsValid
Returns TRUE
if the claim meets the requirements(Trusted Claim Topic issued
by a Trusted Claim Issuer
) and FALSE
if not.
function claimIsValid(ClaimHolder _identity, uint256 claimTopic)public constant returns (bool claimValid);
Triggers ClaimValid
event if the claim meets the requirements.
Triggers ClaimInvalid
event if the claim doesn't meet the requirements.
- getRecoveredAddress
Recovers the address of the data
signer
function getRecoveredAddress(bytes sig, bytes32 dataHash) public view returns (address addr);
Events
- ClaimValid
COULD be triggered IF
the claim
was valid.
event ClaimValid(ClaimHolder _identity, uint256 claimTopic);
- ClaimInvalid
COULD be triggered IF
the claim
was invalid.
event ClaimInvalid(ClaimHolder _identity, uint256 claimTopic);
Trusted Claim Issuers Registry
- addTrustedIssuer
Adds the Identity Contract
of a Trusted Claim Issuer
corresponding to the index
provided.
Requires the index
to be greater than zero.
Requires that an Identity Contract
doesn't already exist corresponding to the index
.
Only the owner of the Registry (i.e. the token issuer) can call this function.
function addTrustedIssuer(ClaimHolder _trustedIssuer, uint index);
Triggers a trustedIssuerAdded
event.
- removeTrustedIssuer
Removes the Identity Contract
of a Trusted Claim Issuer
corresponding to the index
provided.
Requires the index
to be greater than zero.
Requires that an Identity Contract
exists corresponding to the index
.
Only the owner
of the Registry
(i.e. the token issuer) can call this function.
function removeTrustedIssuer(uint index);
Triggers a trustedIssuerRemoved
event.
- getTrustedIssuers
Function for getting all the Trusted Claim Issuer
indexes
stored.
Returns the array of indexes
of all the Trusted Claim Issuer
stored.
function getTrustedIssuers() public view returns (uint[]);
- getTrustedIssuer
Function for getting the Trusted Claim Issuer
's Identity Contract
address corresponding to the index
provided.
Requires the provided index
to have an Identity Contract
stored.
Only the owner
of the Registry
(i.e. the token issuer) can call this function.
function getTrustedIssuer(uint index) public view returns (ClaimHolder);
- updateIssuerContract
Updates the Identity Contract
of a Trusted Claim Issuer
corresponding to the index
provided.
Requires the index
to be greater than zero.
Requires that an Identity Contract
already exists corresponding to the provided index
.
Only the owner
of the Registry
(i.e. the token issuer) can call this function.
function updateIssuerContract(uint index, ClaimHolder _newTrustedIssuer);
Triggers a trustedIssuerUpdated
event.
Events
- trustedIssuerAdded
MUST be triggered when addTrustedIssuer
was successfully called.
event trustedIssuerAdded(uint indexed index, ClaimHolder indexed trustedIssuer);
- trustedIssuerRemoved
MUST be triggered when removeTrustedIssuer
was successfully called.
event trustedIssuerRemoved(uint indexed index, ClaimHolder indexed trustedIssuer);
- trustedIssuerUpdated
MUST be triggered when updateIssuerContract
was successfully called.
event trustedIssuerUpdated(uint indexed index, ClaimHolder indexed oldTrustedIssuer, ClaimHolder indexed newTrustedIssuer);
Trusted Claim Topics Registry
- addClaimTopic
Add a Trusted Claim Topic
(For example: KYC=1, AML=2).
Only the owner
of the Registry
(i.e. the token issuer) can call this function.
function addClaimTopic(uint256 claimTopic);
Triggers a claimTopicAdded
event.
- removeClaimTopic
Remove a Trusted Claim Topic
(For example: KYC=1, AML=2).
Only the owner
of the Registry
(i.e. the token issuer) can call this function.
function removeClaimTopic(uint256 claimTopic);
Triggers a claimTopicRemoved
event.
- getClaimTopics
Get the Trusted Claim Topics
for the security token
Returns an array of Trusted Claim Topics
function getClaimTopics() public view returns (uint256[]);
Events
- claimTopicAdded
MUST be triggered when addClaimTopic
was successfully called.
event claimTopicAdded(uint256 indexed claimTopic);
- claimTopicRemoved
MUST be triggered when removeClaimTopic
was successfully called.
event claimTopicRemoved(uint256 indexed claimTopic);
Transfer Manager
The Transfer Manager
contract uses and overrides the functions from Ownable
and StandardToken
from the ERC-20 standard.
contract TransferManager is Ownable, StandardToken
- transfer
ERC-20 overridden function that include logic to check for trade validity.
Require that the msg.sender
and to
addresses are not frozen
.
If the to
address is not currently a shareholder
then it MUST become one.
If the transfer
will reduce msg.sender
's balance to 0 then that address MUST be removed from the list of shareholders
.
Returns true
if successful and revert
if unsuccessful
function transfer(address _to, uint256 _value) public returns (bool);
- transferFrom
ERC-20 overridden function that include logic to check for trade validity.
Require that the from
and to
addresses are not frozen
.
If the to
address is not currently a shareholder
then it MUST become one.
If the transfer
will reduce from
's balance to 0 then that address MUST be removed from the list of shareholders
.
Returns true
if successful and revert
if unsuccessful
function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
- holderCount
This function returns the total number of token holder addresses.
Only the owner
(i.e. the token issuer) can call this function.
function holderCount() public onlyOwner view returns (uint);
- holderAt
This function returns the address of the shareholder
at a specified index
in the holder array.
Requires index
to be smaller than holderCount
result.
Only the owner
(i.e. the token issuer) can call this function.
function holderAt(uint256 index) public onlyOwner view returns (address);
- updateShareholders
If the address is not in the shareholders
array then push it and update the holderIndices
mapping.
function updateShareholders(address addr);
- pruneShareholders
If the address is in the shareholders
array and the forthcoming transfer
or transferFrom
will reduce their balance to 0, then we need to remove them from the shareholders
array.
function pruneShareholders(address addr, uint256 value);
- cancelAndReissue
Cancel the original address and reissue the Tokens to the replacement address.
Access to this function MUST be strictly controlled. Only the owner
(i.e. the token issuer) can call this function.
The original
address MUST be removed from the Identity Registry
.
Throw if the original
address supplied is not a shareholder
.
Throw if the replacement address is not a verified
address.
function cancelAndReissue(address original, address replacement);
Triggers VerifiedAddressSuperseded
event.
- isSuperseded
Checks to see if the supplied address was superseded
.
Returns true
if the supplied address was superseded
by another address.
function isSuperseded(address addr) public view onlyOwner returns (bool);
- getCurrentFor
Gets the most recent address, given a superseded
one.
Addresses may be superseded
multiple times, so this function needs to follow the chain of addresses until it reaches the final, verified address.
Returns the verified address that ultimately holds the share.
function getCurrentFor(address addr) public view onlyOwner returns (address);
- findCurrentFor
Recursively find the most recent address given a superseded
one.
Returns the verified address that ultimately holds the share.
function findCurrentFor(address addr) internal view returns (address);
- setAddressFrozen
Sets an address frozen
status for this token.
Only the owner
(i.e. the token issuer) can call this function.
function setAddressFrozen(address addr, bool freeze);
Triggers AddressFrozen
event.
- setIdentityRegistry
Identity Registry
setter.
Only the owner
(i.e. the token issuer) can call this function.
function setIdentityRegistry(address _identityRegistry);
Triggers IdentityRegistryAdded
event.
Events
- IdentityRegistryAdded
MUST be triggered when setIdentityRegistry
was successfully called.
event IdentityRegistryAdded(address indexed _identityRegistry);
- VerifiedAddressSuperseded
MUST be triggered when cancelAndReissue
was successfully called.
event VerifiedAddressSuperseded(address indexed original, address indexed replacement, address indexed sender);
- AddressFrozen
MUST be triggered when setAddressFrozen
was successfully called.
event AddressFrozen(address indexed addr,bool indexed isFrozen,address indexed owner);
Token
Token
contract is an inheritance of Mintable
contract.
contract Token is Mintable
Mintable
contract is an inheritance of TransferManager
contract.
contract Mintable is TransferManager
So, Token
contract is an inheritance of TransferManager
contract with the features of Mintable
contract, therefore the specs of both Token
and Mintable
contracts will be described in the following section.
- setTokenInformation
This function can be used to change the name
and/or the symbol
of the token.
Only the owner
(i.e. the token issuer) can call this function.
function setTokenInformation(string _name, string _symbol);
Triggers an UpdatedTokenInformation
event.
- mint
Improved version of default mint
function. Tokens can be minted
to an address if only it is a verified
address contained in the Identity Registry
.
Only the owner
(i.e. the token issuer) can call this function.
function mint(address _to, uint256 _amount) external onlyOwner canMint returns (bool);
Triggers Mint
event.
Triggers Transfer
event.
- finishMinting
Function to notify the end of the minting
process, when this function is called it sets mintingFinished
at TRUE
and turns the modifier cannotMint()
on.
Only the owner
(i.e. the token issuer) can call this function.
function finishMinting() external onlyOwner canMint returns (bool);
Triggers MintFinished
event.
- startMinting
Function to notify the start of the minting
process, when this function is called it sets mintingFinished
at FALSE
and turns the modifier canMint()
on.
Only the owner
(i.e. the token issuer) can call this function.
function startMinting() external onlyOwner cannotMint returns (bool);
Triggers MintStarted
event.
Events
- UpdatedTokenInformation
MUST be triggered when setAddressFrozen
was successfully called.
event UpdatedTokenInformation(string newName, string newSymbol);
- Mint
MUST be triggered when mint
was successfully called.
event Mint(address indexed to, uint256 amount);
- Transfer
MUST be triggered when mint
was successfully called.
event Transfer(address indexed from, address indexed to, uint tokens);
- MintStarted
MUST be triggered when startMinting
was successfully called.
event MintStarted();
- MintFinished
MUST be triggered when finishMinting
was successfully called.
event MintFinished();
Additional References
Developers
The project is created with truffle. Hence all truffle commands will work.
Setup
Install dependencies npm install
.
Run tests
npm run test
In case the command breaks, it may be due to node versioning issues.
Run npm rebuild
and then run npm run test
again.