Skip to main content

13 posts tagged with "NFT"

View All Tags

· 4 min read
Loubna Benzaama

Welcome to our NFT generation and deployment tutorial! In just two clicks, you can create and deploy your own valuable NFTs. Our tutorial will guide you through the process of generating and deploying unique NFTs quickly and easily.

This tutorial will guide you on how to set up your environment to create an AI-generated NFT collection. You will learn how to configure your .env file, run the backend and frontend servers, and ensure that you have enough faucet funds.

You will need

  • A Starton API KEY
  • A Wallet with currency of your network
  • A DALL-E 2 API KEY

What is DALL-E 2?

DALL-E 2 is an artificial intelligence (AI) model developed by OpenAI. DALL-E 2 is designed to generate images from textual descriptions.

It is capable of generating complex and detailed images. It can create images that are up to 1024x1024 pixels in size, and can generate multiple objects and textures within a single image. DALL-E 2 can also create images that are animated and can be interacted with.

INSTALLING THE PROJECT

info

The project has been set up using a client-side and a server-side so that your API key is not exposed on client. You can also use the server-side to connect a database for metrics.

CREATING STARTON PROJECT

  1. Clone the project from Starton AI generative NFT collection GitHub.

SETTING UP ENVIRONMENT VARIABLES

Before you start setting up the project, you need to set the environment variables.

  1. In the backend folder of the project, set up an .env according to .env.example:
API_HOST=localhost
API_PORT=8000

## Put on the following line your Starton API key (available at https://app.starton.com)
STARTON_API_URL='https://api.starton.io/v3'
STARTON_API_KEY=

## Put on the following line your Signer wallet address
STARTON_SIGNER_WALLET=

## Put on the following line your OpenAI API key (available at https://platform.openai.com/account/api-keys)
OPEN_AI_API_KEY=
  1. In the frontend set up your .env file according to .env.example :
NEXT_PUBLIC_BACK_URL=http://localhost:8000

SETTING UP THE SERVER SIDE

Here we would like to enter the data needed to generate and deploy your collection as well as entering your API, so that you can authenticate safely to the API.

  1. Go to the backend folder of the project.
  2. Run the command yarn install to install the required dependencies.
  3. Start the server by running the command yarn dev.

SETTING UP OUR CLIENT SIDE

On the client side, we will set up all the information of your smart contract to connect your project to Web3Auth.

  1. Go to the frontend folder of the project.
  2. Run the command yarn install to install the required dependencies.

RUNNING THE PROJECT

To run your project, you will need to run commands both on server and client side.

  1. In server, run the command yarn dev
  2. In client, run the command yarn dev

Visit http://localhost:3000 to test your project.

GENERATING AND DEPLOYING YOUR COLLECTION

Once started, visit http://localhost:3000 to enter the following parameters:

  1. Enter the receiver wallet of the NFT (where you want to send the NFTs).
  2. Select the network on which you want to deploy your NFTs. If you are testing, then use the testnet network, and if you are ready to deploy, use the mainnet network (between Polygon, Avalanche, Binance, and Ethereum).
  3. Choose the number of NFTs that you want to create for your collection.
  4. Enter the prompt that will generate the images for your NFTs. You can use different prompts to create unique and personalised NFTs.
  5. Click Generate to display the assets for your NFTs.
  6. Review the generated assets.
  7. Once you are satisfied with the images, click Deploy.

Congratulations! You just deployed an NFT collection of images generated by AI.

· 4 min read
Loubna Benzaama

In this tutorial, you will learn how to sell an NFT in an Auction.

Deploy an ERC721

You will start by deploying an ERC721 that will be the base of the NFT you want to sell in an auction.

Upload Contract-level metadata on IPFS

To enter a name on the marketplace’s dashboard and perceive fees when someone sells one of our NFTs, we need to implement the contract-level metadata.

Here are the values we will use:

{
"name": "My Super NFT on Auction",
"description": "You’ve never seen NFTs this beautiful.",
"image": "",
"external_link": "",
"seller_fee_basis_points": 100,
"fee_recipient": "PUT YOUR ADDRESS HERE"
}

We now have the content of our metadata, we need to upload it on IPFS.

To upload our files on IPFS we will now use the Starton IPFS pinning service.

As the contract-level metadata only needs to be uploaded once, we can directly do it from our dashboard.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Enter JSON content.
  5. Enter a name for the file.
  6. Click Upload.

Once done, a column “CID” appears with a value for our file. We will use this data for the Contract Uri Suffix of our smart contract!

Upload a file on IPFS

We also use IPFS to store the content that will be referenced in our deployed contract. We do not store the content directly on blockchain as it is too heavy and would induce a very high cost. The best solution is to store it somewhere else and only store a reference on-chain.

  1. Go to IPFS.
  2. Click Upload.
  3. Select File(s).
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Once our image uploaded, let's upload its metadata so that we can call it from our smart contract function.

Upload the metadata of your content on IPFS

info

Consult the metadata standard format on your marketplace's documentation.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Everything we need to sell your NFT is an auction is done.

Deploy the base contract

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 NFT Smart Contract template.
  2. Enter:
  • a name for your contract,
  • a wallet to sign the transaction,
  • a blockchain / network on which to deploy, here BNB testnet.
  • the parameters of our contract. For more information on parameters, check out the Deploying a Smart Contract.

For example, we can call our contract “Best NFTs on BNB” and deploy it on the BNB Testnet network.

The following constructor parameters are:

  • definitiveName: This name stored on blockchain, we will use “Best NFTs on BNB”.
  • initialTokenUri: This is the public URL that contains the metadata of the collection. Here it starts with ipfs://ipfs/,
  • initialContractUri: The URI of the metadata that will be used to give more details about the description.
  • initialOwnerOrMultisigContract: The address that will own the NFT Collection.

We can finally deploy our contract!

Deploy an ERC721 Auction

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 Auction template.
  2. Enter:
  • definitiveTokenAddress: The token address of the ERC721 that you want to sell.
  • definitiveFeeReceiver: The address that will receive all the price paid to mint the NFTs.
  • initialStartingPrice: The initial price that the NFT will be sold for.
  • initialMinPriceDifference: The price increase that a user needs to at least put to bid on top of the current auction winner.
  • initialStartTime: The time when the sale will begin and users can bid.
  • initialEndTime: The time when the sale will end and users couldn't bid anymore.
  • initialTokenURI: The URI that will be append in the end of the base token URI for the token that will be minted.

Interact with your contract

You can now bid, claim, or start a new auction. The mint of your NFT is made when the bidder which has won the auction claims it.

· 3 min read
Loubna Benzaama

In this tutorial, you will learn how to sell an NFT collection in an Auction.

You will need

  • a deployed ERC1155 NFT contract

Deploy an ERC1155 Auction Sale smart contract

You can start by accessing the list of templates in the Smart contract section. Then, you can deploy the smart contract from code.

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/from-template", {
network: "",
signerWallet: "",
templateId: "ERC1155_AUCTION_SALE",
name: "",
description: "",
params: [
"", //definitiveTokenAddress
"", //definitiveFeeReceiver
"", //initialStartingPrice
"", //initialMinPriceDifference
"", //initialStartTime
"", //initialEndTime
"", //initialTokenID
"", //initialTokenAmount
],
})
.then((response) => {
console.log(response.data)
})
  1. Select the ERC1155 Auction template.
  2. Enter:
  • definitiveTokenAddress: The token address of the ERC1155 that you want to sell.
  • definitiveFeeReceiver: The address that will receive all the price paid to mint the NFTs.
  • initialStartingPrice: The initial price that the NFT will be sold for.
  • initialMinPriceDifference: The price increase that a user needs to at least put to bid on top of the current auction winner.
  • initialStartTime: The time when the sale will begin and users can bid.
  • initialEndTime: The time when the sale will end and users couldn't bid anymore.
  • initialTokenURI: The URI that will be append in the end of the base token URI for the token that will be minted.
  • initialTokenID: The token id of the token that will be minted for the auction.
  • initialTokenAmount: The amount of tokens that will be minted for the auction.

Giving ownership to your sale contract

For the sale contract to be able to mint new tokens, you are going to need to grant the newly created contract the MINTER_ROLE over the base contract deployed.

