RWA Staking Pools
This guide details deploying an EIP-2535 multi-facet diamond proxy-based staking platform. It covers required facets for configuration, enabling staking pool creation with custom settings, and step-by-step proxy storage initialization.
RWA Staking involves an Eligible Issuer creating a digital-twin NFT of a real-world asset. This asset is then locked in a Vault and fractionalized into ERC-20 or ERC-1155 tokens (fractions) using the Fraction Protocol. Each fraction token represents partial ownership of the original digital-twin NFT.
Users acquire these fractions by participating in an Issuer-run campaign that the Issuer runs. Subsequently, a staking platform is deployed, allowing the Issuer to launch a staking pool. This pool enables users to stake their fractions and earn rewards, allocated by the Issuer in a chosen ERC-20 token (e.g., USDC or other wrapped assets) for each staking position.
Note
To fractionalize the real-world asset, refer to the corresponding RWA Fraction Market step-by-step guide.
Facets
The table below lists the facets that must be added to the diamond proxy in order to create a platform that supports such RWA staking use case. Refer to the documentation for each facet to learn more about the underlying functionality and also you can check the address of the smart contract on the blockchain explorer.
Note
Refer to the GitHub repository of the Solid State diamond proxy for more details.
Instructions
Setting Up The Environment
Create a new directory and initialize the Node project by running:
Install the Typescript and TS execution engine:
Install the Hardhat npm package:
Begin by initializing the Hardhat project. When prompted, confirm each option by entering Y (yes) for all configuration choices.
Then install all necessary dependencies required for smart contract deployment:
In your project root folder find the tsconfig.json
file and add the resolveJsonModule
property to your configuration and set
it to true
:
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:
In the root folder of your project, create a new TypeScript file called deploy.ts
. This file will contain the code required to:
- Deploy the diamond proxy smart contract.
- Add facets and initialize the diamond storage.
Insert the code provided below into the deploy.ts
file and follow the comments.
Deploy the diamond proxy smart contract by running it in your terminal:
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:
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:
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.
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:
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
:
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.
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:
We also need to define an async operation to execute the diamondCut
function on the instance of the diamond proxy smart contract.
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:
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 AccessControlStakingFacet 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:
Define the async function to call initAccessControlFacet
on the facet contract:
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.
If you need to pass any other data, you have to encode it properly. For instance, this guide requires initializing the WhitelistedCampaignCreatorsFacet
by calling the initCreatorEligibilityFacet()
function with the initCreatorEligibilityData
calldata. To obtain the data, you need to refer to the corresponding FacetStorage smart contract - which in this case is: WhitelistedCampaignCreatorsFacetStorage
.
After that, you need to find the initCreatorEligibilityData
parameter in the table provided in the Reference.
Finally, you have to encode the data properly by using the following function:
Interacting with the Staking Platform
Suppose you need to interact with the platform we've set up—specifically, to stake in a campaign.
First, obtain an instance of the facet that provides the required functionality (i.e., stake()
) at the diamond proxy address:
Then you will call the function from this instance:
API Client
Evergonlabs provides a high-level API and an associated RWA staking pools template that supports rapid deployment of the staking platform and creation of staking pools. 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 staking use cases.