This is an open source Security Token from CoMakery. It implements the ERC-20 token standard and the ERC-1404 security token standard. It attempts to balance simplicity and sufficiency for smart contract tokens that need to comply with regulatory authorities.
Simplicity is necessary to make the full operations of the contract clear to users of the smart contracts. Simplicity also reduces the number of smart contract lines that need to be secured (each line of a smart contract is a security liability).
This open source software is provided with no warranty. This is not legal advice. CoMakery is not a legal firm and is not your lawyer. Securities are highly regulated across multiple jurisdictions. Issuing a security token incorrectly can result in financial penalties and jail time if you do it wrong. Consult a lawyer and tax advisor. Conduct an independent security audit of the code.
- The Deployer configures the parameters and deploys the smart contracts to a public blockchain. At the time of deployment, the deployer configures a separate token reserve address and Transfer Administrator address. This allows the reserve security tokens to be stored in cold storage since the treasury reserve address private keys are not needed for everyday use by the Transfer Admin.
- The Transfer Admin then provisions a hot wallet address for distributing tokens to investors or other stakeholders. The Transfer Admin uses
setAccountPermissions(investorAddress, transferGroup, addressTimeLock, maxTokens)to set address restrictions. - The Transfer Admin authorizes the transfer of tokens between account groups with
setAllowGroupTransfer(fromGroup, toGroup, afterTimestamp). - The Reserve Admin then transfers tokens to the Hot Wallet address.
- The Hot Wallet Admin then transfers tokens to investors or other stakeholders who are entitled to tokens.
By default the reserve tokens cannot be transferred to. To allow transfers the Transfer Admin must configure transfer rules using both setAccountPermissions(account, ...) to configure the individual account rules and setAllowGroupTransfer(...) to configure transfers between accounts in a group. A group represents a category like US accredited investors (Reg D) or foreign investors (Reg S).
During the setup process to split transfer oversight across three private key holders, the Transfer Admin can setup rules that only allow the Reserve Admin to only transfer tokens to the Hot Wallet admin address. The Hot Wallet should be restricted to a limited maximum balance necessary for doing one batch of token distributions - rather than the whole reserve. The use of a hot wallet for small balances also makes everyday token administration easier without exposing the issuer's reserve of tokens to the risk of total theft in a single transaction. Each of these private keys may also be managed with a multi-sig solution for added security. Multi-sig is especially important for the token reserve admin.
Here is how these restricted admin accounts can be configured:
- Transfer Admin, Reserve Admin and Hot Wallet admin accounts are managed by separate users with separate keys. For example, separate Nano Ledger S hardware wallets.
- Reserve and Hot Wallet addresses have their own separate transfer groups
unrestrictedAddressTimeLock = 0this timestamp will always have passedunrestrictedMaxTokenAmount = 2**256 -1is the largest number storable this number is available as theMAX_UNIT()constant.setAccountPermissions(reserveAddress, reserveTransferGroup, unrestrictedAddressTimelock, unrestrictedMaxTokenAmount)setAccountPermissions(reserveAddress, hotWalletTransferGroup, unrestrictedAddressTimeLock, sensibleMaxAmountInHotWallet)
- Reserve Address can only transfer to Hot Wallet Groups
setAllowGroupTransfer(reserveTransferGroup, hotWalletTransferGroup, unrestrictedAddressTimeLock)setAccountPermissions(reserveAddress, hotWalletTransferGroup, unrestrictedAddressTimeLock, sensibleMaxAmountInHotWallet)
- Hot Wallet Address can transfer to investor groups like Reg D and Reg S.
setAllowGroupTransfer(hotWalletTransferGroup, regD_TransferGroup, unrestrictedAddressTimeLock)setAllowGroupTransfer(hotWalletTransferGroup, regS_TransferGroup, unrestrictedAddressTimeLock)
Then the Hot Wallet Admin can distribute tokens to investors and stakeholders as described below...
- The Transfer Admin gathers AML/KYC and accreditation information from investors and stakeholders who will receive tokens directly from the issuer (the Primary Issuance).
- Transfer Admin then configures approved blockchain account addresses for investor and stakeholders with
setAccountPermissions(address, transferGroup, LockupUntil, maxBalance). Based on the AML/KYC and accreditation process the investor can provision the account address with a maximum number of tokens; a transfer group designating a regulatory class like "Reg D", "Reg CF" or "Reg S"; and a date that the tokens in the address will be locked until. - The tokens can then be transferred from the issuers hot wallet to the provisioned addresses.
Note that there are no transfers yet authorized between accounts. By default no transfers are allowed - all transfer groups are restricted.
The Transfer Admin for the Token Contract can provision account addresses to transfer and receive tokens under certain conditions. This is the process for configuring transfer restrictions and transferring tokens:
- An Investor sends their Anti Money Laundering and Know Your Customer (AML/KYC) information to the Transfer Admin or to a proxy vetting service to verify this information. The benefit of using a qualified third party provider is to avoid needing to store privately identifiable information.
- The Transfer Admin calls
setAccountPermissions(investorAddress, transferGroup, addressTimeLock, maxTokens)to provision their account. Initially this will be done for the Primary Issuance of tokens to investors where tokens are distributed directly from the issuer to holder accounts. - A potential buyer sends their AML/KYC information to the Transfer Admin.
- The Transfer Admin calls
setAccountPermissions(buyerAddress, transferGroup, addressTimeLock, maxTokens)to provision the Buyer account. - At this time or before, the Transfer Admin authorizes the transfer of tokens between account groups with
setAllowGroupTransfer(fromGroup, toGroup, afterTimestamp). Note that allowing a transfer from group A to group B by default does not allow the reverse transfer from group B to group A. This would have to be done separately. An example is that Reg CF unaccredited investors may be allowed to sell to Accredited US investors but not vice versa.
| From | To | Restrict | Enforced By |
|---|---|---|---|
| Reg D/S/CF | Anyone | Until TimeLock ends | setTimeLock(investorAddress) |
| Reg S Group | US Accredited | Forbidden During Flowback Restriction Period | setAllowGroupTransfer(fromGroupS, toGroupD, afterTime) |
| Reg S Group | Reg S Group | Forbidden Until Shorter Reg S TimeLock Ended | setAllowGroupTransfer(fromGroupS, toGroupS, afterTime) |
| Stolen Tokens | Anyone | Fix With Freeze, Burn, Reissue | freeze(stolenTokenAddress);burnFrom(address, amount);mint(newOwnerAddress); |
| Issuer | Reg CF with > maximum value of tokens allowed | Forbid transfers increasing token balances above max balance | setMaxBalance(amount) |
| Any Address During Regulatory Freeze | Anyone | Forbid all transfers while paused | pause() |
There are a few ways that lockup periods are enforced:
- By default all account addresses are locked. They require permissions to be transferred. Permissions can be granted on the account level or per address groups.
setTimeLock(account, unixTimestamp)locks all tokens in an account until the unix timestamp. Unix timestamps are indicated as the number of seconds since midnight UTC on January 1, 1970.setAllowGroupTransfer(fromGroup, toGroup, unixTimestamp)allows transfers from a group of addresses to another group of addresses after the unixTimestamp. If the unixTimestamp is 0, then no transfer is allowed. This is because any uninitialized combination of addresses and timestamp will have a default value of 0.- There is a convenience method for setting group, timelock and the maximum number of tokens for an account.
When transfering tokens to unaccredited investors or in the case that you wish to limit maximum number of tokens that an individual can obtaim:
- You can set the maximum number of tokens that an account can receive using
setMaxBalance(address _account, uint256 _updatedValue)
To allow trading in a group:
- Call
setAccountPermissions(address, transferGroup, addressTimeLock, maxTokens)for traders in the group setAllowGroupTransfer(fromGroupX, toGroupX, groupTimeLock)for account addresses associated with groupIDs like Reg S- A token transfer for an allowed group will succeed if:
- the
addressTimelockandgroupTimeLocktimes have passed; and - the recipient of a token transfer does not exceeded the
maxTokensin their account address.
- the
To allow trading between Foreign Reg S account addresses but forbid flow back to US Reg D account addresses until the end of the Reg D lockup period
- Call
setAccountPermissions(address, groupIDForRegS, shorterTimeLock, maxTokens)to configure settings for Reg S investors - Call
setAccountPermissions(address, groupIDForRegD, longerTimeLock, maxTokens)to configure settings for Reg D investors setAllowGroupTransfer(groupIDForRegS, groupIDForRegS, groupTimeLock)allow Reg S trading- A token transfer for an allowed group will succeed if:
- the
addressTimelockandgroupTimeLocktimes have passed; and - the recipient of a token transfer does not exceeded the
maxTokensin their account address.
- the
By default blockchain addresses cannot receive tokens. To receive tokens the issuer gathers AML/KYC information and then calls setAccountPermissions(). A single user may have multiple addresses. The issuer can track the number of holders offline and stop authorizing holders when the maximum holders amount has been reached.
If you need online enforcement for the maximum number of holders implemented contact noah@comakery.com
Centralized exchanges can register custody addresses using the same method as other users. They contact the Issuer to provision accounts and the Transfer Admin calls setAccountPermissions() for the exchange account.
When customers of the exchange want to withdraw tokens from the exchange account they must withdraw into an account that the Transfer Admin has provisioned for them with setAccountPermissions().
Talk to a lawyer about when exchange accounts may or may not exceed the maximum number of holders allowed for a token.
If there is a regulatory issue with the token, all transfers may be paused by calling pause(). During normal functioning of the contract pause() should never need to be called.
Issuers should have a plan for what to do during a blockchain fork. Often security tokens represent a scarce off chain asset and a fork in the blockchain may present ambiguity about who can claim an off chain asset. For example, if 1 token represents 1 ounce of gold, a fork introduces 2 competing claims for 1 ounce of gold.
In the advent of a blockchain fork, the issuer should do something like the following:
- have a clear and previously defined way of signaling which branch of the blockchain is valid
- signal which branch is the system of record at the time of the fork
- call
pause()on the invalid fork - use
burn()andmint()to fix errors that have been agreed to by both parties involved or ruled by a court in the issuers jurisdiction
In the case of stolen assets with sufficient legal reason to be returned to their owner, the issuer can call freeze(), burn(), and mint() to transfer the assets to the appropriate account.
Although this is not in the spirit of a cryptocurrency, it is available as a response to requirements that some regulators impose on blockchain security token projects.
In the case of lost keys with sufficient legal reason to be returned to their owner, the issuer can call freeze(), burn(), and mint() to transfer the assets to the appropriate account. This opens the issuer up to potential cases of fraud. Handle with care.
Once again, although this is not in the spirit of a cryptocurrency, it is available as a response to requirements that some regulators impose on blockchain security token projects.
Although this code does not implement dividend distribution or staking, it can be used with staking and dividend contracts with payments in stable coins like USDC, DAI as well as ETH or ERC20 tokens. Contact noah@comakery.com for further details.