You will need:

  • the address of the base ERC1155 contract
  • the address of the Sale contract
  • the data of the Minter Role (in bytes)

Retrieving the MINTER_ROLE

First, let's get the minter role:

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/{network}/{YOUR_BASE_CONTRACT}/read", {
functionName: "MINTER_ROLE",
params: [],
})
.then((response) => {
console.log(response.data)
})

Granting the minter role to your Sale contract

To grant the minter role to your contract, you can use the following request:

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: { "x-api-key": "PUT HERE YOUR API KEY" },
})

axiosInstance
.post("/v3/smart-contract/${network}/${YOUR SMART CONTRAT ADDRESS}/call", {
functionName: "grantRole(bytes32,address)",
params: [
"0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6", // MINTER_ROLE
"", // account
],
signerWallet: "",
speed: "average",
})
.then((response) => {
console.log(response.data)
})
  • account: The whitelist sale contract address

Interact with your contract

You can now bid, claim, or start a new auction. The mint of your NFT is made when the bider which has won the auction claims it.

· 4 min read
Loubna Benzaama

In this tutorial, you will learn how to sell an NFT.

Deploy an ERC721

You will start by deploying an ERC721 that will be the base of the NFT you want to sell.

Upload Contract-level metadata on IPFS

To enter a name on the marketplace’s dashboard and perceive fees when someone sells one of our NFTs, we need to implement the contract-level metadata.

Here are the values we will use:

{
"name": "My Super NFT for Sale",
"description": "You’ve never seen NFTs this beautiful.",
"image": "",// the URI of your image, for example ipfs://CID_OF_YOUR_IMG
"external_link": "",
"seller_fee_basis_points": 100,
"fee_recipient": "PUT YOUR ADDRESS HERE"
}

We now have the content of our metadata, we need to upload it on IPFS.

To upload our files on IPFS we will now use the Starton IPFS pinning service.

As the contract-level metadata only needs to be uploaded once, we can directly do it from our dashboard.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Enter JSON content.
  5. Enter a name for the file.
  6. Click Upload.

Once done, a column “CID” appears with a value for our file. We will use this data for the Contract Uri Suffix of our smart contract!

Upload a file on IPFS

We also use IPFS to store the content that will be referenced in our deployed contract. We do not store the content directly on blockchain as it is too heavy and would induce a very high cost. The best solution is to store it somewhere else and only store a reference on-chain.

  1. Go to IPFS.
  2. Click Upload.
  3. Select File(s).
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Once our image uploaded, let's upload its metadata so that we can call it from our smart contract function.

Upload the metadata of your content on IPFS

info

Consult the metadata standard format on your marketplace's documentation.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Everything we need to sell your NFT is an auction is done.

Deploy the base contract

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 NFT Smart Contract template.
  2. Enter:
    • a name for your contract,
    • a wallet to sign the transaction,
    • a blockchain / network on which to deploy, here BNB testnet.
    • the parameters of our contract. For more information on parameters, check out the Deploying a Smart Contract.

For example, we can call our contract “Best NFTs on BNB” and deploy it on the BNB Testnet network.

The following constructor parameters are:

  • definitiveName: This name stored on blockchain, we will use “Best NFTs on BNB”.
  • initialTokenUri: This is the public URL that contains the metadata of the collection. Here it starts with ipfs://ipfs/,
  • initialContractUri: The URI of the metadata that will be used to give more details about the description.
  • initialOwnerOrMultisigContract: The address that will own the NFT Collection.

We can finally deploy our contract!

Deploy an ERC721 Sale

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 Sale template.
  2. Enter:
  • definitiveTokenAddress: The token address of the ERC721 that you want to sell.
  • definitivePrice: The price that the NFTs will be sold for.
  • definitiveStartTime: The time when the sale will begin and users can mint tokens.
  • definitiveEndTime: The time when the sale will end and users couldn't mint anymore tokens.
  • definitiveMaxTokensPerAddress: The maximum amount of tokens that can be minted by a single address.
  • definitiveMaxSupply: The maximum amount of tokens that can be minted during the sale.
  • definitiveFeeReceiver: The address that will receive all the price paid to mint the NFTs.

Interact with your contract

Once your NFT is sold, you can withdraw the amount received during the sale by interacting with the smart contract.

  1. On the smart contract page, click Interact.
  2. Select the withdraw function.
  3. Click Run.

The amount is transferred from the address of the smart contract to the address of the fee receiver.

· 5 min read
Loubna Benzaama

In this tutorial, you will learn how to sell an NFT in an Auction.

You will need

  • The token address of the ERC721 that you want to sell.
  • The address that will receive the amount paid for the NFTs.
  • The initial price offered for the NFT.
  • The minimum bid increment to place a bid on top of the current maximum bid.
  • The time at which the sale will begin and end, where users can bid. Timestamp in seconds.
  • The URI that will be append in the end of the base token URI for the token that will be minted.

Deploy an ERC721

You will start by deploying an ERC721 that will be the base of the NFT you want to sell in an auction.

Upload Contract-level metadata on IPFS

To enter a name on the marketplace’s dashboard and perceive fees when someone sells one of our NFTs, we need to implement the contract-level metadata.

Here are the values we will use:

{
"name": "My Super NFT on Auction",
"description": "You’ve never seen NFTs this beautiful.",
"image": "",
"external_link": "",
"seller_fee_basis_points": 100,
"fee_recipient": "PUT YOUR ADDRESS HERE"
}

We now have the content of our metadata, we need to upload it on IPFS.

To upload our files on IPFS we will now use the Starton IPFS pinning service.

As the contract-level metadata only needs to be uploaded once, we can directly do it from our dashboard.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Enter JSON content.
  5. Enter a name for the file.
  6. Click Upload.

Once done, a column “CID” appears with a value for our file. We will use this data for the Contract Uri Suffix of our smart contract!

Upload a file on IPFS

We also use IPFS to store the content that will be referenced in our deployed contract. We do not store the content directly on blockchain as it is too heavy and would induce a very high cost. The best solution is to store it somewhere else and only store a reference on-chain.

  1. Go to IPFS.
  2. Click Upload.
  3. Select File(s).
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Once our image uploaded, let's upload its metadata so that we can call it from our smart contract function.

Upload the metadata of your content on IPFS

info

Consult the metadata standard format on your marketplace's documentation.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Select content.
  5. Enter a name for the file.
  6. Click Upload.

Everything we need to sell your NFT is an auction is done.

Deploy the base contract

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 NFT Smart Contract template.
  2. Enter:
    • a name for your contract,
    • a wallet to sign the transaction,
    • a blockchain / network on which to deploy, here BNB testnet.
    • the parameters of our contract. For more information on parameters, check out the Deploying a Smart Contract.

For example, we can call our contract “Best NFTs on BNB” and deploy it on the BNB Testnet network.

The following constructor parameters are:

  • definitiveName: This name stored on blockchain, we will use “Best NFTs on BNB”.
  • initialTokenUri: This is the public URL that contains the metadata of the collection. Here it starts with ipfs://ipfs/,
  • initialContractUri: The URI of the metadata that will be used to give more details about the description.
  • initialOwnerOrMultisigContract: The address that will own the NFT Collection.

We can finally deploy our contract!

Deploy an ERC721 Auction

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 Auction template.
  2. Enter:
  • definitiveTokenAddress: The token address of the ERC721 that you want to sell.
  • definitiveFeeReceiver: The address that will receive all the price paid to mint the NFTs.
  • initialStartingPrice: The initial price that the NFT will be sold for.
  • initialMinPriceDifference: The price increase that a user needs to at least put to bid on top of the current auction winner.
  • initialStartTime: The time when the sale will begin and users can bid.
  • initialEndTime: The time when the sale will end and users couldn't bid anymore.
  • initialTokenURI: The URI that will be append in the end of the base token URI for the token that will be minted.

Interact with your contract

You can now bid, claim, or start a new auction. The mint of your NFT is made when the bidder which has won the auction claims it.

· 5 min read
Loubna Benzaama

If you want to mint more than one edition of your NFT, you'll need to create a smart contract using an ERC1155-flavored template.

When minting an NFT collection, you make multiple identical editions of the content. This is one type of collection. Multiple digital items will be issued. They will feature identical content with a different, unique token ID for each NFT. In this case, you will have a unique token ID for each digital item issued with its unique data.

