Skip to content

Instantly share code, notes, and snippets.

@yupeshekhonov
Last active January 16, 2023 13:25
Show Gist options
  • Save yupeshekhonov/c7b5ab6b15314087c04e181cbd60639a to your computer and use it in GitHub Desktop.
Save yupeshekhonov/c7b5ab6b15314087c04e181cbd60639a to your computer and use it in GitHub Desktop.
How to work with Unique Network via EVM

How to mint an NFT on Unique Network using Ethereum technologies

In this tutorial, we will walk through creating and deploying an ERC-721 smart contract on the Opal test network using Hardhat and Solidity. Also, we will mint an NFT using a library.

Prerequisites

You need to have the following installed and prepared to start this guide:

  • Node.js, version > 14.
  • npm or yarn.
  • Metamask account.

At the moment, we have Opal Testnet. Its websocket URL is wss://ws-opal.unique.network (rpc endpoint - https://rpc-opal.unique.network).

In Polkadot apps, you can check it using this link.

You can use @unique2faucet_opal_bot in Telegram to get some OPL.

Initialize your project

Create a folder and initialize a project.

mkdir unq-nft
cd unq-nft
npm init - y
yarn init -y 

Add the Hardhat to your project and initialize it:

npm install --save-dev hardhat
yarn add -D hardhat

npx hardhat
yarn hardhat

To check if everything works properly, please run:

npx hardhat test
yarn hardhat test

This command will prompt to select the project type, please choose TypeScript and answer yes to all questions. Just in case, make sure that the additional plugins are installed by running these commands:

npm install --save-dev @nomicfoundation/hardhat-toolbox
yarn add --dev @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers

Before we start

Please note that in Unqiue Network, there is no need to write smart contracts as it is usually required in Ethereum networks.

Unique Network provides emulated smart contracts. This means that if you call a specific address where a smart contract is supposed to be, Unique Network will pretend that it has a smart contact there. Thus, you can access our node using Ethereum technologies, and the node will respond.

This advantage allows using smart contracts just as libraries from any .js, .ts or even .sol file. The following guide will demonstrate how we call the smart contracts from the solidity-interfaces library.

Import smart contracts and write a new one

Let's install the solidity-interfaces library that provides the smart contracts.

npm i @unique-nft/solidity-interfaces
yarn add @unique-nft/solidity-interfaces

After this, we will write a new smart contract that will use this library. Please pay attention that we just import a couple of .sol files and use them. We will create a new file in the /contracts folder with the CollectionManager.sol name.

pragma solidity ^0.8.17;

import {CollectionHelpers, CollectionHelpersEvents} from  "@unique-nft/solidity-interfaces/contracts/CollectionHelpers.sol";
import {UniqueNFT, CrossAddress} from "@unique-nft/solidity-interfaces/contracts/UniqueNFT.sol";

contract CollectionManager is CollectionHelpersEvents {
    CollectionHelpers helpers = CollectionHelpers(0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F);

    function createCollection(
        address owner,
        address managerContract,
        string calldata name,
        string calldata description,
        string calldata symbol,
        string calldata baseURI
    ) public payable virtual returns (address){
        address collectionAddress = helpers.createNFTCollection{value: helpers.collectionCreationFee()}(name, description, symbol);

        helpers.makeCollectionERC721MetadataCompatible(collectionAddress, baseURI);

        UniqueNFT collection = UniqueNFT(collectionAddress);

        collection.addCollectionAdminCross(CrossAddress(managerContract, 0));
        collection.changeCollectionOwnerCross(CrossAddress(owner, 0));

        return collectionAddress;
    }
}

⚠️ Make sure that the version defined above (^0.8.17) is the same as the version defined in the hardhat.config.ts file.

Connect the network and the Metamask account

Install the dotenv package in your project directory by running:

npm install dotenv --save
yarn add dotenv --save

Then, create a .env file in the root directory of our project, and add your Metamask private key and the network RPC to it.

Follow these instructions to export your private key from Metamask.

Your .env should look like this:

RPC_OPAL="https://rpc-opal.unique.network"
PRIVATE_KEY = "your-metamask-private-key"

After this, update your hardhat.config.ts so that our project knows about all of these values.

import dotenv from 'dotenv'
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

dotenv.config()
const { RPC_OPAL, PRIVATE_KEY } = process.env;

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.17",
    settings: {
      metadata: {
        // Not including the metadata hash
        // https://github.com/paulrberg/solidity-template/issues/31
        bytecodeHash: "none",
      },
      optimizer: {
        enabled: true,
        runs: 800,
      },
      viaIR : true,
    },
  },
  networks: {
    hardhat: {},
    opal: {
      url: RPC_OPAL,
      accounts: [`${PRIVATE_KEY}`]
    },
  }
};

export default config;

Write a deployment script

Now, when our contract is written and our configuration file is ready, it is time to write the contract deploy script.

Create the file deploy.ts with the following:

const {ethers} = require('hardhat');

async function main() {
  // Grab the contract factory
  const CollectionManager = await ethers.getContractFactory("CollectionManager");

  // Start deployment, returning a promise that resolves to a contract object
  const collectionManager = await CollectionManager.deploy(); // Instance of the contract
  console.log("Contract deployed to address:", collectionManager.address);
}

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

Deploy the contract

We are finally ready to deploy our smart contract! For this, run the following command line:

