// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc721// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";import"@openzeppelin/contracts/utils/Counters.sol";contractMyNFTisERC721URIStorage{usingCounters for Counters.Counter;Counters.Counterprivate_tokenIds;constructor() ERC721("MyNFT","MNFT") {}functionmintNFT(addressrecipient,stringmemorytokenURI)publicreturns(uint256){_tokenIds.increment();uint256newItemId=_tokenIds.current();_mint(recipient,newItemId);_setTokenURI(newItemId,tokenURI);returnnewItemId;}}
import { Contract, ethers } from "ethers";
import { getContractAt } from "@nomiclabs/hardhat-ethers/internal/helpers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { env } from "./env";
import { getProvider } from "./provider";
export function getContract(
name: string,
hre: HardhatRuntimeEnvironment
): Promise<Contract> {
const WALLET = new ethers.Wallet(env("ETH_PRIVATE_KEY"), getProvider());
return getContractAt(hre, name, env("NFT_CONTRACT_ADDRESS"), WALLET);
}
export function env(key: string): string {
const value = process.env[key];
if (value === undefined) {
throw `${key} is undefined`;
}
return value;
}
import { ethers } from "ethers";
export function getProvider(): ethers.providers.Provider {
return ethers.getDefaultProvider("ropsten", {
alchemy: process.env.ALCHEMY_API_KEY,
});
}
import { ethers } from "ethers";
import { env } from "./env";
import { getProvider } from "./provider";
export function getWallet(): ethers.Wallet {
return new ethers.Wallet(env("ETH_PRIVATE_KEY"), getProvider());
}
import { ethers, waffle } from "hardhat";
import { Contract, Wallet } from "ethers";
import { expect } from "chai";
import { TransactionResponse } from "@ethersproject/abstract-provider";
import sinon from "sinon";
import { deployTestContract } from "./test-helper";
import * as provider from "../lib/provider";
describe("MyNFT", () => {
const TOKEN_URI = "http://example.com/ip_records/42";
let deployedContract: Contract;
let wallet: Wallet;
beforeEach(async () => {
sinon.stub(provider, "getProvider").returns(waffle.provider);
[wallet] = waffle.provider.getWallets();
deployedContract = await deployTestContract("MyNFT");
});
async function mintNftDefault(): Promise<TransactionResponse> {
return deployedContract.mintNFT(wallet.address, TOKEN_URI);
}
describe("mintNft", async () => {
it("emits the Transfer event", async () => {
await expect(mintNftDefault())
.to.emit(deployedContract, "Transfer")
.withArgs(ethers.constants.AddressZero, wallet.address, "1");
});
it("returns the new item ID", async () => {
await expect(
await deployedContract.callStatic.mintNFT(wallet.address, TOKEN_URI)
).to.eq("1");
});
it("increments the item ID", async () => {
const STARTING_NEW_ITEM_ID = "1";
const NEXT_NEW_ITEM_ID = "2";
await expect(mintNftDefault())
.to.emit(deployedContract, "Transfer")
.withArgs(
ethers.constants.AddressZero,
wallet.address,
STARTING_NEW_ITEM_ID
);
await expect(mintNftDefault())
.to.emit(deployedContract, "Transfer")
.withArgs(
ethers.constants.AddressZero,
wallet.address,
NEXT_NEW_ITEM_ID
);
});
it("cannot mint to address zero", async () => {
const TX = deployedContract.mintNFT(
ethers.constants.AddressZero,
TOKEN_URI
);
await expect(TX).to.be.revertedWith("ERC721: mint to the zero address");
});
});
describe("balanceOf", () => {
it("gets the count of NFTs for this address", async () => {
await expect(await deployedContract.balanceOf(wallet.address)).to.eq("0");
await mintNftDefault();
expect(await deployedContract.balanceOf(wallet.address)).to.eq("1");
});
});
});
import { deployTestContract, getTestWallet } from "./test-helper";
import { waffle, run } from "hardhat";
import { expect } from "chai";
import sinon from "sinon";
import * as provider from "../lib/provider";
describe("tasks", () => {
beforeEach(async () => {
sinon.stub(provider, "getProvider").returns(waffle.provider);
const wallet = getTestWallet();
sinon.stub(process, "env").value({
ETH_PUBLIC_KEY: wallet.address,
ETH_PRIVATE_KEY: wallet.privateKey,
});
});
describe("deploy-contract", () => {
it("calls through and returns the transaction object", async () => {
sinon.stub(process.stdout, "write");
await run("deploy-contract");
await expect(process.stdout.write).to.have.been.calledWith(
"Contract address: 0x610178dA211FEF7D417bC0e6FeD39F05609AD788"
);
});
});
describe("mint-nft", () => {
beforeEach(async () => {
const deployedContract = await deployTestContract("MyNFT");
process.env.NFT_CONTRACT_ADDRESS = deployedContract.address;
});
it("calls through and returns the transaction object", async () => {
sinon.stub(process.stdout, "write");
await run("mint-nft", { tokenUri: "https://example.com/record/4" });
await expect(process.stdout.write).to.have.been.calledWith(
"TX hash: 0xd1e60d34f92b18796080a7fcbcd8c2b2c009687daec12f8bb325ded6a81f5eed"
);
});
});
});
import sinon from "sinon";
import chai from "chai";
import sinonChai from "sinon-chai";
import { ethers as hardhatEthers, waffle } from "hardhat";
import { Contract, Wallet } from "ethers";
chai.use(sinonChai);
afterEach(() => {
sinon.restore();
});
export function deployTestContract(name: string): Promise<Contract> {
return hardhatEthers
.getContractFactory(name, getTestWallet())
.then((contractFactory) => contractFactory.deploy());
}
export function getTestWallet(): Wallet {
return waffle.provider.getWallets()[0];
}
import("@nomiclabs/hardhat-ethers");
import("@nomiclabs/hardhat-waffle");
import dotenv from "dotenv";
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
const argv = JSON.parse(env("npm_config_argv"));
if (argv.original !== ["hardhat", "test"]) {
require('dotenv').config();
}
import("./tasks/nft");
import { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: "0.8.6",
};
export default config;
AVAILABLE TASKS:
deploy-contract Deploy NFT contract
mint-nft Mint an NFT
mintNft
✓ calls through and returns the transaction object (60ms)
MyNFT
mintNft
✓ emits the Transfer event (60ms)
✓ returns the new item ID
✓ increments the item ID (57ms)
✓ cannot mint to address zero
balanceOf
✓ gets the count of NFTs for this address
6 passing (2s)
✨ Done in 5.66s.