ERC1155 NFT Trading Smart Contract Issues

I built an ERC1155 marketplace contract for creating, buying and selling NFTs.

The token creation works fine, but I’m having trouble with marketplace functionality. When someone tries to buy an NFT, the transaction fails and I can’t figure out why.

Market purchase test failed:
   Error: VM Exception while processing transaction: reverted with reason string 'ERC1155: caller is not owner nor approved'
  at TokenContract.balanceOf (@openzeppelin/contracts/token/ERC1155/ERC1155.sol:71)
  at TokenContract.isApprovedForAll (@openzeppelin/contracts/token/ERC1155/ERC1155.sol:110)
  at TokenContract.executePurchase (contracts/TokenContract.sol:165)

Here’s my marketplace contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract TokenMarketplace is ERC1155, Ownable, ERC1155Supply {
    constructor() ERC1155("") {}
    
    mapping(uint256 => string) private _uris;
    mapping(uint256 => ListingData) private listings;
    Counters.Counter private _soldCounter;
    
    struct ListingData {
        uint256 id;
        address payable creator;
        address payable currentOwner;
        uint256 cost;
        bool purchased;
    }
    
    event ItemListed(
        uint256 indexed id,
        address creator,
        address currentOwner,
        uint256 cost,
        bool purchased
    );
    
    using Counters for Counters.Counter;
    Counters.Counter private _idCounter;
    
    function updateBaseURI(string memory newUri) public onlyOwner {
        _setURI(newUri);
    }
    
    function createToken(
        string memory metadataUri,
        uint256 supply,
        uint256 cost
    ) public returns (uint256) {
        uint256 currentId = _idCounter.current();
        _mint(address(this), currentId, supply, "");
        _setMetadata(currentId, metadataUri);
        _idCounter.increment();
        return currentId;
    }
    
    function listForSale(
        uint256 id,
        uint256 cost,
        uint256 supply
    ) private {
        require(cost > 0, "Cost must be greater than zero");
        listings[id] = ListingData(
            id,
            payable(msg.sender),
            payable(address(this)),
            cost,
            false
        );
        setApprovalForAll(address(this), true);
        safeTransferFrom(msg.sender, address(this), id, supply, "");
        
        emit ItemListed(
            id,
            msg.sender,
            address(this),
            cost,
            false
        );
    }
    
    function _setMetadata(uint256 id, string memory uri) private {
        _uris[id] = uri;
    }
    
    function executePurchase(uint256 id, uint256 supply) public payable {
        uint256 cost = listings[id].cost;
        address creator = listings[id].creator;
        
        listings[id].currentOwner = payable(msg.sender);
        listings[id].purchased = true;
        listings[id].creator = payable(address(0));
        
        _soldCounter.increment();
        
        safeTransferFrom(address(this), msg.sender, id, supply, "");
        setApprovalForAll(address(this), true);
        payable(creator).transfer(msg.value);
    }
    
    function getAvailableItems() public view returns (ListingData[] memory) {
        uint256 totalItems = _idCounter.current();
        uint256 availableCount = _idCounter.current() - _soldCounter.current();
        uint256 index = 0;
        
        ListingData[] memory available = new ListingData[](availableCount);
        for (uint256 i = 0; i < totalItems; i++) {
            if (listings[i + 1].currentOwner == address(this)) {
                uint256 currentId = i + 1;
                ListingData storage item = listings[currentId];
                available[index] = item;
                index += 1;
            }
        }
        return available;
    }
}

I already tried using setApprovalForAll during minting but still get the same error. What am I missing here?

Your contract has a fundamental flaw in how it handles token ownership. You’re minting tokens directly to address(this) in createToken(), making the contract the owner. But then listForSale() expects msg.sender to be the owner transferring tokens to the contract. That’s a circular dependency.

I hit this exact problem building my first ERC1155 marketplace. Here’s how to fix it:

Restructure your token creation flow. Either mint to the creator first, then have them list for sale, OR modify createToken() to auto-create the listing without needing a separate transfer. Your executePurchase() function calls setApprovalForAll() after the transfer - but you need approval before attempting the transfer. The error happens because the contract can’t transfer tokens it supposedly owns. I’d mint to the creator’s address first, then require them to approve and call a separate listing function. Cleaner ownership semantics and no more approval headaches.

Interesting! I’ve been working on something similar and hit weird approval issues too.

Looking at your code - when you call createToken(), the NFT mints directly to the contract address (address(this)), but then you’ve got a separate listForSale() function marked as private. How are you actually calling that function to create the listing?

Also noticed you’re doing setApprovalForAll(address(this), true) inside executePurchase, but wouldn’t the contract need approval BEFORE it tries transferring the token? The error message suggests it’s failing during the transfer itself.

One thing that caught my eye - are you sure the listing data gets created properly when you mint? Since listForSale() is private and I don’t see it called anywhere in your createToken() function, maybe the ListingData struct isn’t getting populated at all?

Can you show us how you’re testing this? What does your test script look like when it tries calling executePurchase()? Might help us figure out if it’s the contract logic or how it’s being called.

Btw, have you tried checking what balanceOf(address(this), tokenId) returns before the purchase attempt?

Your problem’s in the flow - you’re minting tokens but never creating listings for them. The listForSale() function is private and isn’t called anywhere, so when executePurchase() runs there’s no listing data. You’re also checking approval after the transfer fails instead of before. Make listForSale() public or call it automatically inside createToken().