This article is intended to be a continuation of @dabit3's great tutorial on how to create a NFT Marketplace on Ethereum with Polygon. So, if you haven't started from there, I suggest you do.
I'll be covering how to create a resell item feature to your marketplace, since this was an issue to me and to other developers to whom I have talked to.
First, in the NFT contract, we'll add a function to transfer the token. You may be asking why I don't just simply use ERC721's tranferFrom function. And the reason is that when I tested my functions, in some situations requests reverted with "transfer caller is not owner nor approved", even though msg.sender == ownerOf(tokenId) asserted true. So my solution was to write a custom transfer function. But feel free to share the solution for this strange bug in the comments if you know it. So this will be added to your NFT contract:
contract NFT is ERC721URIStorage {
(...)
function transferToken(address from, address to, uint256 tokenId) external {
require(ownerOf(tokenId) == from, "From address must be token owner");
_transfer(from, to, tokenId);
}
}
The "external" visibility allows only other smart contracts to call the function. And the "require" statement checks if the caller is the token's current owner.
Now, in the MarketPlace contract, you have to import the NFT contract to call the function, and it will look like this:
import "./NFT.sol";
The second change I made was to add a "creator" attribute to the MarketItem struct, so it won't lose the information about who first minted the token between transfers:
struct MarketItem {
uint256 itemId;
address nftContract;
uint256 tokenId;
address payable creator;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
That means you'll have to alter your other existing functions and add this attribute too. I also created a new event, to broadcast that the token is being sold again:
event ProductListed(
uint256 indexed itemId
);
Also, to write smaller functions and be able to reuse code, I created a modifier function to prevent that others than the contract owner do the operation:
modifier onlyItemOwner(uint256 id) {
require(
idToMarketItem[id].owner == msg.sender,
"Only product owner can do this operation"
);
_;
}
And finally, the function to (re)list the token in the marketplace:
function putItemToResell(address nftContract, uint256 itemId, uint256 newPrice)
public
payable
nonReentrant
onlyItemOwner(itemId)
{
uint256 tokenId = idToMarketItem[itemId].tokenId;
require(newPrice > 0, "Price must be at least 1 wei");
require(
msg.value == listingPrice,
"Price must be equal to listing price"
);
//instantiate a NFT contract object with the matching type
NFT tokenContract = NFT(nftContract);
//call the custom transfer token method
tokenContract.transferToken(msg.sender, address(this), tokenId);
address oldOwner = idToMarketItem[itemId].owner;
idToMarketItem[itemId].owner = payable(address(0));
idToMarketItem[itemId].seller = oldOwner;
idToMarketItem[itemId].price = newPrice;
idToMarketItem[itemId].sold = false;
_itemsSold.decrement();
emit ProductListed(itemId);
}
For testing:
const { expect } = require('chai')
const { ethers } = require("hardhat");
let market;
let nft;
let nftAddress;
let marketAddress;
beforeEach(async ()=>{
const Market = await ethers.getContractFactory("NFTMarket");
market = await Market.deploy();
await market.deployed();
marketAddress = market.address;
const NFT = await ethers.getContractFactory("NFT");
nft = await NFT.deploy(marketAddress);
await nft.deployed();
nftAddress = nft.address;
})
describe("Marketplace", () => {
(...)
it("should allow buyer to resell an owned item", async () => {
const [, creator, buyer] = await ethers.getSigners();
await nft.connect(creator).createToken("www.mytoken.com")
const listingPrice = await market.getListingPrice();
await market.connect(creator).createMarketItem(nftAddress, 1, 100, {value: listingPrice})
await market.connect(buyer).createMarketSale(nftAddress, 1, {value: 100})
await market.connect(buyer).putItemToResell(nftAddress, 1, 150, {value: listingPrice})
const item = await market.fetchSingleItem(1)
expect(item.seller).to.equal(buyer.address)
expect(item.creator).to.equal(creator.address)
})
})
And in the front-end:
export async function resellOwnedItem(id, price, signer) {
const marketContract = new ethers.Contract(
nftmarketaddress,
Market.abi,
signer
);
const listingPrice = await marketContract.getListingPrice();
const tx = await marketContract.putItemToResell(
nftaddress,
id,
ethers.utils.parseUnits(price, "ether"),
{ value: listingPrice.toString() }
);
await tx.wait();
}
I'm taking the signer as an argument here but you can also fetch it in your function as @dabit3 did:
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
And this is how I handled this task. If you found a better solution, please share in the comments. Thank you!
Top comments (12)
Thank you so much for the post, I have been trying to do this myself. Really appreciate it.
This is what I need to create a resell function, thanks Mateus. Could you upload the market.sol file? I'm getting an error when adding the "creator" attribute to other existing functions in the market.sol file and getting an error because of this. What functions did you add the "creator" attribute?
hey! there you go: github.com/mateusasferreira/nft-ma...
but I would take some parts of this code with a grain of salt as this is a work in progress (currently paused)
Yes, I will be verifying the code. Thank you so much I really appreciated it, just what I needed!
I get the message after adding your code to NFTMarket.sol ..... can't move forward with running the app. Any solution? Thanks
[{
"resource": "/C:/Users/admin/nft-marketplace/contracts/NFTMarket.sol",
"owner": "generated_diagnostic_collection_name#2",
"severity": 8,
"message": "Member \"transferToken\" not found or not visible after argument-dependent lookup in contract NFT.",
"startLineNumber": 209,
"startColumn": 9,
"endLineNumber": 209,
"endColumn": 36
}]
I got the unhandled error
TypeError: marketContract.fetchItemsCreated() is not a function
Source: pages\creator-dashboard.js (31:38)
in this line:
const data = await marketContract.fetchItemsCreated()
Great work on this, I'm using this code and but seem to be getting the below error - any ideas? I get this when attempting to mint OR buy, using an EVM based chain where the contracts are successfully written to.
code: -32000
message: "gas required exceeds allowance or always failing transaction"
[[Prototype]]: Object
message: "Internal JSON-RPC error."
[[Prototype]]: Object
anyone getting this error when trying to upload an NFT to a test network?
Hey! the code works well till the NFT is relisted for selling. But when I try to buy that relisted NFT it gives me this error..
code: -32603, message: 'Internal JSON-RPC error.'
data: {code: -32603, message: 'Error: Transaction reverted: function call failed to execute'}
Please help I have been stuck on it.. tried every solution there is.. Is there something that I'm missing?
Good job Mateus.
Amazing explanation!!
Hey, how i'm struggling with the ipfs, please can I pin IPFS to bsc
Hi Mateus!
I just wanted to thank you for the code. I think I have it working properly. Thanks a lot for sharing your solution!