Step-by-step Guides

RWA Fraction Market

This guide provides a step-by-step overview for setting up a fraction platform using the Fraction Protocol. It details the required facets for platform configuration and the correct initialization of the proxy’s storage for RWA Fraction Market applications.

The issuer of fractions can set up a staking platform using the Staking Protocol, where the issued ERC-1155 or ERC-20 fractions are used as the input token for a staking pool. The issuer defines the reward token (output) for the staking pool, which can be any ERC-1155 or ERC-20 token, such as USDC, to enable dividend-like payouts to fraction holders. Buyers of fractions can participate in the staking pool by staking their fractions to earn rewards.

Note

Deployment details of the staking platform are not covered in this topic; refer to the RWA Staking Pools guide for implementation instructions.

Facets

The following table outlines the required facets to integrate into the Diamond proxy for enabling the RWA Fraction Market use case. For details on each facet’s functionality, refer to the respective documentation below.

Note

Refer to the GitHub repository of the Solid State diamond proxy for more details.

ContractAddress
AccessControlFacet0x13a0A7B011E790dE74458b7512ad6E9A54f8Ac31
BurnFungibleForDiscountGenesisIdsExplicitFacet0xB6E18280161eA2880BBa6947E07DF2123164340D
CheckAndJumpToNonFundedFacet0x0C77b9A07A097f1689fDb122942c5B84bb108F4F
CreateFractionsSetterFacet0xd93365374c1FDA1e4A6ED3E7Ac26495618253582
CreateFractionsSkeleton0xE721AF43317dcC74De7DfAaB3F522e990b05cA0d
CreatorOnlyOnNonFundedEligibilityFacet0x97E95C31152C605D5F29bd9059156404e0Afb2e7
CreatorOnlyReceiveEligibilityFacet0x714F3b51Ff4A006EbE001CB60c011B50E17A64d7
CreatorOnlyReceiveNonPurchasedWrappedAssetsEligibilityFacet0x1c686Ad24Ee27020002BcF7af25DF4481218E9b2
CreatorOnlyRecoverFractionsEligibilityFacet0x0917C81A447FE9f4B6eB6ed8C05E94d78E8698cc
DoForceUnlockOnNonFundedFacet0xF020eF5a6E30887C5e68c2F26b935c3bb2DB7F92
DoRecoverErc1155FractionsFacet0x3e59259eD129938e8eC3060e7D8F04fee0014ef4
DoTransferPacketsFacet0xDb1a60C703f70471884a99f4Ba1E104faECC47af
Erc1155ReceiverFacet0x6530e1f196b64784C4EE6a6E4E14d37F8eB8D37E
Erc1155WithGenesisFractionFacet0xd8082d2CF6E4746c9c72501E376d4189Ab1b7dAe
Erc721ReceiverFacet0xDec3899c58e669d4400450979858961044288562
ForceUnlockOnNonFundedSubSkeleton0x29f930ad4019F1B79Dc3905a4c955339735d15D5
FractionsAddressApprovalFacet0x7Bb2cB5769997532DBd088BE5d72DEA9f01CC428
FundingErc20PacketsFacet0xE6cfa3D475d76c1a8d1DD747C505952dBa862110
LostAccountRoleEligibilityFacet0x2Ae95499D835D079813C159CAaC84D215f7997c4
MultipleStateReceiveNonPurchasedWrappedAssetsFacet0x66bC21cEA1d407A4D43fe764b20e9BAdc565856C
TwoCapPurchaseAmountFacet0xcaba852De2ED55BABF72eB61ccEd40c513f258Bd
MultipleStateVestingFacet0x4cfE4A898E99Cae1526e36cD026769F8dcE68fC2
NoDiscountGenesisIdsExplicitFacet0x18A9a44C02A7188E1F95F2c926714C7937254F3B
NonFundedSubSkeletonGenesisIds0x7f59f4e24CBA06228BCbA3Ac9B3d516a84EAC57b
PropPacketsGatheredFeeCollectorFacet0x9087490E11Fe2c7b5d11fA47EF853bBcEa64F5d8
PropWrappedAssetsFeeCollectorFacet0x2329A9358ee4adf48020bC9b01f11D859A1747B5
PurchaseDiscountGenesisIdsMixer0x0529511ED00Bb2dfE25B72038267021e70D0e3c4
PurchaseRoleEligibilityFacet0xfaE99ceba3ee467a050794ac6B2a5a87B36bd3B7
PurchaseSkeletonGenesisIds0x291984209cE0BDDe8dB7eFEB841c4ABD371323d1
PurchaseToReceiveRoleApprovalFacet0xB7e16D56624DC75E12226C95f032572bD56056b6
ReceiveAfterNonFundedRoleEligibilityFacet0xEc52Fd630B36964a5856a84edDe67CDe575Fb362
ReceiveAllGatheredFundsFacet0x8B64DE40Fa03A9b713Aeb47fB9aCC309F2613A7D
ReceiveAllNonPurchasedWrappedAssetsFacet0xA2cDf5b3209CfB0fb4F02c50405aE145De07359C
ReceiveBackFundsFacet0x035085B29c70C139626E657F5a3AAC38bC4Ee724
ReceiveNonPurchasedWrappedAssetsSubSkeleton0x01c3a194E160D911cd6F100F73222Bede90164d9
ReceiveSkeleton0x570BF6eBF4c7a8f6681D657E5D3CdD0A34aE31cB
RecoverFractionsSubSkeleton0xef1697EaE5D1159E6289951d8f09EF998d185C98
RestrictedStatesRecoverFractionsFacet0xb888B91cA232eca557Cf6078Fd564ea59fD952Dd
ReturnFungAndSemiFungFractionsFacet0x289642f7BBa7a3DcC6B15254e435Df19b6c23D1e
SingleStateNonFundedFacet0x651Dbe856b9054fbD2adE7398aDd1Af3Ed37A937
SingleStatePurchaseFacet0x2e5BF5c6848681b5a02d49b49df86E982519d42F
SingleStateReceiveFacet0xA3B206fa5e9442FE960D2d130531C232250Ff9Fd
StablePriceFacet0x3911ff8A5c9385abce7AB93076A3510C09A442cB
StateFacet0x24Ded24e30b96f749C40E00B7b178801C5239ce6
TwoBorderTimeFacet0x405221633CC4fD56bE908ED46a393e34A54d6780
TwoCapPurchaseAmountFacet0xcaba852De2ED55BABF72eB61ccEd40c513f258Bd
UniformlyProvidedVestingFacet0x8d711809853F3410815ce025e7F6837405f7FC85
VestingSubSkeleton0xC647FF3266644aca2E1C9398294b4Ec111806327
WhitelistedCreatorsFacet0xBc9A140B70724Df6f9B70F726E45D6844c198687
WrapFungibleAssetsFacet0xE7029E973BC618B05844DF088Ba804Ee31453918