You will need:

  • a wallet to fund the creation of your contract
  • the URI of the metadata of your collection Read more.
  • the URI of the content of the NFT. You can upload your file on IPFS. Read more.
  • the address of the initial owner
  • the network on which you want to deploy

In this tutorial, we will:

Deploying the smart contract

This is where we use the values we've listed earlier:

  • Name: "My first NFT collection"
  • Description: "This is my first collection of NFT "
  • Definitive Name: "myFirstCollection"
  • Initial Token URI: the link to the content of your NFT
  • Initial Contract URI: the link to the metadata of your contract
  • Initial Owner of Multi Sig Contract: The address of the owner of the contract
const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {"x-api-key": "PUT HERE YOUR API KEY"},
})

axiosInstance
.post("/v3/smart-contract/from-template", {
network: "",
signerWallet: "",
templateId: "ERC1155_META_TRANSACTION",
name: "My first NFT collection",
description: "This is my first collection of NFT ",
params: [
"myFirstCollection",
"", // Initial Token URI
"", // Initial Contract URI
"", // Initial Owner of Multi Sig Contract
],
speed: "average",
})
.then((response) => {
console.log(response.data)
})

Minting the first NFT of your collection

You will need the following information:

  • Wallet: the signer wallet
  • To: the wallet receiving your NFT
  • Id: the identifer of the NFT within the collection
  • Amount: amount to mint
    const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com/",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance.post(
"/v3/smart-contract/polygon-amoy/0xc900546AA43C88aBcAF70c20448DF45917c8363A/call",
{
functionName: "mint(address,uint256,uint256,bytes)",
params: [
"", // the signer wallet
"", // the receiving wallet
"1",// the ID
"1" // the amount
],
signerWallet: "",
speed: "average"
}).then((response) => {
console.log(response.data)
})

· 4 min read
Loubna Benzaama

In this tutorial, we will create your own token. The fixed supply version of this standard guarantees no token will ever be created after the initial emission. Fungible tokens are token from which the value of each token is equal to another.

You will need:

  • a wallet address with funds: You can use your default Starton wallet, at creation Starton provides you with faucets.
  • definitiveName: The name of your smart contract which will be reflected on-chain.
  • definitiveSymbol: The symbol of your smart contract which will be reflected on-chain
  • definitiveSupply: The total amount of tokens that will ever be minted.
  • initialOwnerOrMultisigContract: The address that will own the ERC20 contract.

In this tutorial, we will:

Deploying the Smart contract from our template

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/from-template", {
network: "", // The blockchain network on which you want to deploy your smart contract
signerWallet: "", // The address of the signer wallet
templateId: "ERC20_META_TRANSACTION",
name: "", // The name of the contract on Starton
description: "", // The description of the contract on Starton
params: [
"", // The name of your smart contract which will be reflected on-chain.
"", // The symbol of your smart contract which will be reflected on-chain
"", // The total amount of tokens that will ever be minted.
"", // The address that will own the ERC20 contract.
],
})
.then((response) => {
console.log(response.data)
})

Transferring your first token

You will need the following information:

  • Wallet: the signer wallet
  • To: the wallet receiving your transfer
  • Amount: amount to transfer
const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/YOUR_SMART_CONTRACT_NETWORK/YOUR_SMART_CONTRACT_ADDRESS/call", {
functionName: "transfer(address,uint256)",
params: [
"", // Enter the wallet receiving tokens.
"", //amount of token transferred
],
signerWallet: "", // Enter the wallet from which tokens will be transferred.
speed: "average",
})
.then((response) => {
console.log(response.data)
})

Congratulations! You've transferred your first token.

· 4 min read
Loubna Benzaama

In this tutorial, we will create your own token. The mintable supply version of this standard guarantees no token will ever be created after the initial emission. Fungible tokens are token from which the value of each token is equal to another.

You will need:

  • a wallet address with funds: You can use your default Starton wallet, at creation Starton provides you with faucets.
  • definitiveName: The name of your smart contract which will be reflected on-chain.
  • definitiveSymbol: The symbol of your smart contract which will be reflected on-chain
  • initialSupply: The initial amount of tokens that will be minted.
  • initialOwnerOrMultisigContract: The address that will own the ERC20 contract.

In this tutorial, we will:

Deploying the Smart contract from our template

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/from-template", {
network: "", // The blockchain network on which you want to deploy your smart contract
signerWallet: "", // The address of the signer wallet
templateId: "ERC20_MINT_META_TRANSACTION",
name: "", // The name of the contract on Starton
description: "", // The description of the contract on Starton
params: [
"", // The name of your smart contract which will be reflected on-chain.
"", // The symbol of your smart contract which will be reflected on-chain
"", // The total amount of tokens that will ever be minted.
"", // The address that will own the ERC20 contract.
],
})
.then((response) => {
console.log(response.data)
})

Congrats on deploying your smart contract.

Process your first token transfer

You will need the following information:

  • Wallet: the signer wallet
  • To: the wallet receiving your transfer
  • Amount: amount to transfer
const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/YOUR_SMART_CONTRACT_NETWORK/YOUR_SMART_CONTRACT_ADDRESS/call", {
functionName: "transfer(address,uint256)",
params: [
"", // Enter the wallet receiving tokens.
"", //amount of token transferred
],
signerWallet: "", // Enter the wallet from which tokens will be transferred.
speed: "average",
})
.then((response) => {
console.log(response.data)
})

Congratulations! You've transferred your first token.

· 9 min read
Loubna Benzaama

In this tutorial, we will see how we can deploy a smart contract and interact with it to mint NFTs on BNB Chain testnet in 6 steps. We will use random images uploaded on IPFS (a distributed file storage system) and assigned to an BNB Chain address.

You will:

  • Upload the contract-level metadata
  • Use a template from Starton for our smart contract (ERC721).
  • Deploy it with Starton from the dashboard. To deploy contracts from code, see Deploying a smart contract from code.
  • Upload the content of our NFTs and their metadata on IPFS.
  • Interact with our smart contract using Starton API in order to mint the NFTs and give it to a specific address.

If you feel stuck or have questions, feel free to get in touch in our Discord. We’ll be glad to help you!

Upload the contract level metadata on IPFS

To enter a name on the marketplace’s dashboard and perceive fees when someone sells one of our NFTs, we need to implement the contract-level metadata.

Here are the values we will use:

{
name: "My Super NFT on BNB",
description: "You’ve never seen NFTs this beautiful.",
image: "URI of your image", // This will be used as the image of your collection
external_link: "",
seller_fee_basis_points: 100,
fee_recipient: "PUT YOUR ADDRESS HERE",
}

We now have the content of our metadata, we need to upload it on IPFS.

To upload our files on IPFS we will now use the Starton IPFS pinning service.

As the contract-level metadata only needs to be uploaded once, we can directly do it from our dashboard.

  1. Go to IPFS.
  2. Click Upload.
  3. Select JSON.
  4. Enter JSON content.
  5. Enter a name for the file.
  6. Click Upload.

Once done, a column “CID” appears with a value for our file. We will use this data for the Contract Uri Suffix of our smart contract!

Choose a smart contract template

Several standards of smart contracts have been developed for NFTs.

The most used ones are ERC721 and the ERC1155. If you want to see in more details the differences between the two standards, read The Difference Between ERC721 vs ERC1155.

Today, we will use the ERC721 smart contract template.

Deploy the contract with Starton

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 template.
  2. Enter:
    • a name for your contract,
    • a wallet to sign the transaction,
    • a blockchain / network on which to deploy, here BNB testnet.
    • the parameters of our contract. For more information on parameters, check out the Deploying a Smart Contract.

For example, we can call our contract “Best NFTs on BNB” and deploy it on the BNB Testnet network.

The following constructor parameters are:

  • Name: This name stored on blockchain, we will use “Best NFTs on BNB”.
  • Symbol: The symbol that will be displayed on blockchain explorers for example. We’ll use “BNFTBNB”.
  • Base Uri: This corresponds to the root of the url that will be used to find the content. We’ll use “ipfs://ipfs/“ as we store the content on IPFS.
  • Owner Or Multi Sig Contract: This is the address of the owner of the smart contract. We will put our Starton account address that can be found in the “Wallets” section and should be the one chosen at the top as well.
  • Contract Uri Suffix: This corresponds to the CID of your contract-level metadata on IPFS. This is needed for example if you want to perceive fees when your NFTs are sold on OpenSea.

