Contracts setup
When developing TokenF, a lot of attention was paid to making it easy to create projects based on it. The architecture may seem complicated at first glance, but once you understand it, it is easy enough to implement a project of any size.
In order to start developing a TokenF-based project, you need to add an npm package with the core contracts
Copy npm install --save-dev @tokenf/contracts
Regulatory token setup
Since you have chosen TokenF
, the centrepiece of your protocol is likely to be the regulatory token itself. All you need to do is inherit the token contract from the TokenF
contract and add an init function. This completes the configuration of the base token.
Example of a basic EquityToken
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { TokenF } from "@tokenf/contracts/core/TokenF.sol" ;
contract EquityToken is TokenF {
function __EquityToken_init (
address regulatoryCompliance_ ,
address kycCompliance_ ,
bytes memory initRegulatory_ ,
bytes memory initKYC_
)
external
initializer ( DIAMOND_ERC20_STORAGE_SLOT )
initializer ( DIAMOND_ACCESS_CONTROL_STORAGE_SLOT )
initializer ( AGENT_ACCESS_CONTROL_STORAGE_SLOT )
initializer ( TOKEN_F_STORAGE_SLOT )
{
__DiamondAccessControl_init ();
__DiamondERC20_init ( "Equity Token" , "ET" );
__AgentAccessControl_init ();
__TokenF_init (regulatoryCompliance_ , kycCompliance_ , initRegulatory_ , initKYC_);
}
}
This contract is easily customisable by overriding the virutal functions, so you can make any logic you require
Compliances setup
The next step is to create KYCCompliance
and RegulatoryCompliance
contracts. As with the token, in the basic version you only need to inherit from the required TokenF
compliance contracts and add init functions.
Like all other contracts, compliance contracts can be easily customised to suit your needs. If the contracts cannot provide you with the functionality you need, you can implement the compliance contracts yourself, as long as the IKYCCompliance
and IRegulatoryCompliance
interfaces are implemented.
Example of basic compliance contracts for EquityToken
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { KYCCompliance } from "@tokenf/contracts/core/KYCCompliance.sol" ;
contract EquityKYCCompliance is KYCCompliance {
function __EquityKYCCompliance_init ()
external
initializer ( KYC_COMPLIANCE_STORAGE_SLOT )
{
__KYCCompliance_init ();
}
}
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { RegulatoryCompliance } from "@tokenf/contracts/core/RegulatoryCompliance.sol" ;
contract EquityRegulatoryCompliance is RegulatoryCompliance {
function __EquityRegulatoryCompliance_init ()
external
initializer ( REGULATORY_COMPLIANCE_STORAGE_SLOT )
{
__RegulatoryCompliance_init ();
}
}
Modules setup
The last but not least part is module configuration. TokenF
provides abstract contracts for KYC
and Regulatory
modules, which in most situations will be suitable for inheritance. In case they can't provide the necessary business logic, there is an AbstractModule
contract that will definitely fit all situations.
To release your own modules, you need to inherit from the required abstract module or from already implemented modules, which TokenF
also provides.
Example of a TokenF
regulatory module for setting minimum and maximum transfer limits
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { TokenF } from "../../core/TokenF.sol" ;
import { AbstractRegulatoryModule } from "../AbstractRegulatoryModule.sol" ;
abstract contract TransferLimitsModule is AbstractRegulatoryModule {
bytes32 public constant MIN_TRANSFER_LIMIT_TOPIC = keccak256 ( "MIN_TRANSFER_LIMIT" );
bytes32 public constant MAX_TRANSFER_LIMIT_TOPIC = keccak256 ( "MAX_TRANSFER_LIMIT" );
uint256 public constant MAX_TRANSFER_LIMIT = type( uint256 ).max;
uint256 private _minTransferLimit;
uint256 private _maxTransferLimit;
function __TransferLimitsModule_init (
uint256 minTransferValue_ ,
uint256 maxTransferValue_
) internal onlyInitializing {
_minTransferLimit = minTransferValue_;
_maxTransferLimit = maxTransferValue_;
}
function setMinTransferLimit (
uint256 minTransferLimit_
) public virtual onlyRole ( _complianceModuleRole ()) {
_minTransferLimit = minTransferLimit_;
}
function setMaxTransferLimit (
uint256 maxTransferLimit_
) public virtual onlyRole ( _complianceModuleRole ()) {
_maxTransferLimit = maxTransferLimit_;
}
function _handlerer () internal virtual override {
_setHandler (MIN_TRANSFER_LIMIT_TOPIC , _handleMinTransferLimitTopic);
_setHandler (MAX_TRANSFER_LIMIT_TOPIC , _handleMaxTransferLimitTopic);
}
function transferred ( TokenF . Context calldata ctx_) public virtual override {}
function getTransferLimits ()
public
view
virtual
returns ( uint256 minTransferLimit_ , uint256 maxTransferLimit_)
{
return (_minTransferLimit , _maxTransferLimit);
}
function _handleMinTransferLimitTopic (
TokenF . Context memory ctx_
) internal view virtual returns ( bool ) {
return ctx_.amount >= _minTransferLimit;
}
function _handleMaxTransferLimitTopic (
TokenF . Context memory ctx_
) internal view virtual returns ( bool ) {
return ctx_.amount <= _maxTransferLimit;
}
uint256 [ 48 ] private _gap;
}
Example of a ready-to-use user module
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { TokenF } from "@tokenf/contracts/core/TokenF.sol" ;
import { TransferLimitsModule } from "@tokenf/contracts/modules/regulatory/TransferLimitsModule.sol" ;
contract EquityTransferLimitsModule is TransferLimitsModule {
function __EquityTransferLimitsModule_init (
address tokenF_ ,
uint256 minTransferValue_
) external initializer {
__AbstractComplianceModule_init (tokenF_);
__TransferLimitsModule_init (minTransferValue_);
}
function getClaimTopicKey ( bytes4 selector_) external view returns ( bytes32 ) {
TokenF.Context memory ctx_;
ctx_.selector = selector_;
return _getClaimTopicKey (ctx_);
}
}
Deployment process
Deployment process depends quite a lot on the specific implementation of the project, but still there will be common parts. Let's look at the steps for deploying basic contracts from the examples above:
Deployment of core contracts, including EquityToken
, EquityKYCCompliance
and EquityRegulatoryCompliance
Initialising EquityToken
contract
Deployment of required modules
Adding modules to EquityToken
The sample deploy script you can find in the examples in the TokenF repository
Copy import { Deployer , Reporter } from "@solarity/hardhat-migrate" ;
import {
EquityKYCCompliance ,
EquityKYCCompliance__factory ,
EquityRarimoModule ,
EquityRarimoModule__factory ,
EquityRegulatoryCompliance ,
EquityRegulatoryCompliance__factory ,
EquityToken ,
EquityToken__factory ,
EquityTransferLimitsModule ,
EquityTransferLimitsModule__factory ,
RarimoSBT ,
RarimoSBT__factory ,
} from "@ethers-v6" ;
async function setupCoreContracts (
deployer : Deployer ,
) : Promise <[ EquityToken , EquityKYCCompliance , EquityRegulatoryCompliance ]> {
const tokenF = await deployer .deploy (EquityToken__factory);
const kycCompliance = await deployer .deploy (EquityKYCCompliance__factory);
const regulatoryCompliance = await deployer .deploy (
EquityRegulatoryCompliance__factory
);
const regulatoryComplianceInitData = regulatoryCompliance .interface
.encodeFunctionData ( "__EquityRegulatoryCompliance_init" );
const kycComplianceInitData = kycCompliance .interface
.encodeFunctionData ( "__EquityKYCCompliance_init" );
await tokenF .__EquityToken_init (
regulatoryCompliance ,
kycCompliance ,
regulatoryComplianceInitData ,
kycComplianceInitData ,
);
return [
tokenF ,
kycCompliance .attach (tokenF) as EquityKYCCompliance ,
regulatoryCompliance .attach (tokenF) as EquityRegulatoryCompliance ,
];
}
async function setupTransferLimitsModule (
deployer : Deployer ,
tokenF : EquityToken
) : Promise < EquityTransferLimitsModule > {
const transferLimitsModule = await deployer .deploy (
EquityTransferLimitsModule__factory
);
await transferLimitsModule .__EquityTransferLimitsModule_init (tokenF);
const transferClaimTopicKey = await transferLimitsModule .getClaimTopicKey (
await tokenF .TRANSFER_SELECTOR ()
);
const transferFromClaimTopicKey = await transferLimitsModule .getClaimTopicKey (
await tokenF .TRANSFER_FROM_SELECTOR ()
);
await transferLimitsModule .addClaimTopics (transferClaimTopicKey , [
await transferLimitsModule .MIN_TRANSFER_LIMIT_TOPIC () ,
await transferLimitsModule .MAX_TRANSFER_LIMIT_TOPIC () ,
]);
await transferLimitsModule .addClaimTopics (transferFromClaimTopicKey , [
await transferLimitsModule .MIN_TRANSFER_LIMIT_TOPIC () ,
await transferLimitsModule .MAX_TRANSFER_LIMIT_TOPIC () ,
]);
return transferLimitsModule;
}
async function setupRarimoModule (
deployer : Deployer ,
tokenF : EquityToken
) : Promise <[ EquityRarimoModule , RarimoSBT ]> {
const rarimoSBT = await deployer .deploy (RarimoSBT__factory);
await rarimoSBT .__RarimoSBT_init ();
const rarimoModule = await deployer .deploy (EquityRarimoModule__factory);
await rarimoModule .__EquityRarimoModule_init (tokenF , rarimoSBT);
const transferClaimTopicKey = await rarimoModule .getClaimTopicKey (
await tokenF .TRANSFER_SELECTOR ()
);
const transferFromClaimTopicKey = await rarimoModule .getClaimTopicKey (
await tokenF .TRANSFER_FROM_SELECTOR ()
);
await rarimoModule .addClaimTopics (transferClaimTopicKey , [
await rarimoModule .HAS_SOUL_SENDER_TOPIC () ,
await rarimoModule .HAS_SOUL_RECIPIENT_TOPIC () ,
]);
await rarimoModule .addClaimTopics (transferFromClaimTopicKey , [
await rarimoModule .HAS_SOUL_SENDER_TOPIC () ,
await rarimoModule .HAS_SOUL_RECIPIENT_TOPIC () ,
await rarimoModule .HAS_SOUL_OPERATOR_TOPIC () ,
]);
return [rarimoModule , rarimoSBT];
}
export = async (deployer : Deployer ) => {
const [ tokenF , kycCompliance , regulatoryCompliance ] = await setupCoreContracts (
deployer
);
const [ rarimoModule , rarimoSBT ] = await setupRarimoModule (deployer , tokenF);
const transferLimitsModule = await setupTransferLimitsModule (deployer , tokenF);
await kycCompliance .addKYCModules ([rarimoModule]);
await regulatoryCompliance .addRegulatoryModules ([transferLimitsModule]);
Reporter .reportContracts (
[ "EquityToken" , await tokenF .getAddress ()] ,
[ "TransferLimitsModule" , await transferLimitsModule .getAddress ()] ,
[ "RarimoModule" , await rarimoModule .getAddress ()] ,
[ "RarimoSBT" , await rarimoSBT .getAddress ()] ,
);
};
Last updated 8 months ago