- Launching Your Personal NFT Crypto-Art Platform
A Practical Guide for Artists and Creators
Just now·16 min read
CryptoDappers (Source: Author)
This guide retraces the steps we took in creating CryptoDappers, an NFT art collection that lives on the Avalanche C-Chain. In this guide we cover the basics of creating an NFT marketplace (on any blockchain), from writing a programmable Solidity smart contract governing the NFTs, to deploying the contract using Hardhat, and finally to building a user-facing website using Web3 and Svelte.
Table of Contents
- Introduction: Why Should I Create My Own NFT Platform?
What is an NFT, really? Introducing the ERC721 Standard.
Towards An End-to-End, Decentralized NFT Marketplace Application
Writing a Smart Contract using Solidity
Using the Hardhat Development environment and Deploying your Contract to the Avalanche Network
Pinning Data to and Retrieving data from The Interplanetary File System (IPFS)
Building a front-end using Svelte, Svelte-Web3, and Vercel
Non-fungible tokens (NFTs) have given established artists and celebrities new avenues to share their works with a digitally inclined audience. The individual content creator however, faces a prohibitive barrier to entry: a mountain of fees. From spiking gas fees caused by congested networks to commission and service fees charged by NFT platforms, latent costs are everywhere. One can expect to pay upwards of $100 to mint a single NFT on the Ethereum blockchain — a price which precludes many creators from bringing their works to the digital NFT marketplace. In short, amateur artists are being priced out.
NFT Platforms such as OpenSea, Rarible, Foundation, and Nifty Gateway allow users to create, buy, and sell digital assets. They have user-friendly interfaces and out-of-the-box tools for creating and managing NFT collections. However, these platforms demand commission fees ranging from 3% to 15% per sale. Foundation and Nifty Gateway cater primarily to established artists and employ a rigorous vetting process. Lastly, these platforms give you relatively less flexibility in the contract-creation process and typically use the Ethereum blockchain, which has recently suffered from high congestion and high gas prices.
Creating your own user-facing website which interfaces with an underlying smart contract allows you to enjoy all the benefits of NFTs while avoiding high service and commission fees. Since gas prices and other properties differ from network to network, one can also choose a network that suits one’s needs. Ethereum is the most popular choice, but can have high gas fees. For high throughput and low gas fees, Avalanche is a great option. There are many other platforms supporting smart contacts and worth exploring, such as Cardano, Tezos, Solana, Eos, and Binance NFT. Finally, if anything, creating a front-end for your custom decentralized application is a fun experience and an exercise I would recommend to anyone interested in exploring DeFi!
TL;DR: 1) To circumvent high gas, commission, and service fees 2) For fun! To learn about DeFi and blockchains!
What is the formal definition of an NFT, or non-fungible token? An NFT is a unit of data stored on a smart contract which follows the ERC721 application-user interface (API). This ERC721 standard specifies a number of functions which must be implemented by derived contracts, such as ownerOf, balanceOf, and transferFrom. This standard makes it easier for wallets and exchanges to support NFTs.
A smart contract can be thought of as a programmable and permanent document or deed which lives on a blockchain and which governs a universe of tokens. The contract contains both mutable and immutable information about the tokens, such as supply cap, ownership, and more. Concretely, the contract will often contain a mapping, which keeps track of token ownership. The smart contract also tracks token metadata (URLs), such as the asset that a non-fungible token represents. This could be an image, video, or audio file.
Smart Contracts/NFT Examples
One can glean a lot about how smart contracts work simply by scanning the source code of decentralized applications, such as that of CryptoKitties. (We highly recommend reading the source code, as it is quite transparent). The contract itself is partitioned into 6 or 7 subcontracts, each of which has a unique role. The contracts are linked up through inheritance (each subcontract inherits the previous one and all functions/variables therein). The subcontracts describe ownership, auction mechanisms, breeding mechanisms, and more. Inheritance offers a way of breaking down a big and sophisticated contract into smaller, more manageable ones.
The CryptoKitties contract differs from the typical NFT marketplace/auction smart contract in that it includes a breeding mechanisms which mints new NFTs by using existing ones. We forgo this mechanism, as it adds an extra layer of complexity to the smart contract and also introduces an unlimited supply cap. We instead enforce a fixed supply cap with no possibility of minting new tokens.
To implement a working marketplace, we must keep track of state. State is stored within the smart contract, which in turn lives on a blockchain. For example, the smart contact keeps track of NFT metadata, ownership, auction prices, auction status, etc. When an item is put up for sale, the auction contract might escrow the goods. When a sale takes place, the contract must transfer ownership from seller to buyer. This is what a backend component in a regular, non-decentralized application might be responsible for. Contract state can be updated by sending transactions — which are functions calls which may or may not include value— to the deployed smart contract. A transaction can include call-data (method and the arguments the method should be called with) and/or monetary value. Oftentimes it will include both.
Image by Author.
In this diagram, we outline the dependence of the front-end website on a Solidity smart contract and IPFS. In the next section, we introduce smart contacts as programmable objects which can be tailored to a variety of use cases.
Solidity is a high level programming language used for implementing smart contracts, which can be run inside of the Ethereum Virtual Machine (EVM). The EVM allows contracts to interact with one another. A few examples of contracts one can implement include a bank, a tip jar, a ballot, lottery system, blind auctions, BIN auctions, open auctions. Solidity essentially enables us to create finance-related applications in the EVM.
- Uint256/Uint128/Uint64: n-bit integers. One might also see the type Uint without a number at the end. This is equivalent to Uint256.
Uint: an array object (in this case an array of ints). Arrays in solidity are zero indexed. To obtain the length of an array, do array.length.
Address: Address of a user account or smart contract. Must be cast to payable in order to receive funds: payable(address).transfer(funds). address(0) returns the address 0x00000000.
Mapping: mapping of key-value pairs; does not keep track of the keys that have been “defined” by the user! The value associated with any key is 0x00000000 by default. To check if a certain key-value pair has been set, one can check if the value is zero.
Struct: an object containing one or more fields
Similar to how our bank accounts have routing numbers, Ethereum accounts (which can be user-controlled or be a smart contract) have public addresses, which are typically represented in Hexadecimal. To interact with a smart contract, a user can dispatch a transaction object to the contract address.
The tx object contains several fields, including the value of the transaction, and the data, which is essentially the contract function you are trying to call in addition to the arguments of the function. We can optionally include a gas limit. Finally we include chain information in the tx object.
Contract Instantiation, Fallbacks, and More
Contracts may have at most one constructor, which is called once and only once (when the contract is first deployed). In the example below, the constructor sets the owner of the tip jar to msg.sender, the account that deployed the contract. The receive function is a special function that allows the contract to receive funds (receive is a reserved keyword). Fund received by a contract are implicitly stored in the contract. One can query the balance of a contact using the balance function, for example address(this).balance.
Code by Author.
Funds can be withdrawn by using the built-in transfer function. The fallback function (currently empty) is called when the contract is called with a undefined function. While the tip jar above does not do much, the contract illustrates four special functions in Solidity programming, namely the constructor, receive, fallback, and transfer functions.
A Smart Contract for NFTs
A rudimentary (and as it happens, incomplete) smart contract for NFTs might look something like the following.
Code by Author.
In this contract we keep a mapping indexToOwner of token IDs to owners, which are represented as public addresses. There are currently two functions implemented, getMyTokens and _transfer. The first function returns an array of Ids of tokens belonging to a particular user and has public visibility —meaning that it can be called externally via messages/transactions or internally by other contract methods. The second function transfers a token from one user to another, taking are to update relevant data structures. Notice however, that this function has internal visibility, meaning that it can only be accessed internally (by functions in the contract, or by functions in derived contracts).
To make this a fully fledged smart contract, we might want to implement a sibling metadata contract, which maps token ID to digital asset uniform resource identifiers (URIs), i.e. links to the assets that the tokens represent. For the NFT marketplace DApp, we might also want to implement a sibling auction contract that handles the creation of auctions, escrows items, and handles auction sales.
The programmability of smart contracts give them a surprising degree of flexibility, and allow us to write contacts for a diverse range of applications. The fact that the contract code becomes immutable after being deployed to the blockchain means that once the rules are set, they cannot be changed (unless we allow for upgrading of the smart contract). Decentralized finance (DeFi) relies on this very technology to create transparent protocols which replicate existing financial services.
It is worth mentioning that the official Solidity docs is a rich source of contract examples. CryptoZombies is a great tutorial for people new to smart contracts, Web3, and blockchain. Instead of using Truffle and Ganache however, we use Hardhat, which is covered in the next section.
Ethereum development environment for professionals by Nomic Labs
Writing automated tests when building smart contracts is of crucial importance, as your user's money is what's at…
Hardhat is an all-in-one professional development environment for writing and deploying smart-contracts. An in-browser alternative to Hardhat is the Remix IDE. Remix might be a good starting point for those who are new to smart contracts, because it has UI elements for deploying contracts, sending transactions to deployed contracts, and selecting to/from addresses. It takes care of setting up tx objects behind the scenes. In Hardhat, one would write code for sending transactions and the handling of private/public keys. In summary, we recommend Hardhat for developers who want more control over writing and deploying their contracts, and Remix for new users who want a friendly user-interface.
Getting Started with Hardhat and Avalanche Smart Contracts
We recommend this excellent guide for getting started with deploying smart contracts on Avalanche using Hardhat.
Avalanche is an open-source platform for launching decentralized applications and enterprise blockchain deployments in…
We follow this tutorial for this most part, but deviate from the tutorial in that instead of deploying a contract to a local network, we deploy to a test network. For the tutorial, one should install Yarn (a package manager with high performance guarantees), AvalancheJS (A JS library for the Avalanche platform). One should be comfortable using Node package manager (npm).
Creating an Account
To create an account, so to speak, one merely has to generate a public-private key pair. The public key can be used for receiving funds, while the private key is used to authenticate the account and sign outgoing transactions from the account. Key generation functionalities for the Avalanche network are provided by AvalancheJS:
In Hardhat, there is a hardhat.config.js file where you can define Hardhat tasks and include network information. The config file also stores your private keys. After generating private keys, one should put them in the module.exports section of the hardhat.config.js file
I personally found the configuration step to be the most tedious, however after filling in network details and generating public/private keys, you should be good to go.
Signing and Sending Transactions
One possible sequence of steps for sending a transaction might look like the following.
One first loads an account using the privateKeyToAccount function. The account object contains an address field, as well as a signTransaction function. We can create a tx object, sign the message, and finally send it to its destination by calling the sendSignedTransaction function.
When interacting with your DApp, most people will use MetaMask, a browser extension that serves a couple of purposes. MetaMask advertises itself as a key, wallet, and shield — in short an indispensable tool for navigating the decentralized web. Underneath the surface, it is simply an application which handles users’ private keys for them, and which provides a connection to the Ethereum network (a provider). When sending a transaction, you don’t have to worry about signing a transaction (with the private key), because MetaMask does so automatically after you import your private key into MetaMask.
The MetaMask UI has a couple of components. The network and active account can be changed by pressing buttons in the upper right corner. To add the Avalanche FUJI Testnet or Mainnet to MetaMask, we refer to this short article.
When making a transaction, MetaMask will prompt you to review the transaction data and gas fee before allowing you to confirm the transaction.
Where can we store metadata associated with NFTs, be it PNG files, GIFs, audio files, or videos? One possibility is IPFS, a resilient and distributed content delivery network (CDN). With the help of pinning services, we can be assured of data persistence.
The Interplanetary Filesystem (IPFS) is a P2P network for storing and sharing files. What sets IPFS apart from other protocols is that it uses a single global namespace and that files pinned on the network are content-addressed.
Pinata Pinning Service
To keep data permanently pinned on IPFS, one would either have to run a local IPFS node or, alternatively, to use a pinning service such as Pinata. Pinata allots new users 1 GB of storage space for free.
The Pinata dashboard allows users to upload files manually and displays the hashes of pinned files. When uploading files in bulk, one can use the SDK:
Official NodeJS SDK for Pinata The Pinata NodeJS SDK provides the quickest / easiest path for interacting with the…
A simple setup for pinning a file using the SDK is as follows.
Code by Author.
Accessing Files Pinned on IPFS
After pinning your file and obtaining its hash, you can access the file using the following URL: ipfs.io/ipfs/insert_hash_here.
Using Axios to Fetch Data from IPFS
Image by Author.
Using Svelte-Web3 to Load a Smart Contract
In Svelte, there is Svelte-Web3, a library that provides several useful Svelte stores (including one for web3)! In Svelte, a store is simply an object with a subscribe method that allows subscribers to be notified when the store value changes. In Svelte-Web3, important Web3 constructs such as web3, selectedAccount, chainId, connected, chainName, nativeCurrency, are implemented as stores. In a browser context, these values are automatically updated when the user switches accounts in MetaMask or connects to a different chain. Therefore, we do not have to write code for detecting such changes and updating the user’s selectedAccount, web3 provider, connected chain, etc.
Below, we use the Svelte set context function to make our smart contract accessible to all components (similar to how props are passed around in React, except without doing the passing explicitly).
Code by Author
Notice how we instantiate the contract using its address and its application-binary interface (ABI). The ABI can be obtained after compiling your contract in Remix or Hardhat. Once the contract is made available as a web3.eth.Contract object, we can make function calls and interact with the contract. In other Svelte files, we can call get(“myContext”) to load the contract defined in Line 7 of the code snippet above.
Interacting with Smart Contract
Remember when we what a transaction (tx) object in the EVM looks like? We can send a tx by using the web3.eth.sendTransaction function. A simple transfer function might look something like the following.
Code by Author
For our reactive app, we write many such helper functions to interact with the contract. The functions are all asynchronous because the Web3 sendTransaction function returns a promise. A list of functions that I wrote include getBalance, getMetadataAddress, getURL, getMyItems, transfer, getAuctionItemIds, getAuctionPrice, getAuctionSeller, cancelAuction. The common thread between these functions is that they all look like the transfer function above: each function loads the contract, and then sends a transaction to the contract, which returns some data of interest or mutates the state of the contract.
Svelte Files and Logic: Await and If Blocks
Imports and functions may be defined in the script portion, while HTML code goes in the body, and CSS code goes between the style tags. In the example above, we used an await block to wait for the asynchronous function to finish executing and then show the result within curly brackets. “If blocks” work similarly. It is also possible to nest await and if blocks — hence making it easy to express complex logic within the markup itself.
Vercel makes deploying a reactive website as easy as clicking a button, because it integrates with Github. I was stoked about Vercel after stumbling upon it, not only because of ease of deployment but also because it offers free SSL certificates (enabling websites to move from HTTP to HTTPS) and free web-hosting. In comparison, BlueHost/GoDaddy charge more than $150 for web hosting and purchase of SSL certificates and do not integrate as easily with reactive apps.
In this tutorial, we explained what NFTs are, went over Solidity fundamentals, and introduced several IDEs, tools, and technologies for building a decentralized NFT marketplace application. The Solidity contract constitutes the backend of our DApp, while the front-end is the user-facing website. Asset metadata is pinned on IPFS, a distributed content delivery network. We deployed our smart contract on the Avalanche Network, which provides an eco-friendly alternative for minting NFTs to other networks. While we did not have space to provide a complete lesson on smart contracts and front-end design, we hope that this guide sheds light on the tech stack for building a functioning DApp and gives the reader a picture of the inner workings of an NFT marketplace platform.
CryptoDappers | CryptoSeals | CryptoPunks | Avalanche| Ethereum
- Date of publication:
- Mon, 05/03/2021 - 14:47
Click on the link - it will be copied to clipboard