Introduction

First, to understand the world of NFTs, you need to understand the underlying technology that makes them possible, because without a blockchain to store NFT information, this world wouldn’t exist.

You’ll have to read Non-fungible tokens to understand what we’re doing here.

Disclaimer: This library is on beta stage, use with precaution and responsability

What’s Fantom?

We will use Fantom as a network to support our NFTs, in this article I will use the testnet but it is exactly the same to reproduce it on the mainnet, so don’t worry about that.

Why Fantom?

Because it is the cheapest and fastest L1 out there, almost free to deploy and really easy to use. You won’t need more than 1 FTM to go through this tutorial.

What’s Lighthouse?

Lighthouse allows users to store their files in a decentralized network for life at a fixed price.

I’m a contributor of the core package and for the ecosystem to be robust we need to get our hands on the ground.

Why Lighthouse?

Because other services are pretty massive so far, with a lot of articles, documentation and people using it, and they also add a lot of “centralized” content in their own way. Lighthouse will remove the friction between you and the IPFS network by making the process for creating distributed assets almost invisible.

Preparation

In this tutorial we will be using Lighthouse CLI but you can translate all of this information into Javascript using the package under the name lighthouse-web3. If you want to make a robust tooling around Lighthouse that’s the best way to do it.

Let’s gets started.

Install the CLI as a global package using:

npm install -g lighthouse-web3

For this tutorial we will be using lighthouse-web3@1.2.7

Creating a wallet

In order to create a wallet you can throw in the terminal after installing the CLI

> lighthouse-web3 create-wallet

Set a password for your wallet: <write your own, and don't forget it!>
Public Key: 0xcE929c41EfCF81cc547D2C3a093915b1Ba904F1A
Wallet Created!

Or you can use an already created wallet importing the private key:

lighthouse-web3 import-wallet --key <private-key>

Both options are OK. In this tutorial I’ll use the CLI but if you have hesitations using the create-wallet command you can create your own by hand using the official Fantom Wallet and then importing the private key.

After that, we should set Lighthouse to use Fantom as default network:

> lighthouse-web3 --chain fantom  
  
Currently using: testnet
Chain set to fantom

Deposit some funds into your wallet

After creating the wallet, we would run a command call balance to check if everything works as excepted:

> lighthouse-web3 balance
0

As we can see, the command throw us a result into the terminal: 0. Obviously we would need to fund this wallet and in order to do this we are gonna use the Fantom Faucet (I showed on my previous article how to fund you wallet using this website)

Have in mind that you can use real FTM to fund this account on the mainnet, this is just for the testnet.

After request to the faucet a few bucks we can throw the balance again and see if something changes (this process usually takes more than 2 minutes)

> lighthouse-web3 balance
10

Now we are ready to upload something on the testnet!

Configure Lighthouse

To not mess up our portfolios and deploy something in the real world we are going to use the testnet, to configure Lighthouse to do that you can go throw the following command to change the network to use the mainnet:

lighthouse-web3 --network testnet

Otherwise, it will use the main network instead

Upload a folder

That’s it, we are ready to deploy something in the chain. The command to drop something is really simple but we would need some preparations beforehand related to the NFTs metadata standards

Metadata structure is, basically, like this

{
  "description": "<you can put a description here>", 
  "image": "<any valid url that point to an image>", 
  "name": "<you can put a name here>",
}

Obviously you can extend that scheme to have attributes that make your NFT unique, but so far those parameters are sufficient for the scope of this tutorial.

One thing to note is that the image parameter could point to ANY valid url that exists, and that’s a problem because we want our NFT to be as decentralized as possible. We don’t want to store our images in Google Drive (or similar) only to wake up one day and realize they’re gone because we didn’t pay for the subscription plan.

That’s why we are about to deploy this two times.

  1. For plain images, in order to distribute those images in the IPFS network
  2. For metadata objects, containing the image parameter pointing to the CID of the step 1

Let’s get to work:

First of all, I recommend to rename all the pictures you have by an unique numeric incremental identifier, something like

cat.png -> 1.png
dog.png -> 2.png
elephant.png -> 3.png

This way, having the CID after deployment we can hit and fetch images using only the ID (remember this for the deploy-your-contrat step) and why it’s important to keep track of the id’s.

Once we rename all of the pictures using an identifier we can deploy those into the IPFS network using Lighthouse (I’ll upload a test folder in order to show you how to deploy a folder):

> lighthouse-web3 deploy pictures/test

bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m    2 MB        0.015131840249440111    image/png               1.png
bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m    2 MB        0.015131840249440111    image/png               2.png
bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m    2 MB        0.015131840249440111    image/png               3.png

Summary
Total Size: 2 MB
Fees: 0.005040873106182873
Gas Fees: 98615
Total Fee: 9.861500504087311e-14

Wallet
Address: 0x1449265f3331386F5308eEe40a8b06cdfb1DCCC1
Current balance: 19.680529799801604
Balance after deploy: 19.680529799801505