npx hardhat run scripts/deploy.ts --network opal
yarn hardhat run scripts/deploy.ts --network opal

You should then see something like:

Contract deployed to address: 0xB07956E26FDF1b215aC89AE21F822F8AB9Be9A27

Create a minting script

Now, we can create a new file in the /scripts folder called createCollection.ts.

import dotenv from 'dotenv'
import {ethers} from "hardhat"
import {CollectionHelpersFactory, UniqueNFTFactory} from "@unique-nft/solidity-interfaces"
import {CollectionManager__factory} from '../typechain-types'
import {Address} from "@unique-nft/utils";

dotenv.config()

const TOKEN_IPFS_CIDS = {
    1: 'QmZ8Syn28bEhZJKYeZCxUTM5Ut74ccKKDbQCqk5AuYsEnp',
    2: 'QmZVpSsjvxev3C8Dv4E44fSp8gGMP6aoLMp56HmZi5Wkxh',
    3: 'QmZMo8JDB9isA7k7tr8sFLXYwNJNa51XjJinkLWcc9vnta',
    4: 'QmV7fqfJBozrc7VtaHSd64GvwNYqoQE1QptaysenTJrbpL',
    5: 'QmSK1Zr6u2f2b8VgaFgz9CY1NR3JEyygQPQjJZaAA496Bh',
    6: 'QmafTK2uFRuLyir2zJpLSBMercq2nDfxtSiMWXL1dbqTDn',
    7: 'QmXTMYJ3rKeTCaQ79QQPe2EYcpVFbHr3maqJCPGcUobS4B',
    8: 'QmQa97BYq9se73AztVF4xG52fGSBVB1kZKtAtuhYLHE1NA',
}

async function main() {
    const myEthAddress = '0xb4d6A98aa8CD5396069c2818Adf4ae1A0384b43a'
    // Getting a substrate mirror. You must have tokes on it.
    console.log(Address.mirror.ethereumToSubstrate(myEthAddress))

    // define a provider
    const provider = ethers.provider

    // Creating a signer
    const privateKey = process.env.PRIVATE_KEY
    // @ts-ignore
    const wallet = new ethers.Wallet(privateKey, provider)
    const contractAddress = '0xaD2CEBe3768eE109bc0586E741E96130fc21134c'

    // @ts-ignore
    const collectionHelpers = await CollectionHelpersFactory(wallet, ethers)

    // Creating a contract instance
    const contract = await ethers.getContractFactory('CollectionManager')
    const deployer = await contract.deploy()
    const collectionManager = await deployer.deployed()
    console.log(`Contract address found: ${collectionManager.address}`)

    // Creating a new collection
    let newCollection = await collectionManager.createCollection(
            myEthAddress,
            myEthAddress,
            'My new collection',
            'This collection is for testing purposes',
            'CL',
            'https://ipfs.unique.network/ipfs/',
            {
                value: await collectionHelpers.collectionCreationFee()
            }
            )

    const transactionReceipt = await newCollection.wait()
    const collectionAddress = transactionReceipt.events?.[0].args?.collectionId as string
    const collectionId = Address.collection.addressToId(collectionAddress)
    console.log(`Collection created!`)
    console.log(`Address: ${collectionAddress} , id: ${collectionId}`)

    // Minting NFTs
    const collection = await UniqueNFTFactory(collectionAddress, wallet, ethers)

    const txMintToken = await (await collection.mintWithTokenURI(
         wallet.address, 
        'https://ipfs.unique.network/ipfs/' + TOKEN_IPFS_CIDS['1']
    )).wait()
    const tokenId = txMintToken.events?.[1].args?.tokenId.toNumber()
    const tokenUri = await collection.tokenURI(tokenId)
    console.log(`Successfully minted token #${tokenId}, it's URI is: ${tokenUri}`)

    /* Minting NFTs for all cids 
    for (let cid in tokenIpfsCids) {
      const txMintToken = await (await collection.mintWithTokenURI(wallet.address, cid)).wait()
      const tokenId = txMintToken.events?.[0].args?.tokenId.toNumber()
      const tokenUri = await collection.tokenURI(tokenId)
      console.log(`Successfully minted token #${tokenId}, it's URI is: ${tokenUri}`)
    }*/
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

To run the script, please execute the following commands:

npx hardhat run scripts/createCollection.ts --network opal
yarn hardhat run scripts/createCollection.ts --network opal

You should get the similar result, but with your values:

5DVgiNh1Go8ESa18xEirA4uV3zpra59awPGQjJTmCrqYMNtM
Contract address found: 0x3664F6c1178E19Bb775b597d6584CaA3B88a1C35
Collection created!
Address: 0x17c4e6453Cc49AAaAeACA894E6d9683E00000006 , id: 6

Cool! We created a collection and minted an NFT in it.

❗ We would like to draw your attention, that this example shows two use cases. First, that we created our own smart contact and deployed it, just like Ethereum users usually do. So, you can use the familiar approach to work with Unique Network. Second, we minted an NFT using a library that we imported. So, as we already mentioned in the beginning of this guide, you may not deploy your contracts, but just use ready ones.

Troubleshooting:

At some point, you may face the issue with typings. For example, this error:

Error: Cannot find module './Lock_factory'

In this case, you can do the following to fix it:

npx hardhat clean 
npx hardhat compile 

yarn hardhat clean
yarn hardhat compile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment