Buyback Fraction Market
This guide provides a step-by-step overview for setting up a fraction platform with buyback functionality using the Fraction Protocol. It outlines the necessary facets for platform configuration and explains how to correctly initialize the proxy’s storage for Buyback Fraction Market applications.
The buyback is the process in which a borrower (issuer) creates fraction tokens (ERC-20/1155) to raise capital by selling them to investors. The borrower must then repay the debt, with interest, within a specified time window.
When you create a new campaign you can set the buyback duration and interest:
- Interest – An additional amount the borrower (issuer) must repay to on-chain investors upon buyback, calculated based on a predetermined interest rate as a return for providing capital.
- Buyback duration – A time window during which a borrower (issuer) must repay the debt provided by on-chain investors. If the debt is not repaid within the specified time window, the borrower is liquidated and their real-world assets are sold for fiat currency. The realized gains from the liquidation are converted into the campaign’s supported cryptocurrency and returned to investors.
Buyback Flow
The buyback flow represents a set of scenarios where a campaign can navigate through, where each scenario yields a specific result or destination point. These scenarios are dictated by the selected facets and their setup when deploying a platform.
Scenarios of the campaign are regulated by:
- The buying power of the on-chain investors — how much capital is provided to the borrower (issuer).
- The ability of the borrower (issuer) to repay the debt in the specified time window.
Successful Scenario
-
Twin NFT ERC-721 representing a real-world asset is used as collateral on-chain.
-
Issuer creates a campaign, wraps the ERC-721 twin NFT, and creates fractions (ERC-1155).
-
Once the campaign starts, the funds provision duration begins, and on-chain investors buy fractions up until the purchase phase ends.
-
After the purchase phase ends, if the campaign has reached at least the soft cap (funding goal), the borrower can receive the raised capital.
-
At the moment the purchase phase ends, the campaign initiates the countdown or a time frame in which the borrower (issuer) must return the debt he has received, plus the interest that is calculated based on the formula specified during the creation of the campaign. For instance:
Initial Amount * Interest Rate + Initial Amount
. -
Upon repayment of the debt, the borrower unlocks their ERC-721 twin NFT (collateral). At this point, the fractions cease to be shares of the twin NFT and instead represent shares of the total capital and interest repaid by the borrower.
-
Investors redeem their fractions through the platform and receive their original investment plus their proportional share of the interest.
Liquidation Scenario
-
Twin NFT ERC-721 representing a real-world asset is used as collateral on-chain.
-
Issuer creates a campaign, wraps the ERC-721 twin NFT, and creates fractions (ERC-1155).
-
Once the campaign starts, the funds provision duration begins, and on-chain investors buy fractions up until the purchase phase ends.
-
After the purchase phase ends, if the campaign has reached at least the soft cap (funding goal), the borrower can receive the raised capital.
-
At the moment the purchase phase ends, the campaign initiates the countdown or a time frame in which the borrower (issuer) must return the debt he has received, plus the interest that is calculated based on the formula specified during the creation of the campaign. For instance:
Initial Amount * Interest Rate + Initial Amount
. -
At this stage, there are two sub-scenarios that can take place:
Subscenario 1: Liquidation due to failed Margin Call
When an NFT asset is used as collateral for the campaign, its initial real-world price is assessed and then provided by the oracle to the platform.
The price of the real-world asset is continuously recalculated and provided to the oracle at specific intervals, in order to determine if the current campaign must be forcefully transitioned into a margin call.
Suppose the initial assessed price of the real-world asset is $200, but the borrower might receive only a portion of that amount - for example, $150. Typically, the borrower only receives a percentage of the initial asset price because the full assessed value of the collateral is considered risky.
The initial asset price acts as a point of reference for the campaign. The platform implements a threshold mechanism that determines when a campaign transitions to a margin call state and when it enters the liquidation phase.
If the asset’s market price drops below a certain threshold, for instance $150, then the campaign forcefully enters the
liquidation phase to protect the capital provided by investors. The Admin
of the platform or another custom
role (Liquidator
) with sufficient rights initiates the liquidation phase. Then the Liquidator sells the
real-world asset for fiat.
Note that if the collateral price drops below the value received as funds and a margin call is not initiated, and if the borrower never performs a buyback, investors could lose their funds, as the liquidator may not be able to sell the real-world asset at a value higher than its assessed price.
Subscenario 2: Liquidation due to failed Buyback
Borrower does not repay the debt in the specified time window. The Admin
of the platform or another custom
role (Liquidator
) with sufficient rights initiates the liquidation phase.
The ERC-721 twin NFT gets forcefully unlocked from the vault, and the real-world object associated with that twin NFT gets liquidated (sold) for fiat currency.
If the Liquidator is able to sell the real-world asset at a higher price compared to the initial assessment, then the higher amount of funds is returned to on-chain investors. For example, the originally agreed-upon debt is $110, but the Liquidator was able to sell the real-world asset for $150. Then at least $110 or more is returned to on-chain investors.
-
The realized gains from the liquidation are converted into the campaign’s supported cryptocurrency and sent to the platform.
-
Investors sell their fractions to the platform and recover the initially invested capital.
Non-funded Scenario
-
Twin NFT ERC-721 representing a real-world asset is used as collateral on-chain.
-
Issuer creates a campaign, wraps the ERC-721 twin NFT, and creates fractions (ERC-1155).
-
Once the campaign starts, the funds provision duration begins, and on-chain investors buy fractions up until the purchase phase ends.
-
The campaign does not reach the soft-cap. It progresses to the
NON_FUNDED
state, the Twin ERC-721 NFT is automatically unlocked and returned to the issuer, and all unpurchased fractions held by the platform are burned. Only fractions purchased by investors remain in circulation. -
Investors redeem their fractions through the
NonFundedSubskeleton
and receive only the initial amount that they paid for them. Hence, there is no interest because the campaign has not progressed to the proper state.
Facets
The following table outlines the required facets to integrate into the Diamond proxy for enabling the Buyback Fraction Market use case. For details on each facet’s functionality, refer to the respective documentation below.
Note
Some contracts provided in the table above are still in the development phase and are not audited. Exercise caution when deploying a new platform. Below, you can find a table that provides a list of contracts that are still in the development phase.
Contract |
---|
LendingOracleFacet |
PurchaseSkeletonNoDiscount |
PurchaseRoleEligibilityFacet |
Erc1155NonGenesisFractionFacet |
WrapAssetsFacet |
ReceiveAfterBuybackSkeleton |
BuybackSkeleton |
DoTransferPacketsFacet |
SingleStateReceiverFacet |
SingleStateBuybackFacet |
CreatorOnlyBuybackEligibilityFacet |
FixedInterestBuybackAmountFacet |
SingleStateReceiveAfterBuybackFacet |
NonFundedSubSkeletonNoDiscount |
FailedBuybackToFoldFacet |
FractionsAddressApprovalFacet |
BurnAndRedeemFractionsFacet |
ReceiveAfterBuybackRoleEligibilityFacet |
SingleStateReceiveAfterBuybackFacet |
TransferFundsOnBuybackFacet |
DurationBuybackTimeFacet |
FixedInterestBuybackAmountFacet |
MarginCallSubSkeleton |
ReceiveBackFundsFacet |
SingleStateMarginCallFacet |
FixedDurationMarginCallFacet |
PriceFeedMarginCallAmountFacet |
CreatorOnlyMarginCallEligibilityFacet |
TransferFundsAfterLiquidationFacet |
LiquidationForceUnlockFacet |
SingleStateLiquidationFacet |
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 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:
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 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:
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:
Then you will call the function from this instance: