[Ethereum] How to let a user transfer ERC1155 token from the contract address to his address

erc-1155

What I want to achieve:
A user should be able to transfer ERC1155 tokens from my contract address to his account.

I've tried two approaches:

  1. Deploy ERC1155 contract, then pass its address to the constructor of the second contract which handles transfers.

This works. Users are able to call buyItems() without any errors.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol';
import '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol';
import '@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol';

contract ERC1155Preset is ERC1155PresetMinterPauser {
    constructor() ERC1155PresetMinterPauser("https://token-cdn-domain/{id}.json") {}
}

contract testContract is ERC1155Holder {
    IERC1155 private _IERC1155;
    
    constructor(IERC1155 ERC1155Address) {
        _IERC1155 = ERC1155Address;
    }
    
    function buyItems(uint256 _itemId, uint256 _amount) external {
        require(_IERC1155.balanceOf(address(this), _itemId) >= _amount);
        _IERC1155.safeTransferFrom(address(this), msg.sender, _itemId, _amount, "");
    }
}
  1. Deploy only one contract for ERC1155 and transfers.

This doesn't work. When a user calls buyItems(), I get this error: execution reverted: ERC1155: caller is not owner nor approved

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol';
import '@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol';

contract testContract2 is ERC1155PresetMinterPauser, ERC1155Holder {
    
    constructor() ERC1155PresetMinterPauser("https://token-cdn-domain/{id}.json") {}
    
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155PresetMinterPauser, ERC1155Receiver) returns (bool) {
        return interfaceId == type(IERC1155).interfaceId
            || interfaceId == type(IERC1155Receiver).interfaceId
            || super.supportsInterface(interfaceId);
    }
    
    function buyItems(uint256 _itemId, uint256 _amount) external {
        require(balanceOf(address(this), _itemId) >= _amount);
        safeTransferFrom(address(this), msg.sender, _itemId, _amount, "");
    }
}

My question: Why does the first approach work, and why not the second one?

Best Answer

Could you look into the documentation on openzepplin? I've linked it here, particularly pertaining to ERC1155PresetMinterPauser.There's a dependency that I don't see mentioned here, namely, Access Control.

The address you are using, the caller in this case, is not registered as either an owner or otherwise approved, hence the error.