File Deployed, visit following url to view content!
Visit: https://ipfs.io/ipfs/bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m
CID: bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m

And there’s our precious CID

Now, we can write an script to make .json metadata files to match those same IPFS links. How? by refering the CID and attaching /[id].png at the end of the image url.

Let’s see what I meant with this:

file: index.js

const fs = require("fs");
const path = require("path");

const index = (baseCID,itemsInCID) => {
  const object = (numberOfPhoto) => {
    return {
      name: `Moment #${numberOfPhoto}`,
      description: `Moment #${numberOfPhoto}`,
      image: `ipfs://${baseCID}/${numberOfPhoto}.JPG`,
    };
  };

  for (let index = 0; index <= itemsInCID; index++) {
    fs.writeFileSync(
      path.resolve(__dirname, `${index}.json`),
      JSON.stringify(object(index))
    );
  }
};

// we can use list all the files in the directory of images to 
// get the number instead of using a hardcoded 3 here
index("bafybeigl53pbfhwi7kvsgq66pwtye5v6o5iyso3pp4b32pdulxlykyon5m", 3);

After running the script using node index.js we can see that we now have three files under the same directory in .json format and pointing to the IPFS content we deployed earlier.

Now the process is quite similar to the previous one, save the .json files in a separate folder and do the same to upload them using Lighthouse

> lighthouse-web3 deploy pictures/test/metadata

Now, after deploying this we will get another CID that will point to the metadata instead of the image itself. This is useful to make OpenSea, Rarible, Artion, etc. display the correct information for our NFT.

After saving the last CID generated by the CLI we can now go to the contract and mint some NFTs.

Deploy your contract

I wrote this simple contract to deploy NFT using no access-control so don’t use this in production, but for the sake of this article I think it’s more than enough:

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract YourFirstNFTContract is ERC721URIStorage, Ownable {
	// usings
    using Strings for uint256;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    
    // declaration of variables
    uint256 public maxSupply = <number of pictures you want to create as NFT>;
    string baseURI;
    string baseExtension = ".json";

    constructor(string memory baseCID) ERC721(<name-of-your-nft-collection>, <your-token>) {
        setBaseURI(baseCID);
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

	// needed for the contract to return the tokenURI of the NFT, since we are not using setTokenURI() method
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
        _exists(tokenId),
        "ERC721Metadata: URI query for token that does not exists"
        );

        string memory currentBaseURI = _baseURI();
        
       
       	 // if currentBaseURI exists we return that, if not return empty string
        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, "/", tokenId.toString(), baseExtension))
            : "";
    }

	// hit this method to mint an NFT for the address sent as parameter
    function mint(address user)
        public onlyOwner
        returns (uint256)
    {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        require(newItemId < maxSupply, "token exceed supply");

        _safeMint(user, newItemId);

        return newItemId;
    }
}

As you can see, the deployment of this contract needs one thing, the ``` generated in the previous step.

We can take the CID we got in the last metadata deployment and put it in the contract constructor before deploying it on the chain.

To deploy it, you can use Ethereum Remix to be able to use your Metamask wallet with the account we created in Lighthouse (importing the private key). You can also follow my previous article to check how to deploy a contract in Fantom without using Remix. If you don’t know how to use Remix don’t worry, it’s quite simple and here is a video tutorial to understand the basics: Introduction to Remix and Solidity

Using hardhat will look like

    const YourFirstNFTContract = await ethers.getContractFactory("YourFirstNFTContract");
    const yourContract = await YourFirstNFTContract.deploy("ipfs://<baseCID>");

Or using Remix:

First choose Environment as Injected Web3, connect your wallet and change it to Fantom Testnet:

Step 1

Then, you can deploy it but first be sure to set the baseCID as parameter:

Step 2

After hitting deploy, you can see the transaction log and spot the address we need on the from: parameter.

Step 3

Either way, you will get a contract deployed and ready to be minted. To access the contract (spotted on hardhat or remix) you can use the following URL testnet.ftmscan.com/address/{your-contract-address}

Mint an NFT

After getting the address of the contract, you can hit the Read Method called mint(address) (in the ftmscan.com site after you verified it) or use Remix UI to call mint method.

First option will need you to verify the contract, so Remix option is faster in order to try it out

After a succesfully transaction you will be the owner of an NFT!. So now, you can add the contract address and the tokenId of your NFT on your wallet and then you will be able to visualize it.

NFT visualization

I’m using Metamask Mobile, currently there is no support for the browser extension:

Check it out the following wallets to visualize NFTs:

Next Steps

It is likely that the library will support a command to deploy NFTs using generic contract ER721 to make it easier for users to get in touch with this technology without too much discomfort.

Conclusion

As you can see, deploying a collection in Fantom using Lighthouse is really affordable and also easy for those who don’t have too much experience in the blockchain world. Don’t hesitate to try it out and start playing with Lighthouse + Fantom.