DEV Community

ilija
ilija

Posted on • Updated on

Web3 backend and smart contract development for Python developers Musical NFTs part 5: writing smart contracts

We will use some OpenZeppelin contracts and that is why we need first to installed them. Brownie comes with package manager we can use for this purpose. Universal sintax is brownie pm install [ORGANIZATION]/[REPOSITORY]@[VERSION] in our case

$brownie pm install OpenZeppelin/openzeppelin-contracts@4.5.0

#check if package is properly installed
$brownie pm list
Brownie v1.19.3 - Python development framework for Ethereum

The following packages are currently installed:

OpenZeppelin
└─OpenZeppelin/openzeppelin-contracts@4.5.0
Enter fullscreen mode Exit fullscreen mode

Now in our nft_package.sol test contract try to import newly installed OpenZeppelin contract ERC721. Open nft_package.sol from ./contracts

// SPDX-License-Identifier: MIT

# Here we are importing newly installed ERC721 contract
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";

#defining compiler
pragma solidity 0.8.13;

contract NftPackage {

    uint256 public test = 10;

}
Enter fullscreen mode Exit fullscreen mode

From command line:

$brownie compile
Brownie v1.19.3 - Python development framework for Ethereum

Compiling contracts...
Solc version: 0.8.13
Optimizer: Enabled  Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Address
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
- NftPackage

Project has been compiled. Build artifacts saved at /my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
Enter fullscreen mode Exit fullscreen mode

We successfully installed and imported OpenZepplein ERC721 inside our contract and now we can start to code our Muscial NFT contract.

Idea here is to have musical NFT based on ERC721 contract with few simple functionalities like minting and adding tokenURI. Then we will inherit from few other OpenZepellin contracts like ownable (track and manage via modifier who have right to call certain functions), counters (count number of newly minted tokens) and URIStorage (linking our newly minted NFT to NFT metadata on IPFS).

I will write whole contract here and explain with inline comments what is going on (very useful tool for this kind of basic contracts and experimentation is OpenZepelinWizard)

We will deploy two main contracts: MockUSDC and MusicNFT. MockUSDC is basically ERC20 that mimic USDC on Mumbai testnet. Reason why we don't use real test USDC is fact that is very hard to obtain them on Mumbai and MockUSDC will play that role just fine. Second contract is ERC721 where we simply mint new musical NFTs and assigne new URI. Here are contracts (with explanations inline).

ERC20 MockUSDC

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

import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/ERC20.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";

contract MockUSDC is ERC20, Ownable {
        // Mint to deoployer all MUSDC tokens
        constructor() ERC20("MockUSDC", "MUSDC") {
        _mint(msg.sender, 1000000000000000 * 10 ** decimals());
    }
}
Enter fullscreen mode Exit fullscreen mode

From command line

$brownie compile

Brownie v1.19.3 - Python development framework for Ethereum

Compiling contracts...
Solc version: 0.8.21
Optimizer: Enabled  Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- MockUSDC

Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
Enter fullscreen mode Exit fullscreen mode

We have our mock token ready to be used

And here is MusicNFT contracts


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

import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/utils/Counters.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/IERC20.sol";


contract MusicNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    IERC20 public immutable USDC;

    uint256 public immutable maxNFT;

    uint256 public NFTPriceInUSDC;

    // Event to be emited ones we mint new NFT token
    event newNFTMinted(uint256 numberOfNFT, address owner);

    // Event to be emited ones new price is setted
    event newPrice(string message, uint256 newPrice);

    constructor(address _usdc, uint256 _maxNft) ERC721("MuscialNFT", "MFT") {
        // instatiating mock ERC20 token (that we previusly deployed)
        USDC = IERC20(_usdc);    
        // Defining maximum number of tokens this contract cna mint
        maxNFT = _maxNft;     
    }



    /** 
        This function is used for minting new NFTs. We need to pass token uri (uniform resource 
        Identifier) from IPFS (via Pinata).  Metadata of some NFT is basically 
        detail explanation of NFT content: characteristic, link to song in our 
        case and other relevant information. Second argument that we need to pass is number of 
        NFTs we want to buy
    */

    // For crypto buyers
    function buyNFT(string memory uri, uint256 nftCount) public {
        // Here we are cheking if desired number of NFTs is bigger then one
        require(nftCount > 0, "You have to mint at least one Track Pack NFT.");
        // Here we are cheking if number of already minted NFT is bigger then max allowed to be minted
        require(nftCount + _tokenIdCounter.current() <= maxNFT, "There aren't enough Track Pack NFTs for this drop for you to mint you amount you chose. Another drop will be available soon!");
        // Here we check if user balance in MockUSDC is bigger the number NFTs he want to buy * price of NFTs
        require(USDC.balanceOf(msg.sender) >= NFTPriceInUSDC * nftCount, "You don't have enough USDC to pay for the Track Pack NFT(s).");
        // Check if total amount of approved MOckUSDC to this contract tokens is bigger then number of NFTs * price  
        require(USDC.allowance(msg.sender, address(this)) >= NFTPriceInUSDC * nftCount, "You haven't approved this contract to spend enough of your USDC to pay for the Track Pack NFT(s).");
        // If everything goes ok then make MockUSDC tokens transfer from user account to this contract
        USDC.transferFrom(msg.sender, address(this), NFTPriceInUSDC * nftCount);
        // Take new NFT token ID
        for (uint256 x = 0; x < nftCount; x++) {
            uint256 tokenId = _tokenIdCounter.current();
            // Increment counter
            _tokenIdCounter.increment();
            // Mint new token
            _safeMint(msg.sender, tokenId);
            // Set new token URI to token ID
            _setTokenURI(tokenId, uri);
            // Emit event about succesful minting
            emit newNFTMinted(tokenId, msg.sender);
        }
    }

    // When credit card buyers buy new NFT we need to mint to custodial wallet. Same as beafore. Diffrence is: onnly owner can mint, 2. there is no need to pay in usdc
    // it is already done by credit card. 
    function createNFT(address custodialWallet, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(custodialWallet, tokenId);
        _setTokenURI(tokenId, uri);
        emit newNFTMinted(tokenId, custodialWallet);
    }
    // This function is used to set new NFT price if necessary
    function setNFTPrice(uint256 _newPrice) public onlyOwner {
        NFTPriceInUSDC = _newPrice;
        emit newPrice("New price is seted", _newPrice);
    }

    // Get token URI function
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }


    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

}

Enter fullscreen mode Exit fullscreen mode

Now we have also our second contract written. Lets compile it and see if everything works well

$brownie compile
Brownie v1.19.3 - Python development framework for Ethereum

Compiling contracts...
Solc version: 0.8.21
Optimizer: Enabled  Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721URIStorage
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Address
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- OpenZeppelin/openzeppelin-contracts@4.5.0/Counters
- OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
- MusicNFT

Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
Enter fullscreen mode Exit fullscreen mode

With this two contract we can move to deployment to Mumbai and start with integration with our Django backend.

Code can be found in github repo

Top comments (0)