How to create a resell token functionality in your NFT marketplace smart contract
Mateus Ferreira
Posted on December 7, 2021
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!
Posted on December 7, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 7, 2021