How to create an NFT using Go
Tutorial on creating and deploying an ERC-1155 contract and minting tokens in Go
If you have written or read blockchain code, you are probably familiar with Go. Go Ethereum is one of the original implementations of the Ethereum protocol, and currently has the biggest user base for Ethereum clients. On top of this, Go is excellent for high scale projects and allows an object-oriented style of programming.
However if you look on the world wide web for deploying an NFT in Go, it’s currently hard to fund a full end-to-end tutorial. This is because much of the smart contract development has been targeted towards browser based apps, focusing the priority on Javascript or Typescript based tutorials.
In this post, we will go over end-to-end on how to deploy an NFT contract and mint NFTs with that contract in Go. Let’s get started!
All code used in this tutorial can be found here: https://github.com/noopmood/go-erc1155-tutorial . The OS used in this tutorial is macOS Monterey v12.2.1.
1. Creating an ERC-1155 contract
One of the most famous NFT and fungible token contract implementation library is OpenZeppelin Contracts, used by Bored Ape Yacht Club, Cool Cats, Tubby Cats, and many more. Code reuse is highly recommended when creating smart contracts for security reasons. Borrowing an excerpt from Mastering Ethereum, “Try not to reinvent the wheel. If a library or contract already exists that does most of what you need, reuse it.”
OpenZeppelin API docs for an ERC-1155 contract can be found here. For easy testing purposes, we will be exposing the internal _mint
method so anyone can mint tokens with our contract.
Create a
contracts
directory for our smart contract code.Inside
contracts
, create aCoinNft.sol
file with the following contents
// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc1155
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract CoinNft is ERC1155 {
constructor() ERC1155("https://raw.githubusercontent.com/noopmood/TutorialNFTInGo/main/metadata/{id}.json") {}
function mintCaller(uint256 tokenId, uint256 amount) public {
_mint(msg.sender, tokenId, amount, "");
}
function mintAddress(uint256 tokenId, uint256 amount, address addr) public {
_mint(addr, tokenId, amount, "");
}
}
The HTTPS link above contains the metadata of our NFT. The string {id}
will be replaced with the actual token ID by clients, in lowercase hexadecimal and leading zero padded to 64 hex characters. This means for token ID 0
, clients would replace {id}
with 0000000000000000000000000000000000000000000000000000000000000000
to retrieve JSON at https://raw.githubusercontent.com/noopmood/TutorialNFTInGo/main/metadata/0000000000000000000000000000000000000000000000000000000000000000.json
, which has the contents:
{
"description": "Gold coin",
"image": "https://openmoji.org/data/color/svg/1FA99.svg",
"name": "Gold",
"attributes": [
{
"trait_type": "Color",
"value": "Gold"
},
{
"trait_type": "ID",
"value": "0"
}
]
}
Here, we can see that a token ID of 0
represents our gold token.
2. Create a Go binding
To interact with the contract, we will need to implement the Ethereum API in Go. Doing so is not trivial due to various edge cases, but luckily for us, Go Ethereum has abigen
that automatically does this for us. The abigen
library also handles all remote client API work for us, so we don’t have to worry about RPC codes. To use abigen
, we simply have to provide a solidity contract and it will generate the full Go source code. On top of this, abigen
is compile time checked, so you get no surprises during deployment. For more information on abigen, please visit Go Ethereum’s documentation.
Before we feed our contract into abigen
however, we will need the help of Truffle to import the OpenZeppelin library. Since Truffle has an abigen
plugin, we will ask Truffle to create the Go binding through abigen
for us.
Initialize Truffle and install dependencies.
cd ~/TutorialNFTInGo yarn init -y yarn add truffle @openzeppelin/contracts @chainsafe/truffle-plugin-abigen yarn truffle init # Answer "N" for overwriting contracts
Add the following line to the end of
truffle-config.js
.plugins: [ "@chainsafe/truffle-plugin-abigen" ]
Compile the contract and generate the ABI and BIN files.
yarn truffle compile # Create the directory that will store ABI and BIN files. # Truffle fails silently if this directory does not exist. mkdir abigenBindings yarn truffle run abigen CoinNft
Create a Go binding,
coincontract.go
. The BIN file is needed to generate deploy methods on the Go contract file.mkdir coincontract abigen --bin abigenBindings/bin/CoinNft.bin --abi abigenBindings/abi/CoinNft.abi --pkg=main --out=coincontract.go
3. Deploying a contract
1. Create an endpoint and private key
We need an endpoint to deploy our smart contract on an EVM compatible network. Select any endpoint service, such as Alchemy or Infura, to create a personal endpoint. To use Alchemy, follow these steps to create an Alchemy app and get its HTTPS endpoint.
Next, we will need the private key of a wallet to deploy contracts and mint NFTs. If you don’t have a wallet, Metamask provides a quick and easy option.
2. Get test ether
Blockchain transactions cost fees, and so we will be using test ether to deploy our contract. While you can choose any test Ethereum network, this tutorial will use the Ropsten network. Here is my favorite Ropsten faucet website, the Thinklair Ropsten Faucet.
3. Write deployment code
If we take a look at the Go binding code we created in the steps above, we can see that Truffle has populated OpenZeppelin contract implementations for us to call. To deploy our contract, let’s call the DeployCoincontract
function.
Create another directory and move the go-binding to the new directory if you prefer to keep everything in one Go package. Feel free to configure Go to point to the current directory if you would like the separation.
Create a
deployer.go
file to write our contract deployer function.package main import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" ) type Contract struct { Address common.Address Instance *Coincontract } func (c *Client) DeployContract() (Contract, error) { contractAddress, _, instance, err := DeployCoincontract(c.Auth, c.Rpc) if err != nil { return Contract{}, errors.Wrap(err, "failed to deploy the contract") } return Contract{contractAddress, instance}, nil }
Create our
main
function to callDeployContract
. The Ropsten network has a chain ID of 3. To speed up or override previous transactions, increase the gas price.package main import ( "fmt" "math/big" ) func check(e error) { if e != nil { panic(e) } } func main() { endpoint := "${YOUR_ENDPOINT}" privateKey := "${YOUR_PRIVATE_KEY}" chainId := big.NewInt(3) client, err := NewClient(endpoint, privateKey, chainId) check(err) client.SetNonce(big.NewInt(${YOUR_WALLET_NONCE})) client.SetFundValue(big.NewInt(0)) client.SetGasLimit(uint64(8000000)) client.SetGasPrice(big.NewInt(1875000000)) contract, err := client.DeployContract() check(err) fmt.Println("Contract address:", contract.Address.Hex()) }
Run the program.
export GOPROXY=direct # Avoid i/o timeouts xcode-select --install # Install developer path go env -w GO111MODULE=on # Turn on auto module imports go mod init # Initialize the Go module go mod tidy # Download module imports go run .
The output should look like the following:
Contract address: ${YOUR_DEPLOYED_CONTRACT_ADDRESS}
Check the status of your transaction on the Ropsten Etherscan by searching with the contract address or your public wallet address. You may have to wait a few minutes depending on your gas price.
Congratulations! You have deployed an NFT smart contract to the Ropsten network!
4. Minting NFTs and fungible tokens
Create a
minter.go
file to mint NFTs.package main import ( "math/big" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" ) func (c *Contract) MintToken(client Client, tokenId *big.Int, tokenAmount *big.Int) (*types.Transaction, error) { tx, err := c.Instance.MintCaller(client.Auth, tokenId, tokenAmount) if err != nil { return nil, errors.Wrap(err, "failed to create signed mint transaction") } return tx, nil }
Create a
checkOk
function inmain.go
to verify boolean returns.func checkOk(ok bool) { if !ok { panic(ok) } }
Append the following to the
main
method inmain.go
to create five tokens of token ID0
, which represents gold.client.SetNonce(big.NewInt(${YOUR_WALLET_NONCE})) tokenId := new(big.Int) tokenId, ok := tokenId.SetString("0", 16) checkOk(ok) tokenAmount := new(big.Int) tokenAmount, ok = tokenAmount.SetString("5", 16) checkOk(ok) fmt.Println("Minting", tokenAmount, "tokens of ID:", tokenId) tx, err := contract.MintToken(client, tokenId, tokenAmount) check(err) fmt.Println("Mint transaction:", tx.Hash())
Run the package.
go run .
The output should look like the following:
Minting 5 tokens of ID: 0 Mint transaction: ${YOUR_MINT_TRANSACTION_HASH}
Check the status of your transaction on the Ropsten Etherscan by searching with the transaction hash or your public wallet address. You may have to wait a few minutes depending on your gas price.
Great work! You have created a fungible token! To create an NFT, set and restrict the tokenAmount
to 1. You can view the NFT in your Metamask wallet by following the steps here. At the time of writing, NFTs images are only visible on Metamask through its mobile app.
For more activities, such as minting tokens in a different address than the caller or transferring tokens, write to the chain with other functions in coincontract.go
.
Final notes
If you are looking for an Go based Ethereum client other than Go-Ethereum due to licensing issues, please let me know if you find a good one. At the time of writing, INFURA’s
go-ethlibs
seems to be the only viable option with MIT licensing. Howevergo-ethlibs
lacks strong tooling, particularly around contract deployment and transaction signing.If you are looking to deploy and create NFTs in Javascript, the Ethereum organization has an excellent tutorial.
Thanks for the amazing tutorial! As I'm new to development in blockchain technology, your tutorial helped me understand the process a lot! Appreciate writing out the steps in a way that's easy to follow!
The timing for this blog post is impeccable. After setting up my EVM compatible blockchain on Cosmos last week, my manager gave me the task of creating a test NFT. Even though I am a 20 year veteran in the blockchain community and a well distinguished senior engineer, I could not figure out how to mint an NFT on my own. I have been extremely stressed this entire week due to this, but after a quick read of noop's instructions, everything clicked and I am excited to show my newly minted NFT to my manager on Monday! Thank you for sharing this and I am looking forward to your next article.