🚀
TokenF
  • Introduction
  • TokenF vs ERC-3643 (T-REX)
  • Deep Dive
    • TokenF Core
    • TokenF Modules
  • For Developers
    • Getting started
    • GitHub repository
Powered by GitBook
On this page
  • Contracts setup
  • Regulatory token setup
  • Compliances setup
  • Modules setup
  • Deployment process
  1. For Developers

Getting started

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

npm install @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 or NFTF contract and add an init function. This completes the configuration of the base token.

Example of a basic EquityToken:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {TokenF} from "@tokenf/contracts/TokenF.sol";

contract EquityToken is TokenF {
    function __EquityToken_init(
        address regulatoryCompliance_,
        address kycCompliance_,
        bytes memory initRegulatory_,
        bytes memory initKYC_
    ) external initializer {
        __AccessControl_init();
        __ERC20_init("Equity Token", "ET");
        __AgentAccessControl_init();
        __TokenF_init(regulatoryCompliance_, kycCompliance_, initRegulatory_, initKYC_);
    }
}

Example of a basic LandNFT:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {NFTF} from "@tokenf/contracts/NFTF.sol";

contract LandNFT is NFTF {
    function __LandNFT_init(
        address regulatoryCompliance_,
        address kycCompliance_,
        bytes memory initRegulatory_,
        bytes memory initKYC_
    ) external initializer {
        __AccessControl_init();
        __ERC721_init("Land NFT", "LNFT");
        __AgentAccessControl_init();
        __NFTF_init(regulatoryCompliance_, kycCompliance_, initRegulatory_, initKYC_);
    }
}

This contracts are easily customisable by overriding the virutal functions, so you can implement arbitrary logic.

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 or EquityNFT:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {KYCCompliance} from "@tokenf/contracts/core/KYCCompliance.sol";