We can finally deploy our contract!

With our smart contract deployed, you are redirected on the page where we will interact with our contract.

Minting an NFT

The process of minting a new NFT and sending it to an address goes in three steps:

  • We upload the content of our NFT on IPFS (as it is too heavy to be stored on-chain) and get the CID of the content.
  • We upload a metadata object as a JSON file on IPFS as we do not reference the content directly in the contract. Instead, we put the CID of the content in a metadata object that we upload on IPFS.
  • We call the function “mint” of our smart contract, giving the CID of our metadata object and the address that will receive the NFT.

You can choose to mint NFT from code or from Starton's interface.

Minting an NFT from code

You will see next how to upload dynamically our images on IPFS from code with the API.

Prepare our connection to the Starton API

const axios = require("axios")
const FormData = require("form-data")
const starton = axios.create({
baseURL: "https://api.starton.com/v3",
headers: {
"x-api-key": "YOUR_STARTON_API_KEY",
},
})

caution

Do not forget the replace the x-api-key value by your own! It is needed to authenticate yourself with our API.

You can find your API keys and generate new ones in the API section.

Upload an image on IPFS with the Starton API

We’ll also use IPFS to store the content that will be referenced in our deployed contract. We do not store the content directly on blockchain as it is too heavy and would induce a very high cost. The best solution is to store it somewhere else and only store a reference on-chain.

We can create a simple function like this one:

// The image variable should be a buffer
async function uploadImageOnIpfs(image, name) {
let data = new FormData()
data.append("file", image, name)
data.append("isSync", "true")

const ipfsImg = await starton.post("/ipfs/file", data, {
maxBodyLength: "Infinity",
headers: { "Content-Type": `multipart/form-data; boundary=${data._boundary}` },
})
return ipfsImg.data
}

By calling this function, providing our image as a buffer as a parameter, we should get back an object containing our image’s CID which we will use next in the metadata object.

Upload the metadata json of our NFT

Consult the metadata standard format on your marketplace's documentation. We can define a new function using our image’s CID to upload the metadata on IPFS:

async function uploadMetadataOnIpfs(imgCid) {
const metadataJson = {
name: `A Wonderful NFT`,
description: `Probably the most awesome NFT ever created !`,
image: `ipfs://ipfs/${imgCid}`,
}
const ipfsMetadata = await starton.post("/ipfs/json", {
name: "My NFT metadata Json",
content: metadataJson,
isSync: true,
})
return ipfsMetadata.data
}

Feel free to change the name and description that suit your needs.

Mint the NFT on the smart contract using the metadata’s CID

Finally, we can call our smart contract “mint” function with the receiver’s address and the CID of the metadata we just uploaded:

const SMART_CONTRACT_NETWORK = "binance-testnet"
const SMART_CONTRACT_ADDRESS = ""
const WALLET_IMPORTED_ON_STARTON = ""
async function mintNft(receiverAddress, metadataCid) {
const nft = await starton.post(`/smart-contract/${SMART_CONTRACT_NETWORK}/${SMART_CONTRACT_ADDRESS}/call`, {
functionName: "mint",
signerWallet: WALLET_IMPORTED_ON_STARTON,
speed: "low",
params: [receiverAddress, metadataCid],
})
return nft.data
}

You will need to modify the SMART_CONTRACT_ADDRESS and WALLET_IMPORTED_ON_STARTON variables so they match your contract and your wallet.

Assemble everything to complete the flow

We can now make the complete flow in only a few lines of code.

Notice that you need to define the receiver of the NFT and set the imgBuffer variable.

const RECEIVER_ADDRESS = ""
const imgBuffer = ""
const ipfsImg = await uploadImageOnIpfs(imgBuffer, "filename.png")
const ipfsMetadata = await uploadMetadataOnIpfs(ipfsImgData.cid)
const nft = await mintNft(RECEIVER_ADDRESS, ipfsMetadata.cid)

Results

Annnnnd it’s done! Congratulations!

Once all of this is executed, the content should be on IPFS, and associated to the given address in our ERC721 contract.

You can check our NFT on the market element marketplace. Use your contract address to modify the following link: https://testnets.element.market/assets/bsctest/YOUR_CONTRACT_ADDRESS/0

Conclusion

We have seen in this tutorial how to upload NFTs on a decentralised file system, how to deploy an ERC721 smart contract using Starton, how to make it compatible with marketplaces standards and how we can dynamically mint the NFTs from code to send them to people!

We hope you liked this tutorial and that you will follow along in this epic journey of making Web3 the new standard for Internet! We are very eager to see what you can build with NFTs. Do not hesitate to share what you’ve done!

· 9 min read
Loubna Benzaama

In this tutorial, we will see how we can deploy a smart contract and interact with it to mint NFTs dynamically from code. We will use random images uploaded on IPFS (a distributed file storage system) and assigned to an Ethereum address.

You will :

  • Use a template from Starton for our smart contract (ERC721).
  • Deploy it with Starton from the dashboard. To deploy contracts from code, see Deploying a smart contract from code.
  • Upload the content of our NFTs and their metadata on IPFS.
  • Interact with our smart contract using Starton API in order to mint the NFTs and give it to a specific address.

If you feel stuck or have questions, feel free to get in touch in our Discord. We’ll be glad to help you!

Choose a smart contract template

Several standards of smart contracts have been developed for NFTs.

The most used ones are ERC721 and the ERC1155. The main big difference between the two is that in an ERC721, every NFT is unique which means you will have to reference the content for each of them. Meanwhile the ERC1155 enables you to create “collections” where there are several copies of the same NFT.

The ERC721, which is easier to use, still can be used to upload several copies of the same content, but is less optimised for this use case than the ERC1155. If you want to see in more details the differences between the two standards, read The Difference Between ERC721 vs ERC1155.

Today, we will use the ERC721 smart contract template.

We’ll also use IPFS to store the content that will be referenced in our deployed contract. We do not store the content directly on blockchain as it is too heavy and would induce a very high cost. The best solution is to store it somewhere else and only store a reference on-chain.

Deploy the contract with Starton

We will deploy the contract only once, so we will do it directly from Starton’s dashboard.

We can access the list of templates in the Smart contract section.

  1. Select the ERC721 template.
  2. Enter:
  • a name for your contract,
  • a wallet to sign the transaction,
  • a blockchain / network on which to deploy,
  • the parameters of our contract. For more information on parameters, check out the Deploying a Smart Contract.

For example, we can call our contract “My Super NFTs” and deploy it on the Polygon amoy network.

The following constructor parameters are:

  • Name: This name stored on blockchain, we will keep “My Super NFTs”.
  • Symbol: The symbol that will be displayed on blockchain explorers for example. We’ll use “MSNFT”.
  • Base Uri: This corresponds to the root of the url that will be used to find the content. We’ll use “ipfs://ipfs/“ as we store the content on IPFS.
  • Owner Or Multi Sig Contract: This is the address of the owner of the smart contract. We will put our Starton account address that can be found in the “Wallets” section and should be the one chosen at the top as well.
  • Contract Uri Suffix: This corresponds to the IPFS cid of your contract level metadata. This is needed for example if you want to perceive fees when your NFTs are sold on OpenSea.

To be able to fill this field we need to upload a JSON file that respects OpenSea’s specification for the contract level metadata on IPFS.

Lost on the Contract Uri Suffix part? Let’s see this in more details so we can finish the deployment of our contract.

Upload the contract level metadata on IPFS

We need to implement OpenSea’s specification the contract level metadata.

This enables us to add a name on the OpenSea’s dashboard and perceive fees when someone sells one of our NFTs.

Here are the values we will use:

{
"name": "My Super NFTs",
"description": "You’ve never seen NFTs this beautiful.",
"image": "",
"external_link": "",
"seller_fee_basis_points": 100,
"fee_recipient": "PUT YOUR ADDRESS HERE"
}

We now have the content of our metadata, we need to upload it on IPFS.

On IPFS, the content is not referred using an address like we are used to with urls, but by the content’s value.

Let’s see the difference: When we query some content based on location we implicitly ask: "Give me the content that is located at https://… no matter what it is that you find".

Whereas when we query some content based on its value on IPFS we implicitly ask: "Find the content on the network with the hash XXXXXXX and give it to me, no matter who where it is".

With the location based approach we are specific on the “How” and not on the “What” while with the content based approach it is the opposite. Using the content based approach, we are sure we get the content we want, without it being altered, replaced or infected as otherwise the hash would have changed as well.

And it is a game changer as we do no longer need to rely on trust!

The hash of the content is called a Content IDentifier (CID) on IPFS.

And when we will upload our contract-level metadata on IPFS we will get a CID back, which is the value we need to put as the Contract Suffix Uri in our smart contract.

To upload our files on IPFS we will now use the Starton IPFS pinning service.

As the contract-level metadata only needs to be uploaded once, we can directly do it from our dashboard Once done, a column “CID” appears with a value for our file that starts with “Qm”. This is the value we need for the Contract Uri Suffix of our smart contract! We can finally deploy our contract!

With our smart contract deployed, we are redirected on the page to interact with our contract.

We won’t interact with it from the dashboard but you can click on the smart contract address at the top to see our contract in the blockchain explorer. You will see next how to upload dynamically our images on IPFS from code with the API.

Minting an NFT

The process of minting a new NFT and sending it to an address goes in three steps:

  • We upload the content on IPFS (as it is too heavy to be stored on-chain) and get the CID of the content.
  • We upload a metadata object as a JSON file on IPFS as we do not reference the content directly in the contract. Instead, we put the CID of the content in a metadata object that we upload on IPFS.
  • We call the function “mint” of our smart contract, giving the CID of our metadata object and the address that will receive the NFT.

Prepare our connection to the Starton API

const axios = require("axios")
const FormData = require("form-data")

const starton = axios.create({
baseURL: "https://api.starton.com/v3",
headers: {
"x-api-key": "YOUR_STARTON_API_KEY",
},
})

Do not forget to replace the x-api-key value by your own! It is needed to authenticate yourself with our API.

You can find your API keys and generate new ones in the API section.

Upload an image on IPFS with the Starton API

We can create a simple function like this one:

// The image variable should be a buffer
async function uploadImageOnIpfs(image, name) {
let data = new FormData()
data.append("file", image, name)
data.append("isSync", "true")

const ipfsImg = await starton.post("/ipfs/file", data, {
maxBodyLength: "Infinity",
headers: { "Content-Type": `multipart/form-data; boundary=${data._boundary}` },
})
return ipfsImg.data
}

By calling this function, providing our image as a buffer as a parameter, we should get back an object containing our image’s CID which we will use next in the metadata object.

Upload the metadata json of our NFT

Consult the metadata standard format on OpenSea documentation. We can define a new function using our image’s CID to upload the metadata on IPFS:

async function uploadMetadataOnIpfs(imgCid) {
const metadataJson = {
name: `A Wonderful NFT`,
description: `Probably the most awesome NFT ever created !`,
image: `ipfs://ipfs/${imgCid}`,
}
const ipfsMetadata = await starton.post("/ipfs/json", {
name: "My NFT metadata Json",
content: metadataJson,
isSync: true,
})
return ipfsMetadata.data
}

Feel free to change the name and description that suit your needs.

Mint the NFT on the smart contract using the metadata’s CID

Finally, we can call our smart contract “mint” function with the receiver’s address and the CID of the metadata we just uploaded:

const SMART_CONTRACT_NETWORK = "polygon-amoy"
const SMART_CONTRACT_ADDRESS = ""
const WALLET_IMPORTED_ON_STARTON = ""

async function mintNft(receiverAddress, metadataCid) {
const nft = await starton.post(`/smart-contract/${SMART_CONTRACT_NETWORK}/${SMART_CONTRACT_ADDRESS}/call`, {
functionName: "mint",
signerWallet: WALLET_IMPORTED_ON_STARTON,
speed: "low",
params: [receiverAddress, metadataCid],
})
return nft.data
}

You will need to modify the SMART_CONTRACT_ADDRESS and WALLET_IMPORTED_ON_STARTON variables so they match your contract and your wallet.

Assemble everything to complete the flow

We can now make the complete flow in only a few lines of code.

Notice that you need to define the receiver of the NFT and set the imgBuffer variable.

const RECEIVER_ADDRESS = ""
const imgBuffer = ""

async function uploadMint() {
const ipfsImg = await uploadImageOnIpfs(imgBuffer, "filename.png")
const ipfsMetadata = await uploadMetadataOnIpfs(ipfsImgData.cid)
const nft = await mintNft(RECEIVER_ADDRESS, ipfsMetadata.cid)
}

Annnnnd it’s done! Congratulations!

Once all of this is executed, the content should be on IPFS, and associated to the given address in our ERC721 contract. You should be able to see the NFTs on OpenSea or via this link https://testnets.opensea.io/collection/my-super-nfts.

Conclusion

We have seen in this tutorial how to upload NFTs on a decentralised file system, how to deploy an ERC721 smart contract using Starton, how to make it compatible with OpenSea’s standards and how we can dynamicaly mint the NFTs from code to send them to people!

We hope you liked this tutorial and that you will follow along in this epic journey of making Web3 the new standard for Internet! We are very eager to see what you can build with NFTs. Do not hesitate to share what you’ve done!

You can also deploy your smart contract from our web application.

· 18 min read
Loubna Benzaama

caution

This tutorial is the continuation of the How to create a JWT-based authentication with NestJS tutorial. We recommend you to do it first before starting this one.

Find the full project in our Github repository.

Prerequisites

Before we begin, make sure that you have the following installed on your machine:

We will use MongoDB for our database. You can create a free instance on Mongo Atlas. Make sure to retrieve the connection URL from your dashboard.

Setting up your environment

Installing Nestjs CLI

To install Nestjs CLI, run:

yarn global add @nestjs/cli

Installing Dependencies

  • axios: A promise based HTTP client for Node.js
  • nodemailer: A package that allow easy email sending
yarn add axios nodemailer

Dev dependencies

  • @types/.*: provide the necessary typings for you to use libraries or modules in your TypeScript code with type checking and IntelliSense support

Update the schema.prisma file

In the last tutorial we have generated a schema.prisma file inside the prisma directory. Update it to add different collections:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
password String
publicAddress String @unique
items Item[] @relation()
sentTransfers Transfer[] @relation("sentTransfers")
receivedTransfers Transfer[] @relation("receivedTransfers")
createdAt DateTime @default(now())
}

model Collection {
id String @id @default(auto()) @map("_id") @db.ObjectId
contractAddress String @unique
name String @unique
description String
items Item[]
nextTokenId Int
}

model Item {
id String @id @default(auto()) @map("_id") @db.ObjectId
collection Collection @relation(fields: [collectionId], references: [id])
collectionId String @db.ObjectId
tokenId String @unique
ownerAddress String @unique
owner User? @relation(fields: [ownerId], references: [id])
ownerId String? @db.ObjectId
transfers Transfer[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Transfer {
id String @id @default(auto()) @map("_id") @db.ObjectId
item Item @relation(fields: [itemId], references: [id])
itemId String @db.ObjectId
from String
fromUser User @relation("sentTransfers", fields: [fromUserId], references: [id])
fromUserId String @db.ObjectId
to String
toUser User @relation("receivedTransfers", fields: [toUserId], references: [id])
toUserId String @db.ObjectId
txHash String @unique
createdAt DateTime @default(now())
}

model RevokedToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
jti String @unique
}

User

The *User* collection is defined to store user information. It has the following fields:

  • id: The unique identifier for the user, generated automatically.
  • email: The user's email address, which must be unique.
  • password: The user's password.
  • publicAddress: The user's public address on the blockchain.
  • items: An array of Item objects that the user owns.
  • sentTransfers: An array of Transfer objects representing transfers that the user has sent.
  • receivedTransfers: An array of Transfer objects representing transfers that the user has received.
  • createdAt: The date and time the user was created, generated automatically.

Collection

The *Collection* collection is defined to store information about NFT collections. It has the following fields:

  • id: The unique identifier for the collection, generated automatically.
  • contractAddress: The public address of the contract that manages the collection on the blockchain.
  • name: The name of the collection, which must be unique.
  • description: A description of the collection.
  • items: An array of Item objects representing the NFTs in the collection.
  • nextTokenId: The ID of the next NFT to be created in the collection.

Item

The *Item* collection is defined to store information about NFTs. It has the following fields:

  • id: The unique identifier for the NFT, generated automatically.
  • collection: The collection that the NFT belongs to.
  • collectionId: The ID of the Collection that the NFT belongs to.
  • tokenId: The ID of the NFT on the blockchain.
  • ownerAddress: The public address of the current owner of the NFT on the blockchain.
  • owner: An array of User objects representing the users that own the NFT.
  • ownerId: An array of ids of the Users that own the NFT.
  • transfers: An array of Transfer objects representing the transfers that the NFT has been involved in.
  • createdAt: The date and time the NFT was created, generated automatically.
  • updatedAt: The date and time the NFT was last updated.

Transfer

The *Transfer* collection is defined to store information about transfers of NFTs. It has the following fields:

  • id: The unique identifier for the transfer, generated automatically.
  • item: The Item object representing the NFT that was transferred.
  • itemId: The id of the Item that was transferred.
  • from: The public address of the user who sent the NFT.
  • fromUser: The User object representing the user who sent the NFT.
  • fromUserId: The id of the User who sent the NFT.
  • to: The public address of the user who received the NFT.
  • toUser: The User object representing the user who received the NFT.
  • toUserId: The id of the User who received the NFT.
  • txHash: The transaction hash of the transfer on the blockchain.
  • createdAt: The date and time the transfer was created, generated automatically.

RevokedToken

The *RevokedToken* collection is defined to store information about revoked JWT tokens. It has the following fields:

  • id: The unique identifier for the revoked token, generated automatically.
  • jti: The "JWT ID" of the revoked token, which must be unique.

Updating the Sign-up route

In the last tutorial our users registered themselves to our app with an email and a password. Since we want to monitor when they receive an NFT, we need to ask them for their public key at registration.

Create a new SignUp.dto.ts in the src/user directory:

import { IsEmail, IsEthereumAddress, IsStrongPassword } from 'class-validator';

export default class SignUpDto {
@IsEmail()
email: string;

@IsStrongPassword()
password: string;

@IsEthereumAddress()
publicAddress: string;
}

This DTO includes a public address field, which allows users to provide their public EVM address. We will use this address to identify when a user receives a new NFT.

Update the DTO type in the controller:

@Post('auth/sign-up')
async signUp(
@Body() newUser: SignUpDto,
@Res() res: Response,
): Promise<void> {
try {
const { user, token }: { user: Partial<User>; token: string } =
await this.userService.signUp(newUser);

delete user.password;

res.cookie('jwt', token, { httpOnly: true });

res.status(201).send({ user: user });
} catch (err: unknown) {
throw new InternalServerErrorException(err);
}
}

Creating a Starton module

To simplify development, we will create a StartonModule. This module will wrap API calls into utility methods.

nest g module starton
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import {
cryptoquartzCollectionAddress,
network,
signerWallet,
} from 'src/utils/constants';

@Injectable()
export class StartonService {
private readonly instance = axios.create({
baseURL:
'https://api.starton.com/v3/smart-contract/' +
network +
'/' +
cryptoquartzCollectionAddress +
'/',
headers: {
'x-api-key': process.env.STARTON_API_KEY,
},
});

async initTransfer(from: string, to: string, tokenId: string) {
await this.instance.post('call', {
signerWallet,
functionName: 'safeTransferFrom',
params: [from, to, tokenId, 1, '0x00'],
});
}

async initMint(to: string, tokenId: string) {
await this.instance.post('call', {
signerWallet,
functionName: 'mint',
params: [to, tokenId, 1, '0x00'],
});
}

async initBurn(address: string, tokenId: string) {
await this.instance.post('call', {
signerWallet,
functionName: 'burn',
params: [address, tokenId, '0x00'],
});
}
}

First, we define an axios instance as a member of the StartonService class. This is convenient because we can configure the instance with a baseURL and headers, which prevents us from repeating ourselves in subsequent code.

Next, we define three wrappers for common API calls: transfers, mints, and burns:

initTransfer

Initiate an NFT transfer by making a safeTransferFrom function call to the smart-contract

initMint

Initiate an NFT mint by making a mint function call to the smart-contract

initBurn

Initiate an NFT burn by making a burn function call to the smart-contract

Now that your have your StartonService, export it in a module:

nest g module starton
import { Module } from '@nestjs/common';
import { StartonService } from './starton.service';

@Module({
imports: [],
controllers: [],
providers: [StartonService],
exports: [StartonService],
})
export class StartonModule {}

Creating an EmailModule

We will encapsulate the functions responsible for sending emails in a service that will be exported from the EmailModule.

nest g service email
nest g module email
import { Injectable } from '@nestjs/common';
import { createTransport } from 'nodemailer';

/*
|--------------------------------------------------------------------------
| MAILING SERVICE
|--------------------------------------------------------------------------
*/
@Injectable()
export class EmailService {
// Utility object to send emails
//--------------------------------------------------------------------------
private readonly _transporter = createTransport(
{
host: process.env.EMAIL_HOST, // smtp.elasticemail.com
port: process.env.EMAIL_PORT, // default to 2525 with elasticemail
auth: {
user: process.env.EMAIL_USER, // The email address that sends the message
pass: process.env.EMAIL_PASS, // The password of the email address
},
},
{
from: `Cryptomancy <${process.env.EMAIL_USER}>`, // "from" field of the message
},
);

// Verify SMTP configuration at construction
//--------------------------------------------------------------------------
constructor() {
this._transporter.verify().then(() => {
console.log('Ready to send emails');
});
}

// Send an email
//--------------------------------------------------------------------------
async sendEmail(to: string, subject: string, text: string) {
await this._transporter.sendMail({ to, subject, text });
}
}

To send emails, we use nodemailer, a package that abstracts SMTP to make it easier to send emails. The first step is to define a transporter, which is responsible for transmitting emails. This can be done by calling the createTransport function and passing a configuration object as an argument to it. For this example we use smtp.elasticemail.com as host. You can create an account and generate SMTP credentials for free on their website.

In the constructor, we call the verify method to check if we are authenticated correctly and that the server is ready to accept messages.

We also define a single method, sendEmail, which takes a recipient email address, subject, and message body as parameters and send the email.

Create an Item resource

ItemService

Now that we have all the necessary utility services, we need services for adding and updating items in the database.

Run the following command to generate the ItemService under src/item/item.service.ts:

nest g service item

Update it to look like this:

/*
| Developed by Starton
| Filename : item.service.ts
| Author : Alexandre Schaffner ([email protected])
*/

import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { nullAddress } from 'src/utils/constants';

/*
|--------------------------------------------------------------------------
| ITEM SERVICE
|--------------------------------------------------------------------------
*/

@Injectable()
export class ItemService {
constructor(private readonly prisma: PrismaService) {}

async create(data: Prisma.ItemCreateInput) {
await this.prisma.item.create({ data });
}

async updateByTokenId(tokenId: string, data: Prisma.ItemUpdateInput) {
await this.prisma.item.update({ where: { tokenId }, data });
}

async deleteByTokenId(tokenId: string) {
await this.prisma.item.delete({ where: { tokenId } });
}

async safeTransferFrom(
collection: string,
tokenId: string,
from: string,
to: string,
) {
// Add item to database in case of a mint (from === nullAddress)
//--------------------------------------------------------------------------
if (from === nullAddress) {
await this.prisma.item.create({
data: {
collection: {
connect: {
contractAddress: collection,
},
},
tokenId,
ownerAddress: to,
},
});

// Increment nextTokenId in the database
//--------------------------------------------------------------------------
await this.prisma.collection.update({
where: {
contractAddress: collection,
},
data: {
nextTokenId: {
increment: 1,
},
},
});

// Update item's owner in case of a transfer
//--------------------------------------------------------------------------
} else {
await this.prisma.item.updateMany({
where: {
AND: [
{ tokenId },
{ collection: { contractAddress: collection } },
{ ownerAddress: from },
],
},
data: {
ownerAddress: to,
},
});
}
}
}

The ItemService is responsible for managing items in the database. It provides several methods:

  • create(data: Prisma.ItemCreateInput): adds a new item to the database.
  • updateByTokenId(tokenId: string, data: Prisma.ItemUpdateInput): updates an existing item in the database based on its token ID.
  • deleteByTokenId(tokenId: string): removes an item from the database based on its token ID.
  • safeTransferFrom(collection: string, tokenId: string, from: string, to: string): adds or updates an item in the database depending on whether it was transferred or minted. If the from address is nullAddress, it creates a new item in the database (mint). Otherwise, it updates the item's owner in the database.

ItemModule

Now that we have an Item service we need to export it in a module to be able to use it elsewhere.

/*
| Developed by Starton
| Filename : item.module.ts
| Author : Alexandre Schaffner ([email protected])
*/

import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';

import { ItemService } from './item.service';

/*
|--------------------------------------------------------------------------
| ITEM MODULE
|--------------------------------------------------------------------------
*/
@Module({
imports: [PrismaModule],
providers: [ItemService],
exports: [ItemService],
})
export class ItemModule {}

Create a Transfer resource

TransferService

/*
| Developed by Starton
| Filename : transfer.service.ts
| Author : Alexandre Schaffner ([email protected])
*/

import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';

/*
|--------------------------------------------------------------------------
| TRANSFER SERVICE
|--------------------------------------------------------------------------
*/
@Injectable()
export class TransferService {
constructor(private readonly prisma: PrismaService) {}

// Create a transfer record in the database
//--------------------------------------------------------------------------
async create(transfer: Prisma.TransferCreateInput) {
await this.prisma.transfer.create({ data: transfer });
}
}

The create method adds a new record to the Transfer collection of the database. We will call this method every time a transfer happens.

TransferController

First things first, let’s generate the TransferController:

nest g controller transfer

Watcher & Webhook

For our app to be notified of a Transfer event, we need to set up a Watcher on Starton. A Watcher is a condition that is checked upon inspection of each block. When the watcher is triggered, it sends a POST request to a webhook containing data about the event. You can find how to create a Watcher here.

/*
| Developed by Starton
| Filename : transfer.controller.ts
| Author : Alexandre Schaffner ([email protected])
*/

import {
Body,
Controller,
InternalServerErrorException,
Post,
UseGuards,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { MintDto } from 'src/contracts/dto/Mint.dto';
import { SafeTransferDto } from 'src/contracts/dto/SafeTransfer.dto';
import { AuthGuard } from 'src/guards/auth/auth.guard';
import { ItemService } from 'src/item/item.service';
import { StartonService } from 'src/starton/starton.service';
import { UserService } from 'src/user/user.service';
import { cryptoquartzCollectionAddress } from 'src/utils/constants';

import { EmailService } from '../email/email.service';
import { TransferService } from './transfer.service';
import { StartonGuard } from 'src/guards/starton/starton.guard';

/*
|--------------------------------------------------------------------------
| TRANSFER CONTROLLER
|--------------------------------------------------------------------------
*/

@Controller('transfer')
export class TransferController {
constructor(
private readonly transferService: TransferService,
private readonly starton: StartonService,
private readonly userService: UserService,
private readonly itemService: ItemService,
private readonly emailService: EmailService,
) {}

/*
|--------------------------------------------------------------------------
| WEBHOOK ENDPOINT TRIGGERED BY STARTON
|--------------------------------------------------------------------------
*/

@UseGuards(StartonGuard)
@Post('webhook')
async webhook(@Body() body: any) {
try {
const { from, to, id } = body.data.transferSingle;
const transfer: Prisma.TransferCreateInput = {
item: { connect: { tokenId: id.hex.toLowerCase() } },
from: from.toLowerCase(),
to: to.toLowerCase(),
toUser: { connect: { publicAddress: to.toLowerCase() } },
fromUser: { connect: { publicAddress: from.toLowerCase() } },
txHash: body.data.transaction.hash.toLowerCase(),
};

// Check if user exists, if not, don't connect records
//--------------------------------------------------------------------------
const toUser = await this.userService.findByPublicAddress(
to.toLowerCase(),
);
if (!toUser) delete transfer.toUser;
const fromUser = await this.userService.findByPublicAddress(
from.toLowerCase(),
);
if (!fromUser) delete transfer.fromUser;

// Change the owner of the item in the database
//--------------------------------------------------------------------------
await this.itemService.safeTransferFrom(
cryptoquartzCollectionAddress,
id.hex.toLowerCase(),
from.toLowerCase(),
to.toLowerCase(),
);

// Create the transfer record
//--------------------------------------------------------------------------
await this.transferService.create(transfer);

// If the recipient is a user, send an email
//--------------------------------------------------------------------------
if (!toUser) return;

// Use a template here
//--------------------------------------------------------------------------
await this.emailService.sendEmail(
toUser.email,
'NFT Transfer',
'The address ' +
from +
' sent the NFT #' +
id.hex +
' to your address ' +
to +
'.',
);

return;
} catch (err: unknown) {
console.error(err);
throw new InternalServerErrorException();
}
}
}

So, we need a route to handle requests sent by Starton.

We create a /transfer/webhook endpoint. In it, we create a Transfer record from the data we received in the body and check if addresses in the Transfer record are related to a user.

Then, we call itemService's safeTransferFrom method to update the Item’s owner in the database and we create a new Transfer record in the DB via transferService's create method.

Finally, we use the emailService we coded previously to send an email to the receiver of the NFT if he is a registered user.

Note that the endpoint is protected by the StartonGuard, which we will focus later on.

StartonGuard

We can now be notified of transfers via the /transfer/webhook endpoint. This is good, but we need to secure the endpoint so that only Starton can trigger it.

To do so we create a StartonGuard:

nest g guard guards/starton
/*
| Developed by Starton
| Filename : starton.guard.ts
| Author : Alexandre Schaffner ([email protected])
*/

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { createHmac } from 'crypto';
import { Request } from 'express';

/*
|--------------------------------------------------------------------------
| STARTON'S SIGNATURE VERIFICATION GUARD
|--------------------------------------------------------------------------
*/

@Injectable()
export class StartonGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request: Request = context.switchToHttp().getRequest();
const payload = JSON.stringify(request.body);

const reqSignature = request.get('starton-signature');
if (!reqSignature) return false;

// Re-compute the signature and compare it with the one received
//--------------------------------------------------------------------------
const localSignature = createHmac(
'sha256',
process.env.STARTON_SECRET as string,
)
.update(Buffer.from(payload))
.digest('hex');

return reqSignature === localSignature;
}
}

In it, we retrieve the Request object, the payload of the body and the signature. Then, we compute the signature again using the payload and the signing key (you can find yours in your Starton’s dashboard under: Your project > Developer > Webhook). Finally we compare the signature we computed with the one provided in the header of the request we retrieved previously. If they match, we allow access to the endpoint, and if not it responds with an error.

Utility endpoints

Next, we add endpoints to allow minting, burning and transfers of items.

/*
|--------------------------------------------------------------------------
| TRANSFER / MINT / BURN ENDPOINTS
|--------------------------------------------------------------------------
*/

// safeTransferFrom
//--------------------------------------------------------------------------
@UseGuards(AuthGuard)
@Post()
async safeTransferFrom(@Body() safeTransferDto: SafeTransferDto) {
await this.starton.initTransfer(
safeTransferDto.from,
safeTransferDto.to,
safeTransferDto.tokenId,
);
}

// Mint a token
//--------------------------------------------------------------------------
@UseGuards(AuthGuard)
@Post('mint')
async mint(@Body() mintDto: MintDto) {
await this.starton.initMint(mintDto.to, mintDto.tokenId);
}

// Burn a token
//--------------------------------------------------------------------------
@UseGuards(AuthGuard)
@Post('burn')
async burn(@Body() burnDto: MintDto) {
await this.starton.initBurn(burnDto.to, burnDto.tokenId);
await this.itemService.deleteByTokenId(burnDto.tokenId);
}

All the 3 endpoints calls methods of the Starton wrapper service. Thus, Starton can make the smart contract function calls to apply changes on-chain. Every call to this endpoint ends up in a Transfer event on-chain, which triggers our webhook and applies changes to the database.

TransferModule

Finally, we group TransferService and TransferController in a TransferModule:

nest g module transfer
import { Module } from '@nestjs/common';
import { EmailModule } from 'src/email/email.module';
import { ItemModule } from 'src/item/item.module';
import { PrismaModule } from 'src/prisma/prisma.module';
import { StartonModule } from 'src/starton/starton.module';
import { UserModule } from 'src/user/user.module';

import { TransferService } from './transfer.service';

@Module({
imports: [PrismaModule, StartonModule, UserModule, ItemModule, EmailModule],
providers: [TransferService],
})
export class TransferModule {}

Now that everything looks fine you can test your code by running

yarn start

Congratulations!

You successfully created an email notification system that is triggered every time a transfer occurs on your ERC1155 NFTs collection !

You have learned how to create an email module to automatically send emails with Nodemailer, how you can use a Watcher with a webhook to track for specific event on the blockchain.

Now that you have successfully created an email notification system that sends emails whenever a transfer occurs on your ERC1155 NFTs collection, you can customize the email template to make it more appealing to your users. You can also explore other notification methods such as SMS or push notifications to provide your users with more options. Additionally, you may want to consider implementing more security features like roles to ensure that your endpoints are properly secured against unauthorized access.

· 2 min read
Loubna Benzaama

To create your NFT by deploying a smart contract you can upload your contract-level metadata on IPFS.

Creating your Json file

To upload your metadata, it must be in the Json format. You need to follow the standard of the marketplace where you want to showcase your NFT.

For example, if you wish to showcase your NFT on OpenSea, you can use this template:

{
"name":"My Super NFTs",
"description":"You’ve never seen NFTs this beautiful.",
"image":"",
"external_link":"",
"seller_fee_basis_points":100,
"fee_recipient":""
}

Uploading your file on IPFS

You can upload your file on IPFS using Starton from Dashboard or from Code.

    const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
// AUTHENTICATING TO THE API USING YOUR API KEY
const startonApi = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "YOUR_API_KEY",
},
});
// UPLOAD JSON TO IPFS.
const uploadJsonToIpfs = async (json, name) => {
const ipfsJson = await startonApi.post("/v3/ipfs/json", {
name: name of your json,
content: json,
metadata: { your: "additionnal", meta: "data" },
});
return ipfsJson;
};
// ENTERING YOUR JSON'S INFORMATION
uploadJsonToIpfs({ "metadata": "data" }, "Json name")
.then((res) => console.log(res))
.catch((e) => console.log(e));

After uploading your file, you will get a URI (the address of your file on IPFS), and a CID (content Identifier), that you can use you deploy your ERC721 or ERC1155 smart contracts.

· 6 min read
Loubna Benzaama
When creating and selling a collection, you can allow only a few chosen people to buy them for a defined price. This system is called a whitelist sale and it is what we are going to see today.

You will need

  • nodejs (version 16 or over)
  • npm
  • ethers v5
  • merkletreejs
  • a deployed ERC721 NFT contract

note

To deploy a Sale contract, you must have deployed your NFT by deploying an ERC721 contract first. Learn how to Deploy your NFT with Starton

In this tutorial, we will:

    Create a merkle root from all approved addresses
    Deploy the Sale contract from API
    Interact with the contract
To deploy this contract, you need to compute a special parameter called definitiveMerkleRoot.

Step 1

Creating a merkle root from all approved addresses

The best way to create a whitelist in solidity is by generating a merkle tree. It is a data structure that can store many data in a limited space which ends up being a merkle root.

To be able to generate this parameter, you will need to use this code snippet:

import { ethers } from "ethers"
import { MerkleTree } from "merkletreejs"

// Compute the root of the merkletree
const addresses = [
/* The addresses of the wallets allowed to mint */
]
const leaves = addresses.map((addr) => ethers.keccak256(addr))
const merkleTree = new MerkleTree(leaves, ethers.keccak256, { sortPairs: true })
const rootHash = merkleTree.getRoot()

caution

Don’t forget to replace addresses with the actual wallet addresses of the whitelisted accounts.

Step 2

Deploying the Sale contract from API

After you computed the root of the Merkle Tree, you can deploy the contract:

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: { "x-api-key": "PUT HERE YOUR API KEY" },
})

axiosInstance
.post("/v3/smart-contract/from-template", {
network: "",
signerWallet: "",
templateId: "ERC721_SALE",
name: "My first NFT Whitesale",
description: "This is my first NFT Whitesale ",
params: [
"", // definitiveTokenAddress
"", // definitiveMerkleRoot
"", // definitivePrice
"", // definitiveStartTime
"", // definitiveEndTime
"", // definitiveMaxTokensPerAddress
"", // definitiveMaxSupply
"", // definitiveFeeReceiver
],
speed: "average",
})
.then((response) => {
console.log(response.data)
})
  • definitiveTokenAddress: The address of the previously deployed ERC721
  • definitiveMerkleRoot: The merkle root
  • definitivePrice: The price to sell each token
  • definitiveStartTime: The time when the sale will start
  • definitiveEndTime: The time when the sale will end
  • definitiveMaxTokensPerAddress: The max amount of tokens a single address can buy
  • definitiveMaxSupply: The total amount of tokens that can be sold
  • definitiveFeeReceiver: The address that will receive all the price paid to mint

Step 3

Granting role on your base contract

For the whitelist sale contract to be able to mint new tokens, you are going to need to grant the newly created contract the MINTER_ROLE over the base contract deployed.

You will need

  • the address of the base ERC721 contract
  • the address of the Sale contract
  • the data of the Minter Role (in bytes)

Retrieving the MINTER_ROLE

First, let's get the minter role:

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: {
"x-api-key": "PUT HERE YOUR API KEY",
},
})

axiosInstance
.post("/v3/smart-contract/{network}/{YOUR_BASE_CONTRACT}/read", {
functionName: "MINTER_ROLE",
params: [],
})
.then((response) => {
console.log(response.data)
})

Granting the minter role to your Sale contract

To grant the minter role to your contract, you can use the following request:

const axios = require("axios")

const axiosInstance = axios.create({
baseURL: "https://api.starton.com",
headers: { "x-api-key": "PUT HERE YOUR API KEY" },
})

axiosInstance
.post("/v3/smart-contract/${network}/${YOUR SMART CONTRAT ADDRESS}/call", {
functionName: "grantRole(bytes32,address)",
params: [
"0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6", // MINTER_ROLE
"", // account
],
signerWallet: "",
speed: "average",
})
.then((response) => {
console.log(response.data)
})
  • account: The whitelist sale contract address

Step 4

Interacting with the contract

You can access different parameters of the contract using these functions:

priceGet the price to mint one token
startTimeGet the start time of the sell
endTimeGet the end time of the sell
maxTokensPerAddressGet the max tokens per address
leftSupplyGet the total amount of tokens that can still be minted
tokensClaimed(address)Get the total amount already minted by an address

And finally you can use the withdraw function to withdraw all the fees earned to the definitiveFeeReceiver.

  • definitiveMaxTokensPerAddress: The max amount of tokens a single address can buy
  • definitiveMaxSupply: The total amount of tokens that can be sold
  • definitiveFeeReceiver: The address that will receive all the price paid to mint

Minting a NFT

Now that the contract is deployed, every user can mint a token.

To be able to do this, every user will need to specify two parameters while minting:

  • the address of the receiver of the NFT
  • the merkle proof

This proof is also linked to the merkle tree and it can prove that an address is inside the merkle tree To be able to generate it, you can use this code snippet:

import { keccak256 } from "ethers/lib/utils"
import { MerkleTree } from "merkletreejs"

// Compute the root of the merkletree
const addresses = [
/* The addresses of the wallets allowed to mint */
]
const leaves = addresses.map((addr) => keccak256(addr))
merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true })

// replace MINTER_ADDRESS with the address of the account that will mint the NFT
const proof = merkleTree.getHexProof(keccak256(MINTER_ADDRESS))

Users will then be able to mint a new token using their wallet such as metamask with the right parameters.

Step 5

Integrating the Sale to your application

Congrats! You're now all set for your NFT Sale. You can connect this smart contract using the mint function to a button. By, using etherjs/web3js, call the mint function of the sale contract with the desired address.

You can also give more information on this mint page such as NFT left to mints or end time of the sale.

Shall we continue ?

How to create your own cryptocurrency (ERC20)
How to sell an NFT collection in an auction