How to Set the Price of an NFT Within the Smart Contract

06/12/237 min read

Mantleby Mantle

Developers

Training

Tutorials

Web3

How to Set the Price of an NFT Within the Smart Contract

Gm! Thanks for the amazing response to the NFT Series! Based on the feedback and queries, we also wanted to introduce how you can set the price of the NFT in the same contracts deployed during the walkthrough of the NFT Series. As most of us know, there are already marketplaces like OpenSea and Magic Eden where we can deploy, list, and set prices for NFTs. But, it's also important to know how prices can be set within the smart contract.

Setting an NFT Price In-Contract

The subsequent segment isn't a ready-made solution. As part of the fourth step in the Part 1 series for the guide to creating NFTs, we'd need to modify the Solidity code to accept payment during the minting process. This implies that any frontend ethers.js logic governing the minting process would need to incorporate the 'msg.value' parameter to facilitate the exchange of Mantle Network's token, $MNT. Note: To get $MNT tokens, please use Mantle's Official Faucet.

This fee pattern is completely decentralized, since it takes place in contract and bakes the fee mechanism into the minting process itself. To implement a price on minting, you need to alter your smart contract to include the following behavior. As a high-level summary, an NFT minting price can be enacted by making the mint function payable and requiring the user to pay a particular amount of ETH before triggering the transfer of the NFT to the buyer.

Here's a sample piece of code for this type of minting process:

function mintToken(address to, uint256 tokenId, string uri) public virtual payable {

require(msg.value >= 10, "Not enough $MNT sent; check price!");

_mint(to, tokenId);
_setTokenURI(tokenId, uri);
}

function mintToken(address to, uint256 tokenId, string uri) public virtual payable

  • To allow users to pay ETH to mint an NFT, we need to make this function both public and payable. public functions can be called internally or via messages to allow anyone to interact with the function. (We don't want this function to be only callable by the contract's owner, since it would lock out prospective buyers!)

require(msg.value >= 10, "Not enough ETH sent; check price!");

  • This require statement requires that the payable function receive at least 10 wei, else the function will fail and revert. The msg.value parameter is the ETH value of amount sent in alongside the mint function.

_mint(to, tokenId);

  • This calls the mint function included in OpenZepplin's ERC721 contract file and instantiates/transfers the selected NFT to the buyer.

_setTokenURI(tokenId, uri);

  • This calls the _setTokenURIfunction included in OpenZepplin's ERC721 contract file and sets the NFT URI to a particular endpoint.

Note: The above code isn't complete as it doesn't include tokenID -which might result in an error.

Here's how the complete mintNFT() function should look like.

function mintNFT(
address to,
string memory tokenUri
) public payable virtual {
require(msg.value >= 10, "Not enough BIT sent; check price!");

// Increment the token ID before minting a new NFT
_tokenIds.increment();

// Get the current token ID
uint256 newTokenId = _tokenIds.current();

_mint(to, newTokenId);
_setTokenURI(newTokenId, tokenUri);
}
}

With this implementation, the tokenId is incremented automatically each time you mint a new NFT. The Counters.Counter is a utility from the OpenZeppelin library that provides a secure way of incrementing/decrementing numbers. We initialize _tokenIds with this Counter and increment it before minting a new token. The current() function is used to fetch the latest tokenId.

Upon complete changes, MyNFT.sol would look like ⬇️

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

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyNFT is ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

constructor() ERC721("MyNFT", "NFT") {}

function mintNFT(address recipient, string memory tokenURI)
public
onlyOwner
returns (uint256)
{
_tokenIds.increment();

uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);

return newItemId;
}
}

Let's try deploying a fresh contract.

npx hardhat run scripts/deploy.js --network mantle-testnet

If you've followed the steps correctly, the new contract should be successfully deployed.

Outpur 1

Copy the contract address and add it to the contractAddress in mint-nft.js under scripts. (refer to the screenshot below)

Output 2

That's not all! We still have a few changes to make sure our new NFT gets minted only for a specific price.

Additional Changes

// Call mintNFT function
async function mintNFT() {
let nftTxn = await myNftContract.mintNFT(signer.address, tokenUri, { value: ethers.utils.parseEther("10") });
await nftTxn.wait();
console.log(`NFT Minted! Check it out at: https://explorer.testnet.mantle.xyz/tx/${nftTxn.hash}`);
}

Remember, here in the code ethers.utils.parseEther("0.1"), 10 represents the amount of Ether being sent with the transaction, and in this context, it can be interpreted as the price of the NFT. mint-nft.js

require("dotenv").config();
const { JsonRpcProvider, Signer } = require("@ethersproject/providers");
const ethers = require("ethers");

// Create a JsonRpcProvider instance
const rpcUrl = "https://rpc.testnet.mantle.xyz";
const chainId = 5001;
const provider = new JsonRpcProvider(rpcUrl, chainId);

// Create a signer using the private key from the environment variable
const privateKey = process.env.PRIV_KEY;
const signer = new ethers.Wallet(privateKey, provider);

// Get contract ABI and address
const abi = require("../artifacts/contracts/MyNFT.sol/MyNFT.json").abi;
const contractAddress = "0xE699708e1a6D3Ca7Cd27771F80FdF6FA313c7D2D";

// Create a contract instance
const myNftContract = new ethers.Contract(contractAddress, abi, signer);

// Get the NFT Metadata IPFS URL
const tokenUri =
"https://gateway.pinata.cloud/ipfs/QmUXZLiXHD4RpWYNopiKHMV6FWTzkc2FdYpVLsDTD1inF8";

// Call mintNFT function
async function mintNFT() {
let nftTxn = await myNftContract.mintNFT(signer.address, tokenUri, { value: ethers.utils.parseEther("10") });
await nftTxn.wait();
console.log(`NFT Minted! Check it out at: https://explorer.testnet.mantle.xyz/tx/${nftTxn.hash}`);
}

mintNFT()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

Save the changes and now mint the nft using "node scripts/mint-nft.js".

Output 3

Now wait for a few seconds for the transaction to show up on the explorer and Voilà! We can see the NFT's value was 10 $MNT, the same as we mentioned in the code above and you can cross-check your wallet balance to make sure :)

Output 4

Common Misunderstanding

Now, require(msg.value >= 10, "Not enough $MNT sent; check price!") is a conditional statement in Solidity that checks whether the condition inside the parenthesis is true before the function continues to execute.

msg.value is a special variable in Solidity that contains the number of wei (the smallest unit of Ether) sent with the call. So, msg.value >= 10 is checking if the amount of Ether (expressed in wei) sent with the transaction is at least 10 wei.

If the condition is not met, the function will stop executing and revert the transaction. In this case, the error message "Not enough $MNT sent; check price!" will be emitted.

Please note that 1 Ether is equivalent to 1e18 wei, so 10 wei is a very small amount of Ether (much less than 0.1 Ether). If you want the price to be 0.1 Ether, you should adjust the require statement to compare msg.value with the equivalent amount in wei, like require(msg.value >= ethers.utils.parseEther("0.1"), "Not enough Ether sent; check price!"). However, this requires you to use the ethers library in Solidity which might not be desirable or necessary, so it's common to specify the required amount directly in wei. If you wanted to require 0.1 Ether, that would be require(msg.value >= 100000000000000000, "Not enough Ether sent; check price!").

Hope this add-on for the existing 3-part NFT series was exciting! There are many different options for implementing fees into minting contracts. The one listed above is one of the most simple, but many protocols also use fee patterns that are significantly more complex. Thanks again if you've read this long! Catch you in the next one! 👋🏼


Join the Community