contract KYCComplianceFacet is KYCCompliance {
    function __KYCComplianceFacet_init() external onlyInitializing {
        __KYCCompliance_init();
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {RegulatoryCompliance} from "@tokenf/contracts/core/RegulatoryCompliance.sol";

contract RegulatoryComplianceFacet is RegulatoryCompliance {
    function __RegulatoryComplianceFacet_init() external onlyInitializing {
        __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:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {IAssetF} from "../../../interfaces/IAssetF.sol";
import {ERC20TransferLimitsModule} from "../../../modules/regulatory/ERC20TransferLimitsModule.sol";

contract ERC20TransferLimitsModuleMock is ERC20TransferLimitsModule {
    function __ERC20TransferLimitsModuleMock_init(
        address assetF_,
        uint256 minTransferValue_,
        uint256 maxTransferValue_
    ) external initializer {
        __AbstractModule_init(assetF_);
        __AbstractRegulatoryModule_init();
        __ERC20TransferLimitsModule_init(minTransferValue_, maxTransferValue_);
    }

    function __TransferLimitsDirect_init() external {
        __ERC20TransferLimitsModule_init(0, 0);
    }

    function __AbstractModuleDirect_init() external {
        __AbstractModule_init(address(0));
    }

    function __AbstractRegulatoryModuleDirect_init() external {
        __AbstractRegulatoryModule_init();
    }

    function getContextKey(bytes4 selector_) external view returns (bytes32) {
        IAssetF.Context memory ctx_;
        ctx_.selector = selector_;

        return _getContextKey(ctx_);
    }
}

Example of a ready-to-use user module:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {IAssetF} from "@tokenf/contracts/interfaces/IAssetF.sol";
import {ERC20TransferLimitsModule} from "@tokenf/contracts/modules/regulatory/ERC20TransferLimitsModule.sol";

contract EquityERC20TransferLimitsModule is ERC20TransferLimitsModule {
    function __EquityERC20TransferLimitsModule_init(address tokenF_) external initializer {
        __AbstractModule_init(tokenF_);
        __ERC20TransferLimitsModule_init(1 ether, MAX_TRANSFER_LIMIT);
    }

    function getContextKey(bytes4 selector_) external view returns (bytes32) {
        IAssetF.Context memory ctx_;
        ctx_.selector = selector_;

        return _getContextKey(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:

  1. Deployment of core contracts, including EquityToken, KYCComplianceFacet and RegulatoryComplianceFacet

  2. Initializing EquityToken contract

  3. Deployment of required modules

  4. Initializing modules

  5. Adding modules to EquityToken

import { Deployer, Reporter } from "@solarity/hardhat-migrate";

import {
  KYCComplianceFacet,
  KYCComplianceFacet__factory,
  EquityRarimoModule,
  EquityRarimoModule__factory,
  RegulatoryComplianceFacet,
  RegulatoryComplianceFacet__factory,
  EquityToken,
  EquityToken__factory,
  EquityERC20TransferLimitsModule,
  EquityERC20TransferLimitsModule__factory,
  RarimoSBT,
  RarimoSBT__factory,
} from "@ethers-v6";

async function setupCoreContracts(
  deployer: Deployer,
): Promise<[EquityToken, KYCComplianceFacet, RegulatoryComplianceFacet]> {
  const tokenF = await deployer.deploy(EquityToken__factory);
  const kycCompliance = await deployer.deploy(KYCComplianceFacet__factory);
  const regulatoryCompliance = await deployer.deploy(RegulatoryComplianceFacet__factory);

  const regulatoryComplianceInitData = regulatoryCompliance.interface.encodeFunctionData(
    "__RegulatoryComplianceFacet_init",
  );
  const kycComplianceInitData = kycCompliance.interface.encodeFunctionData("__KYCComplianceFacet_init");

  await tokenF.__EquityToken_init(
    regulatoryCompliance,
    kycCompliance,
    regulatoryComplianceInitData,
    kycComplianceInitData,
  );

  return [
    tokenF,
    kycCompliance.attach(tokenF) as KYCComplianceFacet,
    regulatoryCompliance.attach(tokenF) as RegulatoryComplianceFacet,
  ];
}

async function setupTransferLimitsModule(
  deployer: Deployer,
  tokenF: EquityToken,
): Promise<EquityERC20TransferLimitsModule> {
  const transferLimitsModule = await deployer.deploy(EquityERC20TransferLimitsModule__factory);
  await transferLimitsModule.__EquityERC20TransferLimitsModule_init(tokenF);

  const transferContextKey = await transferLimitsModule.getContextKey(await tokenF.TRANSFER_SELECTOR());
  const transferFromContextKey = await transferLimitsModule.getContextKey(await tokenF.TRANSFER_FROM_SELECTOR());

  await transferLimitsModule.addHandlerTopics(transferContextKey, [
    await transferLimitsModule.MIN_TRANSFER_LIMIT_TOPIC(),
    await transferLimitsModule.MAX_TRANSFER_LIMIT_TOPIC(),
  ]);
  await transferLimitsModule.addHandlerTopics(transferFromContextKey, [
    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 transferContextKey = await rarimoModule.getContextKey(await tokenF.TRANSFER_SELECTOR());
  const transferFromContextKey = await rarimoModule.getContextKey(await tokenF.TRANSFER_FROM_SELECTOR());

  await rarimoModule.addHandlerTopics(transferContextKey, [
    await rarimoModule.HAS_SOUL_SENDER_TOPIC(),
    await rarimoModule.HAS_SOUL_RECIPIENT_TOPIC(),
  ]);
  await rarimoModule.addHandlerTopics(transferFromContextKey, [
    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()],
    ["ERC20TransferLimitsModule", await transferLimitsModule.getAddress()],
    ["RarimoModule", await rarimoModule.getAddress()],
    ["RarimoSBT", await rarimoSBT.getAddress()],
  );
};
PreviousTokenF ModulesNextGitHub repository

Last updated 1 month ago

A sample deploy script can be found in the folder in the TokenF repository:

Deployment process for the NFTF is quite similar to TokenF. A sample deploy script can be found in the same folder in the TokenF repository.

examples
examples