Instructions

Setting Up The Environment

Create a new directory and initialize the Node project by running:

npm init

Install the Typescript and TS execution engine:

npm install --save-dev typescript ts-node

Install the Hardhat npm package:

npm install hardhat 

Begin by initializing the Hardhat project. When prompted, confirm each option by entering Y (yes) for all configuration choices.

npx hardhat init

Then install all necessary dependencies required for smart contract deployment:

npm install @solidstate/contracts @nomicfoundation/hardhat-toolbox ethers

In your project root folder find the tsconfig.json file and add the resolveJsonModule property to your configuration and set it to true:

{
  "compilerOptions": {
    "resolveJsonModule": true
    /// ... other properties
  }
}

This property ensures that Typescript can read the data from the .json files.

Diamond Proxy Deployment

In your project, find the contracts folder and, inside it, create a new file that will contain the implementation of the diamond proxy smart contract. For demonstration purposes, the name of the file will be MyDiamond.sol.

Insert the following code into the MyDiamond.sol file:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
 
import "./proxy/diamond/SolidstateDiamondProxy.sol";
 
contract FractionDiamond is SolidStateDiamond {}

In the root folder of your project, create a new TypeScript file called deploy.ts. This file will contain the code required to:

  1. Deploy the diamond proxy smart contract.
  2. Add facets and initialize the diamond storage.

