ItemMainInterface

Overview

The Main Interface represents the ERC1155 face of an Item. Every Items shares the same Main Interface and so the same ERC1155 address. It is itself implemented by all Items, both native and wrapped/Decks.

Dynamic Metadata

The Main Interface contract implements the IERC1155, as well as the abstract IDynamicMetadataCapableElement contract, from which it inherits the capabilities of dynamic on-chain Metadata.

Supports Interface

It also implements the ItemMainInterfaceSupportsInterfaceImplementer interface, used for detecting and publishing which interfaces the Main Interface implements in accordance with EIP-165.

Host Interactions

The Items protocol is designed in such a way that the host of an Items Collection can't interact directly with the Core (Main Interface) to perform its host operations (such as create a Collection, mint Items, change Metadata etc..) but he must interact with the Extension hosting the Collection which in turn interacts with the Core (Main Interface).

For example, to mint new Items it is not possible to call directly the Main Interface mintItems function but the host must call the Extension mintItems function that inside has the logic to call the Main Interface mintItems function .

The Items holders instead can both interact directly with the Core and with the Extension to transfer, burn etc.. their items.

Structs

struct Header {
    address host;
    string name;
    string symbol;
    string uri;
}

The Header struct is used in both Item and Collection operations to create Collections, mint Items and manage Collections/Items Metadata .

Depending on how it is used, it will contain different parameters. These include:

  • address host

This is a Collection’s host address, which has the permission to mint Items for the Collection. It can be either an Extension address cloned by the Extension Factory or address(0). In the second case, the Collection has no owner; no one can create itemIds, mint Items or change metadata.

Th address host parameter is only passed to create or modify Collections, not to create or modify Items. An Item has no host—except, indirectly, that of its Collection.

If you try to pass the parameter for an Item, it won’t be taken as valid, and will instead be deprecated by the contract.

  • string name -> this is a Collection or Item’s name

  • string symbol -> this is a Collection or Item’s symbol

  • string uri -> this is a Collection or Item’s uri

CreateItem

struct CreateItem {
    Header header;
    bytes32 collectionId;
    uint256 id;
    address[] accounts;
    uint256[] amounts;
}

The CreateItem struct is used to create a Collection with Items or to create/mint Items on an already existing Collection.

Its parameters are as follows:

  • Header -> this is the struct described above.

  • bytes32 collectionId -> this is the id of the Collection to which an itemId being created will belong.

  • uint256 id -> this is the itemId of an Item being minted. If passed as 0, a new itemId will be created.

  • address[] accounts -> these are the address(es) that will be sent the Items after they are minted. The amount each address receives is the amount in the uint256[] amounts parameter that corresponds to its sequential position.

  • uint256[] amounts -> these are the amount(s) of the Item to mint (and send to the address[] accounts).

Depending on whether you are creating a Collection with Items or creating/minting Items on an already existing Collection, the collectionId and itemId must be passed in different ways:

  • When creating a new Collection with one or more new itemIds:

    • collectionId : 0

    • itemIds : 0

  • When creating one or more new itemIds in a pre-existing Collection:

    • collectionId : id of the Collection

    • itemIds : 0

  • When minting one or more Items of pre-existing itemIds in a pre-existing Collection:

    • collectionId: id of the Collection

    • itemIds: ids of the Items to mint

ItemData

struct ItemData {
    bytes32 collectionId;
    Header header;
    bytes32 domainSeparator;
    uint256 totalSupply;
    mapping(address => uint256) balanceOf;
    mapping(address => mapping(address => uint256)) allowance;
    mapping(address => uint256) nonces;
}

This struct contains all data that is related to a specific itemId; there is one itemData struct for every itemId saved in the contract through the item mapping.

This struct is used by the Main Interface to perform different operations on Items (such as changing Items Metadata, Items approves and permit), and is composed as follows:

  • bytes32 collectionId -> this represents the collectionId of the itemId’s Collection.

  • Header -> this is the struct discussed on the previous page.

  • bytes32 domainSeparator -> this is for performing off-chain signature transactions.

  • uint256 totalSupply -> this represents the itemId’s total supply.

  • mapping balanceOf -> this represents, via mapping, the balance of a specific address that holds the Item of the itemId.

  • mapping allowance -> this represents, via mapping, the amount of an itemId that a specific address can send. The amount can be increased using the various approve and permit functions that are discussed below.

  • mapping permitNonce -> this registers, via mapping, how many signatures a specific holder has used regarding an itemId. The nonce is used to perform the off-chain signature transaction. When creating a signature, a nonce value must be included.

When executing a permit function, the nonce must precisely match the total number of signatures the holder has used thus far (inclusive of the current one). This ensures that each unique signature is used only once

Functions

Create an Items Collection

function createCollection(Header calldata _collection, CreateItem[] calldata items) external returns(bytes32 collectionId, uint256[] memory itemIds);
  • Function type: write

  • Callable by:

    • Extension contract** (users interact with the Extension contract and then the Extension interacts with this function)

    • Any address if the host address will be set as address(0)

