Create a Collection
Estimated completion time: 1 hour.
Using our collection contract API, you can launch your very own NFT collection on Ethereum or Polygon along with a minting website without any previous blockhain experience. ✌️
What's a collection contract?
A collection contract is a feature-rich ERC721 contract that gives you access to advanced functionalties out of the box to launch your NFT collection. For example, you can use our API to launch a 10k PFP collection. For a detailed spec, see Deploy an NFT collection contract.
This tutorial will cover how to:
-
Upload your NFT assets (images) to IPFS
-
Upload a directory of metadata files to IPFS
-
Deploy a collection contract
-
Build a simple website to allow users to mint
-
Withdraw balance from the contract to the treasury (optional)
Prerequisites
- Sign up to get your free API key. Add this to Authorization header when making calls to NFTPort APIs.
- Install Node.js. This tutorial has been tested on v14 LTS but Node versions >14 should work.
Pricing
- Deploying on Polygon is free but max token supply is capped at 5000 for free tier users. Growth or Scale tier users can create collections of any size.
- If you want to mint on Ethereum mainnet, you need to be a subscribed to the Growth or Scale tier and each Ethereum deployment costs $199.
See pricing for details.
Step 1: Upload your NFT assets (images) to IPFS
In this step, you will upload your files to IPFS -- this puts your NFTs into decentralized and immutable storage. For API reference and more code samples, see Upload a file to IPFS. You should make the following API call for every image or file that is part of your collection, and save the returned ipfs_url
s.
curl --request POST \
--url 'https://api.nftport.xyz/v0/files' \
--header 'Authorization: <<apiKey>>' \
--header 'Content-Type: multipart/form-data' \
--form 'file=@/path/to/file_to_upload.png;type=image/png'
import requests
file = open("image.png", "rb")
response = requests.post(
"https://api.nftport.xyz/v0/files",
headers={"Authorization": "<<apiKey>>"},
files={"file": file}
)
const fs = require('fs');
const fetch = require('node-fetch');
const FormData = require('form-data');
const form = new FormData();
const fileStream = fs.createReadStream('image.jpg');
form.append('file', fileStream);
const options = {
method: 'POST',
body: form,
headers: {
'Authorization': '<<apiKey>>',
},
};
fetch('https://api.nftport.xyz/v0/files', options)
.then(response => {
return response.json()
})
.then(responseJson => {
// Handle the response
console.log(responseJson);
After a successful request, your response should look like this:
{
"response": "OK",
"ipfs_url": "https://ipfs.io/ipfs/QmcjGqcUYA5xVSwRb6sBpxYqED5L7avxcncnybueb4ismf",
"file_name": "NFTs.png",
"content_type": "image/png",
"file_size": 1755632,
"file_size_mb": 1.6743
}
Step 2: Upload a directory of metadata files to IPFS
You need to upload all your metadata files to a directory on IPFS. To do this, you can you can use our Upload metadata directory to IPFS API API endpoint. This endpoint takes as input an array of JSON files containing NFT metadata and uploads it into an IPFS directory. The output is an IPFS URI of a directory containing the uploaded JSON files. Save the IPFS URI of this directory -- it will be used as the base_uri
later in the tutorial for the Collection contract.
First, place all your JSON metadata files in a single folder. Assuming you're creating 5,000 NFTs, the folder should be structured like this:
Metadata/
├── 0.json
├── 1.json
├── 2.json
├── 3.json
├── 4.json
├── ...
├── 4997.json
├── 4998.json
└── 4999.json
Below is an example of how a metadata file for one NFT should look like. It needs to be in JSON format; some fields are necessary and others optional. If you are new to NFT metadata, read NFT metadata basics.
{
"name": "Lamp 1",
"description": "My amazing Lamp",
"image": "https://ipfs.io/ipfs/bafkreihqubrfvvp3vzc7tmqamfbodvcevhmnpep6wg6hc3cyrdixkrn25e",
"attributes": [
{
"trait_type": "Lamp Type",
"value": "Classic"
}
]
}
Now you can run the following script to upload all metadata files into an IPFS directory. Make sure to replace the metadata_directory_path
with the local path to your JSON metadata folder, and replace the API key with your own.
import requests
import os
from os import listdir
from os.path import join
metadata_directory_path = "Metadata" #Replace with your path
files = [f for f in listdir(metadata_directory_path) if str(join(metadata_directory_path, f)).endswith('.json')]
metadata_files = []
for metadata in files:
metadata_files.append(
("metadata_files", open(os.path.join(metadata_directory_path, metadata), "rb")))
response = requests.post(
"https://api.nftport.xyz/v0/metadata/directory",
headers={"Authorization": "<<apiKey>>"},
files=metadata_files
)
print(response.json())
const fs = require('fs');
const path = require('path')
const request = require('request');
API_KEY = '<<apiKey>>'
METADATA_DIRECTORY_PATH = 'Metadata' // Replace with your path to directory folder containing metadata json files
function isJson(filename) {
return filename.split('.').pop() === 'json'
}
function getFileStreamForJSONFiles(directory) {
const jsonArray = []
fs.readdirSync(directory).forEach(file => {
if(!isJson(file)) {
return
}
const fileData = fs.createReadStream(path.join(directory, file));
jsonArray.push(fileData)
});
return jsonArray
}
function sendRequest(metadataFileStreams, apiKey) {
const options = {
url: 'https://api.nftport.xyz/v0/metadata/directory',
headers: { 'Authorization': <<apiKey>> }
}
const req = request.post(options, function (err, resp, body) {
if (err) {
console.error('Error: ' + err);
} else {
console.log('Response: ' + body);
}
});
const form = req.form();
metadataFileStreams.forEach(file => {
form.append('metadata_files', file);
})
}
metadataFileStreams = getFileStreamForJSONFiles(METADATA_DIRECTORY_PATH)
sendRequest(metadataFileStreams, API_KEY)
Step 3. Deploy a collection contract
We're ready to deploy the contract! In the interactive box below, add your API key, set Content-Type as application/json
and fill parameters with your own values. Set metadata_updatable
to true
so that the metadata of the NFT can be changed later as required.
In the request body, set the chain you want to deploy your collection on: ethereum
(mainnet), polygon
or goerli
(testnet). If you want to deploy on Ethereum mainnet, you need to be a subscribed user. Deploying on Polygon is free for all users, with a collection size cap of 5,000 (subscribed users have unlimited collection max_supply
). Ethereum deployments cost $199 per contract for subscribed users.
Make sure you update all fields in the request with those appropriate for your contract: collection name, symbol, maximum number of tokens, royalties, etc. To set a royalty rate of 5%, set royalties_share
as 500.
For a full API reference and more code samples, see Deploy an NFT collection contract.
If you set
base_uri
now, the collection will be revealed immediately upon deployment. If you want to reveal the collection later, do the following:
- Before deployment, upload the placeholder image to IPFS using Upload a file to IPFS.
- Before deployment, upload a single placeholder metadata JSON using Upload metadata to IPFS, using the IPFS image URL you just uploaded as
file_url
.- When deploying, keep the
base_uri
empty and setprereveal_token_uri
to the URL of the metadata file you just uploaded to IPFS.- When you are ready to reveal your collection, use Update a deployed collection contract to set the
base_uri
to the location of the IPFS metadata directory created in Step 2.
Note: You can make any changes to the contract after you have deployed it, see Update a deployed collection contract. However, not all fields are mutable; notably max_supply
and mint_price
among others cannot be changed after deployment.
curl --request POST \
--url https://api.nftport.xyz/v0/contracts/collections \
--header 'Authorization: <<apiKey>>' \
--header 'Content-Type: application/json' \
--data '{
"chain": "Set this value to '\''polygon'\'' or '\''ethereum'\'' as appropriate",
"name": "CryptoLamps",
"symbol": "CLAMP",
"max_supply": 5000,
"mint_price": 0.1,
"tokens_per_mint": 10,
"royalties_share": 500,
"royalties_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"owner_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"treasury_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"public_mint_start": "2022-02-08T11:30:48+00:00",
"metadata_updatable": true,
"base_uri": "ipfs://bafybeif2sdh3a2ir5uy74ziwyhufkpd3upysrabpy22ziyeomvaql63idu/",
"prereveal_token_uri": "ipfs://bafkreiedsysj5xeyulisdjrjh37tz2y47dlwzwiwfagmqng3melxtigaie",
"presale_mint_start": "2022-02-08T11:30:48+00:00",
"presale_whitelisted_addresses": [
"0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42"
]
}'
import requests
url = "https://api.nftport.xyz/v0/contracts/collections"
payload = {
"chain": "Set this value to 'polygon' or 'ethereum' as appropriate",
"name": "CryptoLamps",
"symbol": "CLAMP",
"max_supply": 5000,
"mint_price": 0.1,
"tokens_per_mint": 10,
"royalties_share": 500,
"royalties_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"owner_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"treasury_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"public_mint_start": "2022-02-08T11:30:48+00:00",
"metadata_updatable": True,
"base_uri": "ipfs://bafybeif2sdh3a2ir5uy74ziwyhufkpd3upysrabpy22ziyeomvaql63idu/",
"prereveal_token_uri": "ipfs://bafkreiedsysj5xeyulisdjrjh37tz2y47dlwzwiwfagmqng3melxtigaie",
"presale_mint_start": "2022-02-08T11:30:48+00:00",
"presale_whitelisted_addresses": ["0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42"]
}
headers = {
"Content-Type": "application/json",
"Authorization": "<<apiKey>>"
}
response = requests.request("POST", url, json=payload, headers=headers)
print(response.text)
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json', Authorization: '<<apiKey>>'},
body: '{
"chain":"Set this value to \'polygon\' or \'ethereum\' as appropriate",
"name":"CryptoLamps",
"symbol":"CLAMP",
"max_supply":5000,
"mint_price":0.1,
"tokens_per_mint":10,
"royalties_share":500,
"royalties_address":"0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"owner_address":"0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"treasury_address":"0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"public_mint_start":"2022-02-08T11:30:48+00:00",
"metadata_updatable":true,
"base_uri":"ipfs://bafybeif2sdh3a2ir5uy74ziwyhufkpd3upysrabpy22ziyeomvaql63idu/",
"prereveal_token_uri":"ipfs://bafkreiedsysj5xeyulisdjrjh37tz2y47dlwzwiwfagmqng3melxtigaie",
"presale_mint_start":"2022-02-08T11:30:48+00:00",
"presale_whitelisted_addresses":["0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42"]
}'
};
fetch('https://api.nftport.xyz/v0/contracts/collections', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
After you've deployed the contract, it might take a minute before the transaction is confirmed and the contract is deployed on chain. From the response of the contract deployment request above you will get a transaction_hash
; you can use the Retrieve a deployed contract endpoint to retrieve your contract address given the transaction_hash
(see interactive box below). Since blockchains take time to sync, it might take up to a few minutes for your contract address to be available. You can also see your deployed collection contracts with List all your deployed collection contracts.
{
"response": "OK",
"chain": "polygon",
"transaction_hash": "0xd07b2797c08656ac26391fe9f8119c41e9bf47ce6dbd5e07eec142f2b77a7152",
"transaction_external_url": "https://polygonscan.com/tx/0xd07b2797c08656ac26391fe9f8119c41e9bf47ce6dbd5e07eec142f2b77a7152",
"owner_address": "0x5FDd0881Ef284D6fBB2Ed97b01cb13d707f91e42",
"name": "CryptoLamps"
}
You can also find the deployed contract on a block explorer like Polygonscan or Etherscan by searching for either the transaction hash or the contract address. The block explorers might not show the name and symbol of the contract until the first NFT in the contract has been minted.
Get contract address
curl --request GET \
--url 'https://api.nftport.xyz/v0/contracts/transaction_hash?chain=polygon' \
--header 'Authorization: <<apiKey>>' \
--header 'Content-Type: application/json'
import requests
url = "https://api.nftport.xyz/v0/contracts/transaction_hash"
querystring = {"chain":"polygon"}
headers = {
"Content-Type": "application/json",
"Authorization": "<<apiKey>>"
}
response = requests.request("GET", url, headers=headers, params=querystring)
print(response.text)
const options = {
method: 'GET',
headers: {'Content-Type': 'application/json', Authorization: '<<apiKey>>'}
};
fetch('https://api.nftport.xyz/v0/contracts/transaction_hash?chain=polygon', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
After a successful request, your response should look like:
{
"response": "OK",
"chain": "goerli",
"contract_address": "0x33ce68f0c15638cecf6728f914a51a05e7d9af5a",
"transaction_hash": "0xd07b2797c08656ac26391fe9f8119c41e9bf47ce6dbd5e07eec142f2b77a7152",
"error": null
}
Save the contract address: we'll be using it below for minting NFTs into this contract.
Step 4. Build a simple website to allow users to mint
Copy the following the HTML code into an index.html
file.
Download the latest web3.min.js
from here and place it in the same folder as index.html
. This gives you web3 utility to allow the user to mint NFTs from your website by calling the contract ABI.
Note: Make sure to replace the address
param in the HTML code with your contract address to allow users to mint your collection.
To enable whitelisted minting for a pre-sale, you have to generate proofs using Merkle Trees. See tutorial here.
<html>
<head>
<script src="web3.min.js"></script>
<script>
const abi = [
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "maxSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reservedSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokensPerMint",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "treasuryAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "string",
"name": "baseURI",
"type": "string"
},
{
"internalType": "bool",
"name": "metadataUpdatable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "publicMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "presaleMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "presaleMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "presaleMintStart",
"type": "uint256"
},
{
"internalType": "string",
"name": "prerevealTokenURI",
"type": "string"
},
{
"internalType": "bytes32",
"name": "presaleMerkleRoot",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "royaltiesBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "royaltiesAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.RuntimeConfig",
"name": "runtimeConfig",
"type": "tuple"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ApprovalCallerNotOwnerNorApproved",
"type": "error"
},
{
"inputs": [],
"name": "ApprovalQueryForNonexistentToken",
"type": "error"
},
{
"inputs": [],
"name": "ApprovalToCurrentOwner",
"type": "error"
},
{
"inputs": [],
"name": "ApproveToCaller",
"type": "error"
},
{
"inputs": [],
"name": "BalanceQueryForZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "MintToZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "MintZeroQuantity",
"type": "error"
},
{
"inputs": [],
"name": "OwnerQueryForNonexistentToken",
"type": "error"
},
{
"inputs": [],
"name": "TransferCallerNotOwnerNorApproved",
"type": "error"
},
{
"inputs": [],
"name": "TransferFromIncorrectOwner",
"type": "error"
},
{
"inputs": [],
"name": "TransferToNonERC721ReceiverImplementer",
"type": "error"
},
{
"inputs": [],
"name": "TransferToZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ROYALTIES_BASIS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VERSION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "availableSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "baseURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "contractURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getInfo",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "version",
"type": "uint256"
},
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "maxSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reservedSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokensPerMint",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "treasuryAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "string",
"name": "baseURI",
"type": "string"
},
{
"internalType": "bool",
"name": "metadataUpdatable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "publicMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "presaleMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "presaleMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "presaleMintStart",
"type": "uint256"
},
{
"internalType": "string",
"name": "prerevealTokenURI",
"type": "string"
},
{
"internalType": "bytes32",
"name": "presaleMerkleRoot",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "royaltiesBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "royaltiesAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.RuntimeConfig",
"name": "runtimeConfig",
"type": "tuple"
}
],
"internalType": "struct NFTCollection.ContractInfo",
"name": "info",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "maxSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reservedSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokensPerMint",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "treasuryAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.DeploymentConfig",
"name": "deploymentConfig",
"type": "tuple"
},
{
"components": [
{
"internalType": "string",
"name": "baseURI",
"type": "string"
},
{
"internalType": "bool",
"name": "metadataUpdatable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "publicMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "presaleMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "presaleMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "presaleMintStart",
"type": "uint256"
},
{
"internalType": "string",
"name": "prerevealTokenURI",
"type": "string"
},
{
"internalType": "bytes32",
"name": "presaleMerkleRoot",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "royaltiesBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "royaltiesAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.RuntimeConfig",
"name": "runtimeConfig",
"type": "tuple"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "isWhitelisted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "metadataUpdatable",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "mintingActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "prerevealTokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "presaleActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "presaleMerkleRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "presaleMint",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "presaleMintPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "presaleMintStart",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicMintPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicMintStart",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "reserveMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "reserveRemaining",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "reservedSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "salePrice",
"type": "uint256"
}
],
"name": "royaltyInfo",
"outputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "royaltyAmount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokensPerMint",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "transferAdminRights",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasuryAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "baseURI",
"type": "string"
},
{
"internalType": "bool",
"name": "metadataUpdatable",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "publicMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "presaleMintPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "presaleMintPriceFrozen",
"type": "bool"
},
{
"internalType": "uint256",
"name": "publicMintStart",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "presaleMintStart",
"type": "uint256"
},
{
"internalType": "string",
"name": "prerevealTokenURI",
"type": "string"
},
{
"internalType": "bytes32",
"name": "presaleMerkleRoot",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "royaltiesBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "royaltiesAddress",
"type": "address"
}
],
"internalType": "struct NFTCollection.RuntimeConfig",
"name": "newConfig",
"type": "tuple"
}
],
"name": "updateConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
const address = "0x33ce68f0c15638cecf6728f914a51a05e7d9af5a"; //Replace with your own contract address
</script>
<script>
async function connect() {
//Allows the user to connect to a wallet like MetaMask
if (window.ethereum) {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
window.address = accounts[0];
document.getElementById("address").textContent = accounts[0];
window.web3 = new Web3(window.ethereum);
window.contract = new web3.eth.Contract(abi, address);
loadInfo();
return true;
}
return false;
}
async function loadInfo() {
//Fetches information about the contract like mint price
window.info = await window.contract.methods.getInfo().call();
document.getElementById("price").innerText =
info.runtimeConfig.publicMintPrice + " wei";
document.getElementById("price").href =
"https://etherscan.io/unitconverter?wei=" +
info.runtimeConfig.publicMintPrice;
document.getElementById("maxAmount").innerText =
info.deploymentConfig.tokensPerMint;
}
async function mint() {
const amount = parseInt(document.getElementById("amount").value);
const value = BigInt(info.runtimeConfig.publicMintPrice) * BigInt(amount);
// Calls the contract ABI to mint NFTs
await contract.methods
.mint(amount)
.send({ from: window.address, value: value.toString() });
}
connect();
</script>
</head>
<body>
<div>
<div id="address">Wallet not connected</div>
<div>
<button id="connect" onclick="connect()">Connect your wallet</button>
</div>
<div>
<div>Minting price per token: <a id="price" target="_blank"></a></div>
<div>Maximum tokens per mint: <span id="maxAmount"></span></div>
<div>
<input
id="amount"
type="number"
step="1"
min="1"
max="10"
value="1"
/>
<button id="mint" onclick="mint()">Mint</button>
</div>
</div>
</div>
</body>
</html>
Now, create a simple web server to locally test the website. To spin up a simple HTTP server for local testing, run the following commands in a terminal, in the same directory as the HTML file:
npm install
npm install http-server
npx http-server
Here's what our minting site looks like in action:
- Upon arrival, the user will be asked to connect to his Metamask wallet.
- The user selects the amount of tokens they will mint.
-
Once the user presses the mint button, the contract ABI is called from the user side and the user's metamask wallet gets triggered.
-
The user confirms the transaction in Metamask.
Voila! The NFTs have now been minted and transferred to the user's wallet!
Once at least one NFT has been minted in your collection, you can find the collection on OpenSea by searching for the contract_address
using the search box.
Step 5: Withdraw balance from the contract to the treasury (optional)
Note: The minting price paid by the user will be transferred to the balance and can be withdrawn to the treasury_address
at any time. To do this, you have to call the contract ABI. See function below:
// To withdraw minting fees to the treasury address
// @dev Callable by admin (owner) roles only
function withdrawFees() external onlyRole(ADMIN_ROLE) {
_deploymentConfig.treasuryAddress.sendValue(address(this).balance);
}
This can be done very easily using a block explorer like Polygonscan (for Polygon) or Etherscan (for Goerli).
Note: This operation can only be done by the owner wallet.
a) Go to your contract page on Polygonscan (enter your contract address in the search). Now, go to the Contract
section at the bottom and select Write Contract
.
b) Click on the Connect to web3
button. Here, you'll have to connect your wallet (for example using MetaMask). Once you're connected you'll see a green light along with your wallet address.
c) Go the function which says withdrawFees
and click on the Write
button. This will transfer the funds from the contract to the treasury address.
d) Pay the gas fee for the transaction.
Example collection
You can find an example of a collection launched using this API here:
- OpenSea: CryptoLamps
- PolygonScan:
0x33ce68f0c15638cecf6728f914a51a05e7d9af5a
You're ready to rock'n'roll with NFTPort to launch your NFT collection 😎
For more help, check out this tutorial by codeSTACKr which shows you how to create your collection on Polygon. You can follow the same tutorial to deploy on Ethereum; to do so replace the references to chain=polygon
in API calls with chain=ethereum
.
Join the Community for Support and to Build Together
Join our Discord community if you need support from our team and a space to discuss NFT related topics, voice feature requests, and engage with other like-minded NFT developers.
Updated almost 2 years ago