Insert the code provided below into the deploy.ts file and follow the comments.

import { ethers } from "hardhat";
// 1. Import the FractionDiamond.json file to read the ABI and the Bytecode.
import SolidStateDiamond from "./artifacts/contracts/MyDiamond.sol/FractionDiamond.json"
 
// 2. Set up provider (using a testnet like Sepolia). 
// Ensure you have set up the environment variable that contains the provider details.
const provider = new ethers.JsonRpcProvider(process.env.SEPOLIA_RPC);
 
// 3. Set up wallet (replace with your private key).
const wallet = new ethers.Wallet("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", provider);
 
// 4. Define ABI and Bytecode.
const abi = SolidStateDiamond.abi
const bytecode = SolidStateDiamond.bytecode
 
// 5. Initialize the variable for the diamond proxy address.
let diamondAddress: string;
 
// 6. Create a ContractFactory
const contractFactory = new ethers.ContractFactory(abi, bytecode, wallet);
 
// 7. Deploy the contract 
async function deployContract() {
  console.log("Deploying contract...");
 
  // 8. Deploy the contract
  const contract = await contractFactory.deploy();
 
  // 9. Wait for deployment to be mined
  await contract.waitForDeployment();
  console.log("Contract deployed at:", contract.target);
  diamondAddress = contract.target
}
 
deployContract().catch(console.error);

Deploy the diamond proxy smart contract by running it in your terminal:

npx hardhat run deploy.ts

Obtaining Selectors

At this stage, you need to obtain the function selectors corresponding to the facet signatures. This guide outlines two approaches you can use to generate these selectors.

Approach 1: Manual Conversion

For this approach, you need to use the signature-to-selector converter, which allows generating a valid selector for a given signature.

Use the data table provided in this topic to refer to the corresponding smart contract documentation for function signatures.

For each facet, you need to create the corresponding array where you will store selectors. For demonstration purposes, only two selector arrays are provided below:

const AccessControlFacetSelectors = [
    "0x91d14854"
]
 
const StateFacetSelectors = [
    "0xcc90cf94",
    "0x06a91c6a"
]

Approach 2: Automatic Conversion

This approach relies on the automatic generation of selectors based on the human-readable-abi standard.

For all function signatures, refer to the corresponding facet documentation that you can access through the data table provided in this topic.

Create arrays that will store the function signatures. For demonstration purposes, only two signature arrays are provided below:

const AccessControlFacetAbi = [
    "function hasRole(bytes32 role, address account) external view returns (bool)"
]
 
const StateFacetSelectorsAbi = [
    "function changeState(uint256 campaignId, uint256 fromState, uint256 toState) external",
    "function getStateOfId(uint256 campaignId) external view returns (uint256)"
]

Then you have to use the functionality of the ethers library to iterate through all signatures in the arrays and convert them into selectors. Ensure that you replace the value passed to the Interface instance.

import { Interface, id, FunctionFragment, hashMessage, dataLength } from "ethers";
 
//  Create an Interface
const iface = new Interface(StateFacetSelectorsAbi);
 
// Loop over all function signatures
let StateFacetSelectorsAbiResult = []
 
for (const fragment of iface.fragments) {
  if (fragment.type === "function") {
    const fnFragment = fragment as FunctionFragment;
    const signature = fnFragment.format(); // canonical signature
    const selector = id(signature).slice(0, 10); // 4-byte selector
    console.log(`${selector}`);
    StateFacetSelectorsAbiResult.push(selector);
  }
}

Adding Facets To Diamond Proxy

Next, you have to create an array of FacetCut objects containing the function selectors that will be added to the diamond proxy. In the Solidity code, the FacetCut is represented as the following struct:

struct FacetCut {
  address target;
  FacetCutAction action;
  bytes4[] selectors;
}

The FacetCutAction represents an enum that determines how to handle the facet cut process. In our case, we need to use the ADD member of the enum, which is represented by the index position 0:

enum FacetCutAction {
 ADD,
 REPLACE,
 REMOVE
}

You need to specify the address of the facet, the FacetCutAction member, which is 0, and an array of selectors for each facet that you want to add to the diamond proxy.

const AccessControlFacetAddress = "0x13a0A7B011E790dE74458b7512ad6E9A54f8Ac31"
const StateFacetAddress = "0x24Ded24e30b96f749C40E00B7b178801C5239ce6"
 
let facet1 = { target: AccessControlFacetAddress, action: 0, selectors: AccessControlFacetSelectors}
let facet2 = { target: StateFacetAddress, action: 0, selectors: StateFacetSelectors}

Now we have prepared all the required FacetCut objects, and we are ready to add them to the diamond proxy.

Next, you need to create an instance of the diamond proxy you just deployed:

const diamondProxyContract = new ethers.Contract(diamondAddress, abi, wallet);

We also need to define an async operation to execute the diamondCut function on the instance of the diamond proxy smart contract.

async function diamondCut() {
  const result = await diamondProxyContract.diamondCut([facet1, facet2], ethers.ZeroAddress, "0x");
  console.log(result)
}

As you can see, the first argument expects an array of facet arrays containing selectors; the second argument expects the zero address, which we pass using ethers.ZeroAddress; while the third argument expects the init data, which is empty ("0x").

Now you can call the diamondCut() function, which will return the result of the operation:

diamondCut()

After adding the facets to the diamond proxy, you can verify their presence by using the Louper web tool.
Insert the diamond proxy address into the search bar and verify the newly added functions.

Diamond Proxy Storage Initialization

After adding facets to the diamond proxy, we need to initialize them.

Note

You only need to initialize those facets that have an init function available. For example, the AccessControlFacet has the initAccessControlFacet function, which must be called.

Other facets don't require initialization.

Create instances of the facets that need initialization. These instances must be called at the address of the diamond proxy in order to interact with them through the diamond proxy:

const facet1AtDiamond = new ethers.Contract(diamondAddress, iface, wallet);

Define the async function to call initAccessControlFacet on the facet contract:

async function initializeFacet() {
  const result = await facet1AtDiamond.initAccessControlFacet(wallet.address);
  console.log(result)
}

Now, by calling the initializeFacet function, you initialize the diamond storage for the selected facet. In case you have added more facets to your diamond contract, you need to initialize them too.

initializeFacet()

If you need to pass any other data, you have to encode it properly. For instance, this guide requires initializing the TwoBorderTimeFacet by calling the initPurchaseTimeFacet() function with the initPurchaseTimeData calldata. To obtain the data, you need to refer to the corresponding FacetStorage smart contract - which in this case is: TwoBorderTimeFacetStorage.

After that, you need to find the initPurchaseTimeData parameter in the table provided in the Reference.

Finally, you have to encode the data properly by using the following function:

ethers.utils.defaultAbiCoder.encode(["bytes"], ["YOUR_DATA_IS_HERE"]);

Interacting with the Fraction Platform

Suppose you need to interact with the platform we've set up—specifically, to get the state of a specific campaign. First, obtain an instance of the facet that provides the required functionality (i.e., getStateOfId()) at the diamond proxy address:

const StateFacetFacetAbi = [
    "function getStateOfId(uint256 campaignId) external view returns (uint256)"
]
let StateFacetAtDiamond = new ethers.Contract(diamondAddress, StateFacetFacetAbi, wallet);

Then you will call the function from this instance:

let campaignId = 1;
 
async function getStateOfId() {
  const result = await StateFacetAtDiamond.getStateOfId(campaignId);
  console.log(result);
}

API Client

Evergonlabs provides a high-level API and an associated RWA Fraction Market template that supports rapid deployment of the fraction platform and creation of campaigns. The @evergonlab/tmi-protocol-api-client library library handles low-level protocol interactions.

The high-level API offers a simplified interface for configuring and deploying fraction use cases.

On this page