This function is used to create either an empty Collection or a Collection with itemIds. In the first instance, the Header parameter is populated, while the CreateItem one is blank. In the second instance, both parameters are populated.

A Collection’s host address can be set as address(0) so no one has minting permission. For this to work, one or more itemIds must be created when the Collection is made; otherwise, the function fails. If you want an an Extension (MultiOperatorHost or Native), the Collection must be directly created by the Extension. Look at the Extension section for more info.

If the CreateItem[] struct is passed (to create a Collection with Items), the itemIds will also be created. In this case, the collectionId within the createItem struct must be blank (as must the itemIds). If not, it won’t be considered a valid parameter.

The collectionId for the newly created collection will be generated and automatically retrieved by the createCollection function.

Recall, from the previous paragraph, how to correctly pass the collectionId and itemIds:

  • Create a new empty Collection -> collectionId : 0

  • Create a new Collection with one or more new Items -> collectionId : 0; itemIds : 0

Create/Mint Items on an already existing Collection

function mintItems(CreateItem[] calldata items) override external returns(uint256[] memory) {
  • Function type: write

  • Callable by:

    • Operator address having the minting permission power**

This function can be used to create a new Item id or to mint a new amount of an existing Item id inside an already existing Collection.

In the first case, the id parameter of the CreateItem must be passed as 0. In the second case, it must be passed as the existing item id to mint.

Change Collections and Items Metadata

Change Collections Metadata

function setCollectionsMetadata(bytes32[] calldata collectionIds, Header[] calldata values) external returns(Header[] memory oldValues);
  • Function type: write

  • Callable by:

    • Operator address having the Metadata change permission power**

This function is used to change the metadata of one or more Collections, by passing their collectionIds with the desired new metadata in the Header[] parameter.

The output returns the Headers that contain the (now) old metadata.

Change Items Metadata

function setItemsMetadata(uint256[] calldata itemIds, Header[] calldata amounts) external returns(Header[] memory oldValues);
  • Function type: write

  • Callable by:

    • Operator address having the Metadata change permission power**

This function is used to change the metadata of one or more Items, by passing their itemIds with the desired new metadata in the Header[] parameter.

The output returns the Headers that contain the (now) old metadata.

Approvals operations on Items

Allowance

function allowance(address account, address spender, uint256 itemId) external view returns(uint256);
  • Function type: read-only.

This function can be used to check the amount of an Item (represented by its itemId) that an approved operator can spend.

Approve

function approve(address account, address spender, uint256 amount, uint256 itemId) external;
  • Function type: write.

  • Callable by:

    • An Item holder, for a held Item

    • An Item’s InteroperableInterface, for the Item*

This function can be used to grant to or revoke permission from a spender address to act on an account’s item (itemId) amount.

Approve for all

function setApprovalForAll(address operator, bool approved) external
  • Function type: write.

  • Callable by:

    • Item holder for all the held Items

This function can be used to grant or revoke permission to the operator address to act on all the account's Items, according to the approved parameter. In fact, if the approved parameter is true, you’re going to grant permission while if it is false, you’re going to revoke the permission.

Approve for all by Collection host

function setApprovalForAllByCollectionHost(bytes32 collectionId, address account, address operator, bool approved) external
  • Function type: write.

  • Callable by:

    • Extension contract (users interact with the Extension contract and then the Extension interacts with this function)**

This function is used to grant or revoke permission to the operator address to act on all the account's Items of a specific Collection (collectionId), according to the approved parameter. In fact, if the approved parameter is true, you’re going to grant permission while if it is false, you’re going to revoke the permission.

Permit operation

Typehash permit

function TYPEHASH_PERMIT() external returns (bytes32);
  • Function type: read-only

This function is the hash of the function’s name (Permit) and all of its parameters, including the type and the name:

bytes32 override public constant TYPEHASH_PERMIT = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

It is used to clearly identify which function the signature is for. The signature will be processed by the permit function, and if the TYPEHASH-PERMIT() used is not for this specific function, it will revert, ensuring the signature is only used for the intended function.

Domain separator and name

EIP712_PERMIT_DOMAINSEPARATOR_NAME_AND_VERSION
  • Function type: read-only

This function returns the name and version of the specific domainSeparator used in the contract. For the Main Interface contract, the name of the domainSeparator is “Item”, and the version is “I”.

Nonces

function nonces(address owner, uint256 itemId) external view returns(uint256);
  • Function type: read-only

This function returns the specific nonce for a specific owner and itemId. The nonce is used to perform an off-chain transaction signature. To learn about nonce mapping in the itemData section above.

Permit

function permit(uint256 itemId, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) override external
  • Function type: write

  • Callable by: any address

The Item standard supports EIP-2612, which proposes a permit method for making approvals by way of signed transactions rather than gas-costing transactions.

This method is based on EIP-712, the standard for hashing and signing typed structured data (as opposed to just bytestrings).

This means approval can be given via either the approve function or the permit function. Either way, the allowed amount the spender address can spend is increased:

itemData.allowance[owner][spender] = value;

A permit operation requires first that the off-chain signature is generated; then, the permit function must be called. See here for an example of a frontend off-chain signature operation.

Once the off-chain sign is completed, the caller of the permit function can be any address. The function’s input parameters are:

  • uint256 itemId -> this is the id of the Item for which permission to spend is being granted.

  • address owner -> this is the address of the Item owner, who grants permission to a spender address to spend his Item.

  • address spender -> this is the address that can spend the allowed amount of the Item.

  • uint256 value -> this is the amount of the allowed Item that can be spent.

  • uint256 deadline -> the Item owner can limit the time a Permit is valid for by setting a deadline value. The deadline argument can be set to uint(-1) to create Permits that effectively never expire. The permit requires that the block.timestamp is <= the deadline.

  • uint8 v, r, s -> is a valid secp256k1 signature from the owner of the following message:

 abi.encodePacked(
     '\x19\x01',
     itemData.domainSeparator,
     keccak256(abi.encode(TYPEHASH_PERMIT, owner, spender, value, itemData.nonces[owner]++, deadline))
)

where domainSeparator is defined as:

newItem.domainSeparator = keccak256(
    abi.encode(
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
        keccak256(bytes(domainSeparatorName)),
        keccak256(bytes(domainSeparatorVersion)),
        block.chainid,
        address(uint160(itemIds[i]))
)

All three conditions together — the TYPEHASH_PERMIT(), the domainSeparator and the nonce — ensure that each signature is used only for the intended contract, the intended function, and only once.

Transfer operations

safeTransferFrom

function safeTransferFrom(address from, address to, uint256 itemId, uint256 amount, bytes calldata data) override external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amount of the Item being transferred

    • Any approved operator address that holds >= the amount of the Item being transferred

This is the classic ERC1155 safeTransferFrom function. It can be used to transfer an amount of a single Item to a receiver address (to).

safeBatchTransferFrom

function safeBatchTransferFrom(address from, address to, uint256[] calldata itemIds, uint256[] calldata amounts, bytes calldata data) override external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amount of the Item being transferred

    • Any approved operator address that holds >= the amount of the Item being transferred

This is the classic ERC1155 safeBatchTransferFrom function. It can be used to transfer varying amounts of multiple Items in a single transaction from the holder address (from) to a receiver address (to).

Every Items (of different Collections) even wrapped/Decks can be transferred together.

Burn operations

Burn standard

function burn(address account, uint256 itemId, uint256 amount) external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amount of the Item being burned.

    • Any approved operator address that holds >= the amount of the Item being burned.

This is the classic ERC1155 burn function. It can be used to burn an amount of a single Item.

Burn with data

function burn(address account, uint256 itemId, uint256 amount, bytes calldata data) external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amount of the Item being burned.

    • Any approved operator address that holds >= the amount of the Item being burned.

This is a special burn function of the Items protocol. It has one more parameter than the classic ERC1155 burn function: the data (bytes) parameter, which can contain an additional payload, allowing for the execution of more complex burn operations.

For example, when burning a wrapped Item, it can specify an address that will be the receiver of the unwrapped token after the Item is burned.

Burn Batch standard

function burnBatch(address account, uint256[] calldata itemIds, uint256[] calldata amounts) external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amounts of the Items being burned.

      Any approved operator address that holds >= the amounts of the Items being burned.

This is the classic ERC1155 burnBatch function. It can be used to burn varying amounts of multiple Items.

Every Items (of different Collections) even wrapped/Decks can be burned together.

Burn Batch with data

function burnBatch(address account, uint256[] calldata itemIds, uint256[] calldata amounts, bytes calldata data) external;
  • Function type: write.

  • Callable by:

    • Any address that holds >= the amounts of the Items being burned.

    • Any approved operator address that holds >= the amounts of the Items being burned.

This is a special burnBatch function of the Item protocol. It has one more parameter than the classic ERC1155 burnBatch function: the data (bytes) parameter. This can contain an additional payload, allowing for the execution of more complex burnBatch operations.

For example, when burning multiple wrapped Items, it can contain multiple addresses that will be the receivers of the unwrapped tokens after the Items are burned.

Every Items (of different Collections) even wrapped/Decks can be burned together.

[i] regarding the "callable by", it indicates who can call the function. Actually, the "callable by" takes into account the fact that at this time the host of a Collection can be the Extension (multiOperatorHost) or none (address(0)).

*It means that some functions are called by the InteroperableInterface. In fact, the users interact with the InteroperableInterface, in case of a transfer/Burn for example, and then the InteroperableInterface calls the MainInterface specific function.

**It means that some functions are called by the MultiOperatorHost/Native Extension. In fact, the host must interact with the Extension and then the Extension calls the MainInterface specific function.

Last updated