Fractionalization
Fractionalize takes custody of a T-Deed and issues an ERC-20 share token against it. Each
fraction has an id, from 1, and its own FractionToken, cloned by FractionTokenFactory. The deed
comes back out through unlockAsset, to whoever holds every minted share.
On this page
Source: Fractionalize.sol,
FractionToken.sol,
FractionTokenFactory.sol. Every
function, role and event is in smart-contracts.md.
flowchart LR
O["Deed owner"] -->|"createFraction"| F["Fractionalize<br/>holds the deed"]
F -->|"createFractionToken"| T["FractionToken<br/>(clone)"]
F -->|"mintShares<br/>(collection admin)"| H["Share holders"]
H -->|"unlockAsset<br/>(holder of every share)"| R["Deed sent to 'to'"]Creating a fraction
createFraction(params) moves the asset into Fractionalize and creates its share token. The
caller becomes the fraction's collection admin.
params is a FractionCreationParams:
assetTypeDeedNFT (0) or SubdivisionNFT (1)originalTokenId(deedId << 128) | unitIdname, symbolsymbol must not be emptydescription, collectionUrigetFractionExtendedInfototalSharesburnableapprovalPercentagerequiredApprovalPercentage; see Known limitationsWhat createFraction checks:
- The caller owns the asset. For a unit, the subdivision is active and the unit id is below its
totalUnits. totalShares,symbolandapprovalPercentage, as above.
What it does not check:
- Validation. An unvalidated deed can be fractionalized.
validateDeedNFTAssetandvalidateSubdivisionAssetreport a locked asset's validation status; nothing requires it. - An active subdivision. A deed with one can still be locked. While
Fractionalizeholds the deed, no one can mint more units or deactivate the subdivision, because both follow the deed's owner.
Moving the deed. Fractionalize calls DeedNFT.transferFrom, so the owner must approve it
first. While DeedNFT enforces royalties, which it does from initialize, both that approval and
the transfer revert unless Fractionalize is on DeedNFT's approved-marketplace list
(setApprovedMarketplace(Fractionalize, true)).
The share token. FractionTokenFactory.createFractionToken clones the FractionToken
implementation and initializes it with Fractionalize as its minter, the fraction's name and
symbol, burnable, and a per-wallet cap equal to totalShares. Events: FractionCreated,
AssetLocked.
Minting and burning shares
Only the collection admin mints, and never more than totalShares in all.
function mintShares(uint256 fractionId, uint256 amount, address to) external
function batchMintShares(BatchMintParams calldata params) external // fractionId, amounts[], recipients[]- A zero recipient means the caller.
amountmust be above zero, andactiveShares + amountmust not exceedtotalShares.- The recipient's balance after minting must not exceed the per-wallet cap. The cap is
totalShares, so one wallet can hold every share. - Each mint adds to
activeSharesand emitsSharesMinted.
Burning. burnShares(fractionId, amount) burns the caller's shares through the token's
burnFrom and reduces activeShares. It needs burnable. A holder can also call
FractionToken.burn directly, but that leaves activeShares unchanged; see
Known limitations.
Transfers. Shares are ordinary ERC-20 tokens. transfer and transferFrom revert if the
recipient would end up above the per-wallet cap.
Unlocking the deed
unlockAsset(params) ends the fraction and sends the asset to params.to. params is an
UnlockParams: fractionId, to (not zero) and checkApprovals.
activeShares == 0)checkApprovals falsegetVotingPower == activeShares)checkApprovals trueOn unlock the fraction is marked inactive, every share the caller holds is burned, and the asset is
transferred to to. Validation status is not checked. Event: AssetUnlocked.
Reading a fraction
getFractionBasicInfo(fractionId)totalShares, activeShares, maxSharesPerWalletgetFractionExtendedInfo(fractionId)collectionUri, requiredApprovalPercentage, isActive, burnablegetFractionOwnershipInfo(fractionId)getFractionToken(fractionId)canReceiveShares(fractionId, account)getVotingPower(fractionId, account)getApprovals(fractionId, account)(transferApproved, adminApproved), which are always falsevalidateAsset, validateDeedNFTAsset, validateSubdivisionAssetgetAssetInformation(fractionId)tokenURI (for a unit, its deed and unit ids)getDeedNFTAssetDetails, getDeedNFTAssetTrait, getDeedNFTAssetTraitKeysgetSubdivisionAssetDetails, getSubdivisionAssetTrait, getSubdivisionAssetTraitKeysgetFractionToken and the asset views revert once a fraction is inactive. getVotingPower is a
balance; there is no voting function.
Roles and pausing
- Collection admin: the caller of
createFraction. Mints shares and, before any are minted, can unlock. No function hands the role on. ADMIN_ROLEonFractionalize:pauseandunpause, which stopcreateFraction, minting,burnSharesandunlockAsset. They do not stop share transfers.DEFAULT_ADMIN_ROLE: upgrades and role grants.
Each FractionToken's own admin roles belong to FractionTokenFactory, which has no function that
uses them. A token's cap, burnable flag and pause cannot be changed after it is created.
Known limitations
- A subdivision unit cannot be fractionalized.
createFractionmoves a unit withsafeTransferFrom, which requires the receiver to implementonERC1155Received.Fractionalizedoes not, so the call reverts. - The
checkApprovalsunlock path never passes. It counts the caller's shares only ifadminApprovals[caller]is set, and nothing in the contract sets it.requiredApprovalPercentagehas no other use. - A non-burnable fraction cannot be unlocked once shares are minted. Unlocking burns the caller's shares through
burnFrom, which reverts unless the token is burnable. - Shares burned with
FractionToken.burnstay counted.activeSharesdoes not go down, so no one can hold every minted share afterwards and the deed cannot be unlocked. Burn throughburnShares. - No tests cover
Fractionalize,FractionTokenorFractionTokenFactory.