ERC1155 Token Trading Contract Issues - Approval Problems

I built an ERC1155 marketplace contract for creating and trading digital tokens.

The tokens mint correctly but they don’t appear in the marketplace and buyers can’t purchase them. I keep getting this error even though I used setApprovalForAll during minting:

Trading execution test failed:
Error: VM Exception while processing transaction: reverted with reason string 'ERC1155: caller is not owner nor approved'
at TokenMarket.balanceOf (@openzeppelin/contracts/token/ERC1155/ERC1155.sol:71)
at TokenMarket.isApprovedForAll (@openzeppelin/contracts/token/ERC1155/ERC1155.sol:110)
at TokenMarket.executePurchase (contracts/TokenMarket.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 TokenMarket is ERC1155, Ownable, ERC1155Supply {
    constructor() ERC1155("") {}

    mapping(uint256 => string) internal _metadataURIs;
    mapping(uint256 => ListingItem) private idToListing;
    Counters.Counter private _soldCounter;

    struct ListingItem {
        uint256 tokenId;
        address payable seller;
        address payable owner;
        uint256 price;
        bool sold;
    }

    event ListingCreated(
        uint256 indexed tokenId,
        address seller,
        address owner,
        uint256 price,
        bool sold
    );

    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    function updateURI(string memory newuri) public onlyOwner {
        _setURI(newuri);
    }

    function createToken(
        string memory metadataURI,
        uint256 quantity,
        uint256 askingPrice
    ) public returns (uint256) {
        uint256 newTokenId = _tokenCounter.current();
        _mint(address(this), newTokenId, quantity, "");
        _setMetadataUri(newTokenId, metadataURI);
        _tokenCounter.increment();
        return newTokenId;
    }

    function listForSale(
        uint256 tokenId,
        uint256 askingPrice,
        uint256 quantity
    ) private {
        require(askingPrice > 0, "Price must be at least 1 wei");
        idToListing[tokenId] = ListingItem(
            tokenId,
            payable(msg.sender),
            payable(address(this)),
            askingPrice,
            false
        );
        setApprovalForAll(address(this), true);

        safeTransferFrom(msg.sender, address(this), tokenId, quantity, "");

        emit ListingCreated(
            tokenId,
            msg.sender,
            address(this),
            askingPrice,
            false
        );
    }

    function executePurchase(uint256 tokenId, uint256 quantity) public payable {
        uint256 askingPrice = idToListing[tokenId].price;
        address seller = idToListing[tokenId].seller;

        idToListing[tokenId].owner = payable(msg.sender);
        idToListing[tokenId].sold = true;
        idToListing[tokenId].seller = payable(address(0));

        _soldCounter.increment();

        safeTransferFrom(address(this), msg.sender, tokenId, quantity, "");
        setApprovalForAll(address(this), true);
        payable(seller).transfer(msg.value);
    }

    function _setMetadataUri(uint256 tokenId, string memory metadataURI) private {
        _metadataURIs[tokenId] = metadataURI;
    }

    function getAvailableListings() public view returns (ListingItem[] memory) {
        uint256 totalCount = _tokenCounter.current();
        uint256 availableCount = _tokenCounter.current() - _soldCounter.current();
        uint256 currentIndex = 0;

        ListingItem[] memory listings = new ListingItem[](availableCount);
        for (uint256 i = 0; i < totalCount; i++) {
            if (idToListing[i + 1].owner == address(this)) {
                uint256 currentId = i + 1;
                ListingItem storage currentListing = idToListing[currentId];
                listings[currentIndex] = currentListing;
                currentIndex += 1;
            }
        }
        return listings;
    }
}

hmm, this is interesting - i’ve seen similar issues before but your code structure is different than usual. The error’s pointing to line 165 in executePurchase, but why’s it calling balanceOf and isApprovedForAll in that sequence?

I’m curious about your workflow - are you calling createToken first then triggering listForSale? Because listForSale is marked private, so it’s not being called externally. That might be the issue.

Also, when you say “I used setApprovalForAll during minting” - where exactly? I see it in listForSale and executePurchase, but not in createToken. Are you calling setApprovalForAll from your frontend or test scripts?

What’s bugging me is you’re minting to address(this) in createToken, but then in listForSale you’re trying to transfer from msg.sender to address(this). That seems backwards? If the contract already owns the tokens after minting, why transfer them again?

Can you walk us through the exact sequence of function calls? Like createToken, then something else, then executePurchase? And who’s the seller here - the person who calls createToken or the contract itself?

Also curious - what’s your test code look like? That might help us figure out where the approval issue is actually happening.

Your token ownership flow is broken. You’re minting tokens to address(this) in createToken, but listForSale is private and never gets called. So tokens just sit stuck in the contract without being listed.

The error happens because executePurchase tries to transfer tokens that belong to the contract, but the approval chain is messed up. You’re setting approval AFTER trying to transfer - way too late.

I hit this same issue in my marketplace last year. Fix it by either minting tokens directly to the seller’s address, or make a public listing function that actually gets called after minting. Right now you’re creating orphaned tokens that can’t be traded.

Change createToken to mint to msg.sender instead of address(this), then make listForSale public so sellers can list their tokens. And do the approval before any transfer attempts, not after.

you’re mixing up the token ownership logic here. you mint to the contract, but then listForSale tries to transfer FROM msg.sender TO the contract - that doesn’t make sense. also, listForSale is private so it never gets called anyway lol. make createToken mint to msg.sender instead of address(this), and make listForSale public so people can actually list their tokens.