Learning ERC-7208Data Managers

ERC1155 Data Manager

Data Managers are independent smart contracts that implement business logic for data management, serving as the primary entry point for user interactions and ensuring the underlying storage aligns with the required use-case logic.

There are various implementations of Data Managers, as they can integrate any logic, offering limitless possibilities. Having covered the core concepts, let's explore specific minimal implementations.

Data Managers can interact with each other. In this example, two Data Managers are deployed, with ERC1155 and ERC20 being essential for communication.

ERC1155 With ERC20Fractions DataManager

Contract Explanation

The MinimalisticERC1155WithERC20FractionsDataManager smart contract implements the ERC1155 interface, treating each token in the collection as an ERC20.

The contract inherits several ERC1155 functionalities and includes IFractionTransferEventEmitter to emit events across multiple Data Managers. Changes in one Data Manager will update the other, and events are emitted across both Data Managers.

contract MinimalisticERC1155WithERC20FractionsDataManager is
    IFractionTransferEventEmitter, IERC1155, IERC1155Errors,
    IERC1155MetadataURI, ERC165, Ownable

State Variables

Generic Data Manager
Variable NameDescriptionTypeVisibility
_datapointDataPoint for fungible fractionsDataPointinternal
fungibleFractionsDOFungible Fractions Data Object contractIDataObjectpublic
dataIndexData Index implementationIDataIndexpublic
ERC1155 Specific
Variable NameDescriptionTypeVisibility
_nameName of the ERC1155 tokenstringprivate
_symbolSymbol of the ERC1155 tokenstringprivate
_defaultURIDefault URI for token typesstringprivate
_operatorApprovalsApprovals state for operators per addressmapping(address => mapping(address => bool))private
ERC20 Fractions Specific

This part is not mandatory, but we incorporate this information to help retrieve and managing the fractions more easily.

Variable NameDescriptionTypeVisibility
erc20FractionsDMFactoryFactory for ERC20FractionDataManagerMinimalisticERC20FractionDataManagerFactorypublic
fractionManagersByIdERC20FractionDataManager by token IDmapping(uint256 => address)public
fractionManagersByAddressToken ID by ERC20FractionDataManager addressmapping(address => uint256)public

Following an Standard Interface and forwarding methods

In this section, we’ll demonstrate how to perform a write operation to a DataPoint using the ERC1155 Standard Interface, using the mint() operation as an example.

    /**
     * @notice Mint new tokens
     * @param to The address to mint tokens to
     * @param id The token ID
     * @param value The amount of tokens to mint
     * @param data Additional data with no specified format
     */
    function mint(address to, uint256 id, uint256 value, bytes memory data) public virtual onlyMinter {
        _mint(to, id, value, data);
    }_

For external users allowed to mint, the public method follows the standard but requires proper internal management.

    function _mint(address to, uint256 id, uint256 value, bytes memory data) internal virtual {
        if (id == 0) revert IncorrectId(id);
 
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
 
        _deployERC20DMIfNotDeployed(id, data);
        _updateWithAcceptanceCheck(address(0), to, id, value, data);
    }

We perform validation checks on the token ID and ensure the receiver is not a zero address. Before calling this method, we verify if the ERC20Fractions Data Manager has been deployed. Then, we proceed with the logic using the appropriate storage references.

The _updateWithAcceptanceCheck() method performs user updates and checks if the destination implements IERC1155Receiver.onERC1155Received as required by the IERC1155 standard. After update(), we call _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data).

Following the internal calls, you'll see how it notifies the ERC20 Data Manager and forwards the write operation to the Data Index.

    function _writeTransfer(address from, address to, uint256 id, uint256 value) internal virtual {
        if (from == address(0)) {
            dataIndex.write(address(fungibleFractionsDO), _datapoint, IFungibleFractionsOperations.mint.selector, abi.encode(to, id, value));
        } //Other conditions are omitted for readibility
    }

We check that variables like fungibleFractionsDO and _datapoint are set in the constructor, then pass the operation selector encoded with the operation values.

This flow applies to any write() operation, such as burning or transferring. For read() operations, we can directly call the DataObject, as demonstrated in the following method:

    /**
     * @notice Total supply of the ERC1155 token
     * @return The amount of tokens in existence
     * @dev This function is used to get the total supply of the ERC1155 token considering all token IDs
     */
    function totalSupply() public view returns (uint256) {
        return abi.decode(fungibleFractionsDO.read(_datapoint, IFungibleFractionsOperations.totalSupplyAll.selector, ""), (uint256));
    }

On this page