diff --git a/public/images/nft-create/pinata-key-two-permissions.png b/public/images/nft-create/pinata-key-two-permissions.png
new file mode 100644
index 000000000..4d027399e
Binary files /dev/null and b/public/images/nft-create/pinata-key-two-permissions.png differ
diff --git a/public/images/nft-create/taquito-application-created-nft.png b/public/images/nft-create/taquito-application-created-nft.png
new file mode 100644
index 000000000..b58d383ee
Binary files /dev/null and b/public/images/nft-create/taquito-application-created-nft.png differ
diff --git a/public/images/nft-create/taquito-application-flow.png b/public/images/nft-create/taquito-application-flow.png
new file mode 100644
index 000000000..c18abe6af
Binary files /dev/null and b/public/images/nft-create/taquito-application-flow.png differ
diff --git a/public/images/nft-create/taquito-application-home.png b/public/images/nft-create/taquito-application-home.png
new file mode 100644
index 000000000..298bd8f99
Binary files /dev/null and b/public/images/nft-create/taquito-application-home.png differ
diff --git a/public/images/nft-create/web-ligo-ide-account.png b/public/images/nft-create/web-ligo-ide-account.png
new file mode 100644
index 000000000..a42085ad7
Binary files /dev/null and b/public/images/nft-create/web-ligo-ide-account.png differ
diff --git a/public/images/nft-create/web-ligo-ide-ghostnet.png b/public/images/nft-create/web-ligo-ide-ghostnet.png
new file mode 100644
index 000000000..d9368eac8
Binary files /dev/null and b/public/images/nft-create/web-ligo-ide-ghostnet.png differ
diff --git a/public/images/nft-pinata/image10.png b/public/images/nft-pinata/image10.png
deleted file mode 100644
index c59d76225..000000000
Binary files a/public/images/nft-pinata/image10.png and /dev/null differ
diff --git a/public/images/nft-pinata/image14.png b/public/images/nft-pinata/image14.png
deleted file mode 100644
index 811e90e3e..000000000
Binary files a/public/images/nft-pinata/image14.png and /dev/null differ
diff --git a/public/images/nft-pinata/image15.png b/public/images/nft-pinata/image15.png
deleted file mode 100644
index 5cbb68ede..000000000
Binary files a/public/images/nft-pinata/image15.png and /dev/null differ
diff --git a/public/images/nft-pinata/image19.png b/public/images/nft-pinata/image19.png
deleted file mode 100644
index f377da6db..000000000
Binary files a/public/images/nft-pinata/image19.png and /dev/null differ
diff --git a/public/images/nft-pinata/image20.png b/public/images/nft-pinata/image20.png
deleted file mode 100644
index 765bcfa9e..000000000
Binary files a/public/images/nft-pinata/image20.png and /dev/null differ
diff --git a/public/images/nft-pinata/image21.png b/public/images/nft-pinata/image21.png
deleted file mode 100644
index d1d26f3c6..000000000
Binary files a/public/images/nft-pinata/image21.png and /dev/null differ
diff --git a/public/images/nft-pinata/image22.png b/public/images/nft-pinata/image22.png
deleted file mode 100644
index 894aab9fe..000000000
Binary files a/public/images/nft-pinata/image22.png and /dev/null differ
diff --git a/public/images/nft-pinata/image23.png b/public/images/nft-pinata/image23.png
deleted file mode 100644
index 1717c401c..000000000
Binary files a/public/images/nft-pinata/image23.png and /dev/null differ
diff --git a/public/images/nft-pinata/image24.png b/public/images/nft-pinata/image24.png
deleted file mode 100644
index 266f1b3ff..000000000
Binary files a/public/images/nft-pinata/image24.png and /dev/null differ
diff --git a/public/images/nft-pinata/image26.png b/public/images/nft-pinata/image26.png
deleted file mode 100644
index 62350a241..000000000
Binary files a/public/images/nft-pinata/image26.png and /dev/null differ
diff --git a/public/images/nft-pinata/image28.png b/public/images/nft-pinata/image28.png
deleted file mode 100644
index f972a2579..000000000
Binary files a/public/images/nft-pinata/image28.png and /dev/null differ
diff --git a/public/images/nft-pinata/image29.png b/public/images/nft-pinata/image29.png
deleted file mode 100644
index e0bc422cd..000000000
Binary files a/public/images/nft-pinata/image29.png and /dev/null differ
diff --git a/public/images/nft-pinata/image32.png b/public/images/nft-pinata/image32.png
deleted file mode 100644
index 766dc5736..000000000
Binary files a/public/images/nft-pinata/image32.png and /dev/null differ
diff --git a/public/images/nft-pinata/image33.png b/public/images/nft-pinata/image33.png
deleted file mode 100644
index 29c763bf0..000000000
Binary files a/public/images/nft-pinata/image33.png and /dev/null differ
diff --git a/public/images/nft-pinata/image34.png b/public/images/nft-pinata/image34.png
deleted file mode 100644
index 7a01b2728..000000000
Binary files a/public/images/nft-pinata/image34.png and /dev/null differ
diff --git a/public/images/nft-pinata/image36.png b/public/images/nft-pinata/image36.png
deleted file mode 100644
index bb5c9cdde..000000000
Binary files a/public/images/nft-pinata/image36.png and /dev/null differ
diff --git a/public/images/nft-pinata/image37.png b/public/images/nft-pinata/image37.png
deleted file mode 100644
index 7e63176b5..000000000
Binary files a/public/images/nft-pinata/image37.png and /dev/null differ
diff --git a/public/images/nft-pinata/image41.png b/public/images/nft-pinata/image41.png
deleted file mode 100644
index 805a256fc..000000000
Binary files a/public/images/nft-pinata/image41.png and /dev/null differ
diff --git a/public/images/nft-pinata/image8.png b/public/images/nft-pinata/image8.png
deleted file mode 100644
index 9a7004f53..000000000
Binary files a/public/images/nft-pinata/image8.png and /dev/null differ
diff --git a/public/images/nft-pinata/image9.png b/public/images/nft-pinata/image9.png
deleted file mode 100644
index 8fd278c79..000000000
Binary files a/public/images/nft-pinata/image9.png and /dev/null differ
diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx
index 38e6c7814..e52a442df 100644
--- a/src/components/Layout.jsx
+++ b/src/components/Layout.jsx
@@ -294,8 +294,12 @@ const tutorialNavigation = [
href: '/tutorials/create-an-nft',
children: [
{
- title: 'Mint NFT using Taquito and Pinata',
- href: '/tutorials/create-an-nft/nft-pinata',
+ title: 'Create an NFT with the `tznft` tool',
+ href: '/tutorials/create-an-nft/nft-tznft',
+ },
+ {
+ title: 'Create a web app that mints NFTs',
+ href: '/tutorials/create-an-nft/nft-taquito',
},
],
},
diff --git a/src/pages/tutorials/create-an-nft/index.md b/src/pages/tutorials/create-an-nft/index.md
index 3c4b63b2c..f38b1e45c 100644
--- a/src/pages/tutorials/create-an-nft/index.md
+++ b/src/pages/tutorials/create-an-nft/index.md
@@ -1,521 +1,13 @@
---
id: create-an-nft
title: Create an NFT
-authors: 'Sol Lederer, Tim McMackin'
-lastUpdated: 18th September 2023
+authors: 'Tim McMackin'
+lastUpdated: 20th September 2023
---
-This tutorial covers how to create a collection of NFTs on Tezos and manipulate them using the `tznft` command-line tool.
-No prior knowledge of NFTs or Tezos is required, but you need basic experience with your computer's command-line terminal to paste commands and run them.
+These tutorials cover how you can create NFTs on Tezos.
+There are two tutorials that show different ways of creating NFTS:
-In this tutorial, you will learn:
+- The tutorial [Create an NFT with the `tznft` tool](./nft-tznft/) covers how to create NFTs on the command line with the `tznft` tool, a tool that helps you create, test, and manipulate NFTs in a local sandbox before you mint them on the live network.
-- What NFTs are
-- How to install and start a local Tezos sandbox environment
-- How to create metadata files to describe NFT collections and individual NFTs
-- How to deploy (or _mint_) the NFTs to the sandbox
-- How to transfer NFTs and change operator permissions for them
-- How to mint NFTs to a testnet
-
-## What is a non-fungible token (NFT)?
-
-An NFT is a special type of blockchain token that represents something unique.
-Fungible tokens such as XTZ and real-world currencies like dollars and euros are interchangeable; each one is the same as every other.
-By contrast, each NFT is unique and not interchangeable.
-NFTs can represent ownership over digital or physical assets like virtual collectibles or unique artwork, or anything that you want them to represent.
-
-Like other types of Tezos tokens, a collection of NFTs is managed by a smart contract.
-The smart contract defines what information is in each token and how the tokens behave, such as what happens when a user transfers an NFT to another user.
-
-In this tutorial, you create NFTs that comply with the FA2 standard (formally known as the [TZIP-12](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) standard), the current standard for tokens on Tezos.
-The FA2 standard creates a framework for how tokens behave on Tezos, including fungible, non-fungible, and other types of tokens.
-It provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata.
-
-## Prerequisites
-
-To run this tutorial you need Node.JS, NPM, and Docker Desktop to install and use the `tznft` CLI tool, which helps you create and test NFT collections on Tezos.
-
-- Install Node.JS version 18 (not 20) and NPM.
-See .
-You can verify that they are installed by running these commands:
-
- ```bash
- node --version
- npm --version
- ```
-
- If you see a message with the versions of Node.JS and NPM, they are installed correctly.
-
-- To install Docker Desktop, see .
-Make sure to start Docker Desktop after you install it.
-
-- To install the `tznft` tool, run this command:
-
- ```bash
- npm install -g @oxheadalpha/tznft
- ```
-
- You can verify that it is installed by running this command:
-
- ```bash
- tznft --version
- ```
-
- If you see a message with the version of the `tznft` tool, it is installed correctly.
-
-## Create a project folder
-
-1. Create a folder to store your NFT configuration files:
-
- ```bash
- mkdir nft-tutorial
- cd nft-tutorial
- ```
-
-3. Create a starter NFT configuration file:
-
- ```bash
- tznft init
- ```
-
- The resulting file, named `tznft.config`, contains information about the Tezos networks that are available for you to work with, including the [Ghostnet](https://teztnets.xyz/ghostnet-about) test network and the local sandbox that you set up in the next steps.
- The `tznft` tool requires this file, so the commands in the following steps work only from the directory that you ran `tznft init` in.
-
-4. Check that the default active network is "sandbox:"
-
- ```bash
- tznft show-network
- ```
-
- The response should show that the active network is "sandbox."
- The sandbox is a local simulation of Tezos that you can use to test your work.
-
-5. Set up a local Tezos sandbox by running this command:
-
- ```bash
- tznft bootstrap
- ```
-
- This command can take time to run, so wait until you see the message "sandbox started."
-
- This command uses the [Flextesa](https://tezos.gitlab.io/flextesa/) tool to create a local sandbox in a Docker container.
- This sandbox is a local instance of Tezos that you can use to test your work before you send it to a live Tezos network.
- The sandbox comes preconfigured with two account aliases named `bob` and `alice` that you can use to test account operations like creating and transferring NFTs.
-
- You can verify that the sandbox is running by running the command `docker ps` and looking for a container named `flextesa-sandbox`.
- To stop the container, run the command `tznft kill-sandbox`, but beware that stopping the container sets the sandbox back to its initial state, which removes any changes you made or contracts or tokens that you created.
-
- Unlike the live Tezos networks, this sandbox bakes a new block every 5 seconds.
- Therefore, commands that you run on the sandbox can take a few seconds to complete.
-
-## Create NFT metadata
-
-The first step in creating NFTs is to create local metadata files that describe the collection and the individual NFTs:
-
-1. Create a collection metadata file by running this command:
-
- ```bash
- tznft create-collection-meta my_collection
- ```
-
- The new metadata file is named `my_collection.json` and has information such as the name, description, home page, and creator of the collection.
- It also includes the interfaces that the NFTs support, including the TZIP-12 interface that was mentioned earlier.
-
-1. Optional: Edit the `my_collection.json` file to put your information in the `name`, `description`, and `authors` fields.
-
-1. Validate the collection by running this command:
-
- ```bash
- tznft validate-collection-meta my_collection.json
- ```
-
- If you did not change values in the file, this command may show warnings that the collection uses placeholder values.
- You can continue with these placeholder values or insert your own information.
- If there are any errors, make sure that the file is valid JSON.
-
-1. Create a metadata file for the first NFT in the collection by running this command:
-
- ```bash
- tznft create-nft-meta Token1 bob ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
- ```
-
- This command creates a metadata file named `Token1.json` with default information about the NFT.
- It includes the minter's account address and URIs to pictures that represent the NFT.
- In this case, the `ipfs` URI links to a picture of the Tezos logo, which you can see at this link: .
-
-1. Optional: Edit the metadata such as the name and description fields in the `Token1.json` file.
-
-1. Optional: Edit other fields in the metadata based on the FA2 standard.
-
- For example, you can expand the `attributes` section with other attributes.
- Each attribute must have the `name` and `value` fields and can optionally have a `type` field, as in this example:
-
- ```json
- "attributes": [
- {
- "name": "My string attribute",
- "value": "String attribute value"
- },
- {
- "name": "My integer attribute",
- "value": "5",
- "type": "integer"
- },
- {
- "name": "My number attribute",
- "value": "12.3",
- "type": "number"
- },
- {
- "name": "My percentage attribute",
- "value": "19",
- "type": "percentage"
- }
- ]
- ```
-
- By default the `artifactUri`, `displayUri`, and `thumbnailUri` fields are set to the picture that you passed in the `tznft create-nft-meta` command.
- You can update these to different images to allow applications to show media to represent the NFT.
- You can also add a `formats` object to provide media in different formats, such as different image, video, or audio formats:
-
- ```json
- "formats": [
- {
- "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
- "hash": "a56017a1317b1bc900acdaf600874c00e5c048d30894f452049db6dcef6e4f0d",
- "mimeType": "image/svg+xml"
- },
- {
- "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
- "hash": "8968db6bde43255876c464613a31fbd0416ca7d74be4c5ae86c1450418528302",
- "mimeType": "image/png",
- "dimensions": {
- "value": "512x512",
- "unit": "px"
- }
- },
- {
- "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
- "hash": "d4a93fc8d8991caa9b52c04c5ff7edf5c4bc29317a373e3a97f1398c697d6714",
- "mimeType": "model/gltf+json"
- }
- ]
- ```
-
- For specifics about what is allowed in an NFT metadata file, see the [TZIP-21](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-21/tzip-21.md) standard.
-
-1. Validate the NFT metadata file with this command:
-
- ```bash
- tznft validate-nft-meta Token1.json
- ```
-
- If the file does not validate, verify that it is valid JSON and has only the fields listed in the [TZIP-21](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-21/tzip-21.md) standard.
-
-1. Create at least one more metadata file for other NFTs by running commands like this example:
-
- ```bash
- tznft create-nft-meta Token2 bob ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
- ```
-
-## Configure IPFS storage
-
-Because storage space on blockchains is expensive, developers don't put entire token metadata files on Tezos.
-Instead, they configure decentralized storage for the NFT data and put only the link to that data on Tezos itself.
-In this section, you set up storage for the NFT metadata using the InterPlanetary File System (IPFS) protocol.
-
-IPFS requires authentication just like blockchain transactions, so in this section you set up an account with the Pinata IPFS provider and use it to upload (or _pin_) the NFT data to IPFS.
-
-1. Create a free Pinata account at .
-
-1. Go to the API Keys tab and click **New Key**.
-
-1. On the Create New API Key page, expand API Endpoint Access and enable the `pinFileToIPFS` permission, as in this picture:
-
- ![Selecting the permissions for the Pinata key](/images/nft-create/pinata-key-permissions.png)
-
-1. In the **Key Name** field, give the key a name, such as "My Key."
-
-1. Click **Create Key**.
-
- The API Key Info window shows the API key and secret, which you must copy immediately, because it is not shown again.
-
-1. Copy the API Key and API Secret fields and save the values on your computer.
-You need these values in the next section.
-
- You can see the new API key on the API Keys tab:
-
- ![The new Pinata API key in the Pinata web app](/images/nft-create/created-pinata-key.png)
-
-1. Add the API key and secret to your local `tznft` configuration by running this command, replacing `$PINATA_KEY` and `$PINATA_SECRET` with your API key and secret:
-
- ```bash
- tznft set-pinata-keys $PINATA_KEY $PINATA_SECRET --force
- ```
-
- This command stores the key and secret in the `tznft.json` file, so be careful not to share this file.
-
-1. Pin the first NFT metadata file to IPFS by running this command and passing the file name and a tag for the NFT, which can be the same as the file name:
-
- ```bash
- tznft pin-file Token1.json --tag Token1
- ```
-
- The command returns the URI for the data on IPFS, which starts with `ipfs://`.
-
-1. Copy the IPFS URI, because you will need it later.
-
-1. In the same way, pin the other NFT metadata files with the `tznft pin-file` command and save their URIs.
-
-1. Optional: Verify that the files are pinned successfully by opening the Pinata app to the Files page, as in this picture:
-
- ![The Files tab on Pinata, showing three NFT metadata files](/images/nft-create/pinned-nft-meta.png)
-
-Now that the metadata is pinned to IPFS, you can create NFTs that link to this metadata.
-
-## Mint NFTs
-
-Creating NFTs is called _minting_.
-First, you create the smart contract to manage the NFTs.
-Then, you mint one or more NFTs with that contract.
-The related `tznft` commands use the configuration files that you created earlier.
-
-1. Create the collection contract from the metadata file by running this command:
-
- ```bash
- tznft create-collection bob --meta_file my_collection.json --alias my_collection
- ```
-
- This command takes the alias of the user who is the owner of the collection.
- In this case, the owner is one of the default accounts in the sandbox.
- The command also includes the metadata file and an optional local alias for the collection.
-
- The command also updates the `tznft.json` file with information about the new collection, including the address of the smart contract that manages the collection.
- This smart contract is a pre-compiled FA2 NFT contract written in the [LIGO](https://ligolang.org/) smart contract language.
- You can write your own smart contracts to manage NFTs, but using this contract prevents errors and provides all of the functionality needed to create, transfer, and manage NFTs.
-
-1. Run this command to create a token and set Bob as the owner, replacing the IPFS URI with the URI that the `tznft pin-file` command returned in the previous section:
-
- ```bash
- tznft mint bob my_collection --tokens '1, ipfs://abcde12345'
- ```
-
- This command includes these parameters:
-
- - The alias or address of the initial owner.
- - The alias of the collection from the `tznft create-collection` command.
- - The ID number and IPFS URI for the NFTs in a comma-delimited string.
-
- If you forgot the IPFS URI, you can look it up in the Pinata app on the Files tab.
- This tab has a column labeled "Content Identifier (CID)."
- To create the IPFS URI, add the content identifier to the string `ipfs://`.
-
- The response in the terminal says that the token was minted.
-
-1. Run the `tznft mint` command to mint the other NFTs.
-You can create more than one NFT in a single command by providing more than one string after the `--tokens` switch, as in this example:
-
- ```bash
- tznft mint bob my_collection --tokens '2, ipfs://defgh12345' '3, ipfs://ijklm12345'
- ```
-
-1. Verify that the NFTs were minted successfully by getting their metadata with the `tznft show-meta` command:
-
- ```bash
- tznft show-meta bob --nft my_collection --tokens 1 2
- ```
-
- If the NFTs were created successfully, the command prints the metadata that you pinned to IPFS.
-
-Now the NFTs are minted to the sandbox.
-Because these NFTs are only on your local computer, in the Flextesa sandbox, you can interact with them only locally.
-They exist as long as you keep the Flextesa Docker container running, which you started with the `tznft bootstrap` command.
-
-## Transferring and manipulating NFTs
-
-The `tznft` command provides commands to manipulate NFTs locally, including transferring them between accounts.
-Just like transactions on live blockchain networks, the transaction signer must have permission to transfer or manipulate the NFTs.
-Currently, only Bob has access to the NFTs, so the `tznft` commands include him as the signer of most transactions.
-
-1. Use the `tznft show-balance` command to print information about Bob's NFTs.
-This command takes the alias or address of the collection, the signer of the transaction, the owner of the NFTs, and the IDs of one or more NFTs.
-
- ```bash
- tznft show-balance --nft my_collection --signer bob --owner bob --tokens 1 2
- ```
-
- Because NFTs are unique, the response shows a balance of 1 if the account owns the token and 0 if it does not, as in this picture:
-
- ![THe results of the `show-balance` command, with two NFTs in Bob's account](/images/nft-create/show-balance-bob.png)
-
-1. Use the `tznft show-balance` command to print information about Alice's NFTs:
-
- ```bash
- tznft show-balance --nft my_collection --signer alice --owner alice --tokens 1 2
- ```
-
- Because Bob is the initial owner of all of the NFTs, Alice's balance is 0 for each NFT.
-
-1. Use the `tznft transfer` command to transfer one or more NFTs from Bob to Alice.
-This command takes the alias or address of the collection, the signer, and one or more comma-separated strings with the current owner, the new owner, and the ID of the NFT to transfer.
-For example, this command transfers NFTs 1 and 2 from Bob to Alice:
-
- ```bash
- tznft transfer --nft my_collection --signer bob --batch 'bob, alice, 1' 'bob, alice, 2'
- ```
-
-1. Verify that the transfer worked by checking Alice's balance with the `tznft show-balance` command:
-
- ```bash
- tznft show-balance --nft my_collection --signer alice --owner alice --tokens 1 2
- ```
-
- Now Alice's balance is 1 for each token that transferred.
- Alice is in control of these NFTs and Bob can no longer transfer them.
-
-1. Verify that Bob does not have control over the transferred NFTs by trying to transfer them back from Alice's account to Bob's account:
-
- ```bash
- tznft transfer --nft my_collection --signer bob --batch 'alice, bob, 1' 'alice, bob, 2'
- ```
-
- The response shows the error "FA2_NOT_OPERATOR" because Bob's account is not in control of these NFTs.
-
- You can give Bob's account control over the NFTs by making his account an operator of those NFTs.
-
-1. Make Bob an operator of Alice's NFTs by passing the token IDs to the `tznft update-ops` command:
-
- ```bash
- tznft update-ops alice --nft my_collection --add 'bob, 1' 'bob, 2'
- ```
-
-1. Try again to transfer the NFTs from Alice's account to Bob's account with a transaction signed by Bob:
-
- ```bash
- tznft transfer --nft my_collection --signer bob --batch 'alice, bob, 1' 'alice, bob, 2'
- ```
-
-1. Check Bob's account to see that he now owns the NFTs:
-
- ```bash
- tznft show-balance --nft my_collection --signer bob --owner bob --tokens 1 2
- ```
-
-## Freeze the collection
-
-When you have created all of the NFTs that you want, freeze the collection so it cannot be changed and no more NFTs can be added by running this command:
-
-```bash
-tznft mint-freeze bob my_collection
-```
-
-## Mint tokens on a testnet
-
-So far, the NFTs that you have created are available only in your local sandbox.
-When you are satisfied with the NFTs and how they behave, you can send them to a testnet and test them there.
-You can use the same configuration files and IPFS data as you used on the sandbox.
-
-By default, the `tznft.json` file has configuration information for the Tezos Ghostnet testnet, where you can test your tokens on a running Tezos network.
-
-1. Show the available networks by running the command `tznft show-network --all` and verify that the testnet is in the list.
-
-1. Change the `tznft` tool to use the testnet instead of your local sandbox:
-
- ```bash
- tznft set-network testnet
- ```
-
-1. Run the `tznft bootstrap` command to get the testnet ready for your use.
-Now that the network is set to testnet, this command deploys a helper balance inspector contract to testnet that allows the `tznft` command to get information from the testnet.
-You only need to run this command for testnet once.
-
-1. Create an alias on the testnet to own the NFTs.
-You can do this in either of these two ways:
-
- - If you have an existing Tezos wallet that supports testnets (such as Temple wallet), copy the private key from that wallet and use the `tznft add-alias` command to create a local alias for it.
- For example, this command creates a wallet with the alias `my-account`:
-
- ```bash
- tznft add-alias my-account $TEZOS_PRIVATE_KEY
- ```
-
- - Create a local wallet with the installation of the `octez-client` command within the Flextesa Docker container:
-
- 1. Generate local keys with the `octez-client gen keys` command.
- For example, this command creates keys for a wallet with the alias `my-account`:
-
- ```bash
- docker exec flextesa-sandbox octez-client gen keys my-account
- ```
-
- 1. Get the keys for the wallet with this command:
-
- ```bash
- docker exec flextesa-sandbox octez-client show address my-account -S
- ```
-
- The response includes the hash, public key, and secret key for the wallet.
- For example, in this response, the secret key starts with "edsk3WR":
-
- ![The keys for the new account](/images/nft-create/new-key-output.png)
-
- 1. Add the secret key as an alias with the `tznft` command, replacing `$TEZOS_PRIVATE_KEY` with the value of the secret key from the previous command:
-
- ```bash
- tznft add-alias my-account $TEZOS_PRIVATE_KEY
- ```
-
- 1. Add funds to the new wallet by going to the Ghostnet faucet at , pasting the wallet's hash in the "Or fund any address" field, and clicking a button to request tokens.
- The wallet needs tokens to pay the fees to create the collection and mint the tokens on Ghostnet.
-
-1. Create the collection on the testnet.
-The command is the same as for the sandbox, and you can create a new collection file or use the file from the sandbox.
-Similarly, you can use the same collection alias because `tznft` keeps aliases separate on different networks, but be sure not to get the aliases confused.
-
- ```bash
- tznft create-collection my-account --meta_file my_collection.json --alias my_collection
- ```
-
-1. Mint the tokens on the testnet.
-The command is the same as for the sandbox:
-
- ```bash
- tznft mint my-account my_collection --tokens '1, ipfs://abcde12345'
- ```
-
- You can add more NFTs until you freeze the collection.
-
-1. View your token balances.
-The command is the same as for the sandbox:
-
- ```bash
- tznft show-balance --nft my_collection --signer my-account --owner my-account --tokens 1
- ```
-
-1. View the tokens on a block explorer:
-
- 1. Get the address of the collection on the testnet from the `testnet` section of the `tznft.json` file.
- The address starts with "KT1".
-
- 1. Go to a block explorer, such as .
-
- 1. Set the block explorer to use testnet instead of Tezos mainnet.
-
- 1. In the search field, paste the address of the collection and press Enter.
-
- The block explorer shows information about the contract that manages the NFTs, including a list of all NFTs in the contract, who owns them, and a list of recent transactions.
-
-Now the NFTs are on Tezos ghostnet and you can transfer and manipulate them just like you did in the sandbox.
-You may need to create and fund more account aliases to transfer them, but the commands are the same.
-For example, to transfer NFTs to an account with the alias `other-account`, run this command:
-
-```bash
-tznft transfer --nft my_collection --signer my-account --batch 'my-account, other-account, 1' 'my-account, other-account, 2'
-```
-
-## Summary
-
-Now you can create, test, and deploy NFTs locally and to testnets.
-The process for minting NFTs to Tezos mainnet is the same, but you must use an account with real XTZ in it to pay the transaction fees.
-
-If you want to continue working with these NFTs, try creating a marketplace for them as described in the tutorial [Build an NFT Marketplace](../build-an-nft-marketplace).
+- The tutorial [Create a web app that mints NFTs](./nft-taquito/) covers how to create a web application that allows users to mint their own NFTs using the [Taquito](https://tezostaquito.io/) JavaScript/TypeScript SDK for Tezos.
diff --git a/src/pages/tutorials/create-an-nft/nft-pinata/index.md b/src/pages/tutorials/create-an-nft/nft-pinata/index.md
deleted file mode 100644
index 7ed0db410..000000000
--- a/src/pages/tutorials/create-an-nft/nft-pinata/index.md
+++ /dev/null
@@ -1,258 +0,0 @@
----
-id: nft-pinata
-title: Mint an NFT using Taquito and Pinata
-lastUpdated: 10th July 2023
----
-
-In this guide, you will get an overview of the mechanics of NFTs, in general, and on Tezos. If you want to get your hands dirty, you will get an explanation of how the code of an NFT platform works, on the contract level but also on the app level. This article is mainly designed for beginner programmers who want to get a better idea of the code involved in the creation and use of NFTs, but non-technical readers will also find important information to deepen their understanding of both NFTs and the Tezos blockchain.
-
-You will learn how to build a simple NFT platform backed by a smart contract on the Tezos blockchain capable of minting, transferring, and burning NFTs while hosting their metadata on the IPFS. You need a basic knowledge of JavaScript to follow along in part 2. We will only have a high-level overview of the smart contract so no knowledge of Ligo is required, but a general knowledge of programming concepts would help.
-
-If you just want the code, you can find the complete source code in [this GitHub repository](https://github.com/claudebarde/taquito-pinata-tezos-nft). The **backend** folder holds the code that generates the metadata and pins the picture and the metadata to the IPFS. The **frontend** folder holds the code for the app that allows users to connect to their Tezos wallet and mint NFTs. The **contract** folder holds the FA2 contract written in CameLigo to mint, transfer, and burn NFTs.
-
-Now, let’s start by understanding better what NFTs are and how they work!
-
-> Note: the first part of the article doesn’t require any knowledge in programming but to follow the second part, you need to have a basic knowledge of JavaScript.
-
-
-## What is an NFT?
-
-“NFT” is an acronym that stands for “non-fungible token”. A token is called “fungible” when you can exchange it for another identical token without losing any value. For example, the coins in your wallet are fungible. If we both have 1 euro and we exchange our coins, we both still have 1 euro. An NFT is non-fungible due to its unique nature: there is no other token 100% identical with the same value.
-
-> Note: the word “token” in this context means “a list of data that altogether represent something”. An NFT is a token because it is made of data related to something from the real world.
-
-Although NFTs are mostly known for representing artwork, they can actually represent anything and have been used to represent certificates, contracts, users, etc. One of the most important things to remember about NFTs is that they are not a “physical object” but a representation of somebody’s ownership. When you own the NFT for an artwork, it doesn’t prove you have the artwork with you, but it proves you have ownership of the artwork.
-
-On a technical level, NFTs are stored in smart contracts, little programs that live on blockchains and that are able to execute and store data. This point is particularly important to remember: if you have an NFT on your favorite platform, the data is saved in the contract the platform is built upon and the other platforms in the ecosystem built on different contracts are not aware of your NFT. In most cases, this also means you cannot transfer your NFT from one platform to another. If you are the creator of the NFT, you can “_burn_” it \(i.e destroy it\) and “_mint_” it \(i.e create it\) on another platform.
-
-On Tezos, NFTs are stored in contracts that follow the [TZIP-12 standard](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) that you will often see labeled as “**FA2 contracts**”. The NFT is made of 2 main parts: an id to identify it in the contract and metadata to describe what the NFT is. The contract holds a _ledger_, a table where the ids of every NFT are matched with the address of their owner. Another table in the contract matches the ids of every NFT with their metadata, so knowing the id of an NFT, you can easily find out who owns it and what it represents. The metadata is just text and can be stored directly in the contract or somewhere else on the Internet, in which case the address of the metadata is stored in the contract.
-The contract that holds the NFTs can implement different features according to the platform, it can allow its users to transfer their NFTs to other users of the contract, sell them, burn them, track royalties for every purchase, etc. As the smart contract is just a piece of code, the possibilities are virtually limitless!
-
-### Useful lexicon
-
-* To mint an NFT: to create an NFT and record its data into a smart contract
-* To burn an NFT: to delete the data associated with an NFT from a smart contract
-* A smart contract: a piece of autonomous code that lives on a blockchain
-* The [IPFS](https://ipfs.io/#how): a network of computers providing decentralized storage
-* To pin on the IPFS: storing data on the IPFS
-
-### Creating an NFT platform on Tezos
-
-Now comes the time to look at some code 👀
-
-Our simple NFT platform will be made of 3 different parts:
-
-* The **contract** written in Ligo will securely store the NFT ids and metadata and allow the users to mint, transfer, and burn their tokens
-* The **backend** app written in Express \(JavaScript\) will provide us with a secure way of pinning the metadata to the IPFS and ensure that they are not tampered with
-* The **frontend** app written in Svelte \(JavaScript\) will provide a user-friendly interface to interact with the contract and the backend.
-
-These three parts of the platform will communicate with each other at some point: the frontend talks to the contract when a user starts the minting process of a new NFT and to the backend to pin the metadata and the picture on the IPFS. The backend talks to the frontend to provide the IPFS hash \(also called a [**CID**](https://docs.ipfs.io/concepts/content-addressing/)\) before minting the NFT. The contract just listens because Michelson contracts do not return any value, they don’t talk 🙂
-
-#### The contract
-
-The goal of this tutorial is not to create an FA2 contract from scratch but rather to understand the principles of such a contract. You can find amazing templates of FA2 contracts in the [TQ Tezos repository on Github](https://github.com/tqtezos/smart-contracts). This app uses a modified version of their NFT contract.
-
-An FA2 contract generally consists of the following parts:
-
-* A bigmap called **ledger** whose purpose is to associate the token ids created in the contract with their owner
-* A bigmap called **metadata** records the metadata associated with the contract itself \(its name, version, etc.\)
-* A bigmap called **token\_metadata** records the metadata associated with every token stored in the contract
-* An entrypoint called **transfer** allows \(or forbids\) the transfer of one or multiple tokens from one address to another
-* An entrypoint called **update\_operators** allows owners of tokens to give permission to other addresses to handle their tokens. This can be useful, for example, if the contract implements a marketplace where you can set your NFTs on sale and let the contract handle the sale
-* An entrypoint often called **mint** \(or its variations\) creates new tokens with the provided data. This is where the token metadata is provided to be stored and where the token id is assigned to the NFT that’s being created.
-
-> Note: in addition to these entrypoints and bigmaps, an NFT contract can implement other structures according to its use case, for example, you can have a bigmap with all the NFTs on sale and different entrypoints to set an NFT on sale, to purchase it or to withdraw it from the marketplace, you can have a burn entrypoint to destroy the NFTs you don’t want on the platform anymore, etc.
-
-> It is essential to understand the difference between “**metadata”** and “token\_metadata”. The “metadata” bigmap holds information about the contract itself while the “token\_metadata” bigmap holds information about every single token stored in the contract.
-
-The contract we will use for this tutorial is a basic FA2 contract that implements the structures and entrypoints described above. The users of the platform will be able to mint, transfer and burn their NFTs.
-
-You can have a look at the contract [at this address](https://github.com/claudebarde/taquito-pinata-tezos-nft/blob/main/contract/NFTS_contract.mligo).
-
-#### The backend
-
-The backend of the app is a simple Express app written in TypeScript. The app only exposes a single route, “`/mint`”, that will be called to create the NFT metadata and pin it on the IPFS with the associated picture. Before continuing with the code, you must set up an account with [Pinata](https://pinata.cloud/) and get your API keys.
-
-First step, sign up to create an account and follow the instructions:
-
-![](/images/nft-pinata/image36.png)
-
-When you are all set up, click on “API Keys” in the left panel:
-
-![](/images/nft-pinata/image22.png)
-
-To finish, click on “_+ New Key_” to get your keys:
-
-![](/images/nft-pinata/image9.png)
-
-You will get an API key and a secret key, copy-paste them somewhere safe to use them later as they won’t be visible anymore after that.
-
-The app uses 5 packages:
-
-![](/images/nft-pinata/image34.png)
-
-* **express** allows us to set up a server app quickly
-* **@pinata/sdk** gives us convenient functions to interact with our Pinata account
-* **fs** \(or file system\) is a package already installed in Node JS that we will use to manipulate the picture sent by the user
-* **cors** allows us to set up a CORS policy for our app and avoid unwanted requests from unauthorized sources
-* **multer** is a package that will make handling the picture sent by the user a lot easier
-
-Next, we have to do some setup before writing the “mint” endpoint. Because I used [Heroku](https://id.heroku.com/login) to host the app, there is also some Heroku-specific setting up to do to start the server:
-
-![](/images/nft-pinata/image28.png)
-
-Heroku doesn’t like it too much when you try to tell it which port to use 😅 So for the production version, you must let Heroku decide on which port your app is going to listen to.
-
-Setting up the Pinata SDK will also depend on the `process.env.NODE_ENV` variable. You can choose to have your API keys in a separate file, both in the development and production environment, but Heroku lets you define environment variables that are automatically injected in your build and stored securely, so this is generally the solution you would prefer, i.e having a separate file with your keys for development and having your keys in environment variables for production. Whichever solution you choose, the Pinata SDK can be easily instantiated by passing the API key and the secret key as parameters:
-
-![](/images/nft-pinata/image20.png)
-
-Let’s finish setting up the server app:
-
-![](/images/nft-pinata/image29.png)
-
-In the `corsOptions` variable, we indicate the URLs that are allowed to communicate with the server. During development, you should allow `localhost` with the port you are using, then you can use the URL of your app.
-
-Now, we can set up the different middlewares:
-
-* `upload` is a middleware returned by `multer` that we set by passing an object whose `dest` property is the path to the folder where we want to store the picture we will receive
-* `cors` with the options set up above
-* `express.json({ limit: “50mb” })` allows the app to receive up to 50 MB of JSON \(which will be necessary to pass the picture\)
-* `express.urlencoded({ limit: “50mb”, extended: true, parameterLimit: 50000 })` works in conjunction with the setting above and allows the server to receive a picture up to 50 MB in size
-
-Now, everything is set up, let’s write the `mint` endpoint!
-
-![](/images/nft-pinata/image32.png)
-
-This is going to be a `POST` endpoint \(because of the picture we need to receive\) that’s going to be called when a request comes to the `/mint` route. We use the `single` method of the `upload` middleware from `multer` with the `“image”` parameter, which tells `multer` that we are expecting to receive one image on this endpoint. We then store the request in a new variable cast to the `any` type because TypeScript will raise an error later as it is unaware that the request has been modified by `multer`.
-
-The request comes with the file sent by the user:
-
-![](/images/nft-pinata/image14.png)
-
-We check first if a file was provided with `if(!multerReq.file)`, if there is none, the request fails with a 500 error code and a message. If a file was provided, we store the filename available at `multerReq.file.filename`.
-
-After checking if the request came along with a file, we’re going to verify that our connection to the Pinata service works properly:
-
-![](/images/nft-pinata/image21.png)
-
-The instance of the Pinata SDK provides a method called `testAuthentication` that verifies that you are properly authenticated. With that done, we can go ahead and pin the user’s picture in Pinata:
-
-![](/images/nft-pinata/image24.png)
-
-> Note: we have to pin the picture first before pinning the metadata to the IPFS because the metadata must include the hash of the picture.
-
-To pin a file to the IPFS using the Pinata SDK, you must provide a [readable stream](https://nodejs.org/api/stream.html). This can be easily achieved by using the `createReadStream` method of the `fs` package that you call with the path of the file that you want to convert to a readable stream. Remember that `multer` automatically saved the image in the request in the `uploads` folder, so this is where we will be looking for it.
-
-After that, we must set some options to pass with the file, mainly so we can identify the file easily among the other files we pinned in our Pinata account. The `name` and `keyvalues` of the `pinataMetadata` property can be anything you want, the `name` property is going to be displayed in the pin manager of the Pinata website.
-
-Next, we can pin the picture to the IPFS. We use the `pinFileToIPFS` method of the Pinata SDK and pass as arguments the readable stream we created earlier and the options. This returns a promise that resolves with an object containing 2 properties we verify to make sure the pinning was successful: the `IpfsHash` property holds the IPFS hash of the file we’ve just pinned and the `PinSize` property holds the size of the file. If these 2 properties are defined and not equal to zero, we can assume the file was correctly pinned.
-
-Now, we can create the metadata for the NFT and pin it to the IPFS:
-
-![](/images/nft-pinata/image41.png)
-
-First, we are going to remove the user’s image from the server. Whether you are using a service on a free tier with a limited storage or you have your own server, you don’t want to keep the images the users sent on your server. To remove it, you can use the `unlinkSync` method of the `fs` package and pass to it the path to the file.
-
-The metadata must follow a certain structure to help the dapps in the Tezos ecosystem read their properties correctly. Here are a few of the properties you can set:
-
-* `name` => the name of the NFT
-* `description` => a description of the NFT
-* `symbol` => the symbol will appear in wallets to represent your NFT, choose it wisely
-* `artifactUri` => the link to the asset formatted as `ipfs://` + the IPFS hash
-* `displayUri` => the link to the picture formatted as `ipfs://` + the IPFS hash
-* `creators` => a list of the creators of the NFT
-* `decimals` => decimals are always set to `0` for NFTs
-* `thumbnailUri` => the thumbnail to display for the NFT \(for example, in wallets\)
-* `is_transferable` => whether the NFT can be transferred or not
-* `shouldPreferSymbol` => allows wallets to decide whether or not a symbol should be displayed in place of a name
-
-Once we created the object that will become the metadata of the NFT, we can pin it to the IPFS. The Pinata SDK offers a `pinJSONToIPFS` method to do what it says, pin JSON to the IPFS 😅 You can pass to it your JavaScript object directly \(I assume the SDK converts it into JSON because passing a JSON string throws an error\) and just like with the picture, you can set some metadata for the metadata! Once the promise resolves, we check if we got the IPFS hash back and that the data size is over 0. Now everything is pinned! We can send a simple response and attach the CID for the metadata and for the picture:
-
-``` sh
-res.status(200).json({
- status: true,
- msg: {
- imageHash: pinnedFile.IpfsHash,
- metadataHash: pinnedMetadata.IpfsHash
- }
-});
-```
-
-The two hashes will confirm on the frontend side that the picture and the metadata have been correctly pinned.
-
-#### The frontend
-
-The app we will build for the frontend has the typical structure of a Tezos app so we will only focus on the functions required to get the picture and the metadata from the user and send them to the backend before minting the NFT and to display the NFTs the user may own. If you are interested in learning how to build a Tezos app, you can follow [this tutorial](https://medium.com/ecad-labs-inc/how-to-build-your-first-tezos-dapp-2021-edition-b1263b4ba016) to learn everything you need to know!
-
-_1- Displaying the NFTs_
-
-As explained earlier, the NFTs are just token ids stored in the contract. In order to find the NFTs owned by the users connected to the dapp, we just have to find the token ids associated with their addresses. The contract for this tutorial implements a convenient **reverse ledger** that allows you to fetch all the token ids associated with an address in a single call.
-
-> Note: a reverse ledger is not a standard feature of NFT contracts and it may be absent from other platforms. If that’s the case, they may implement other ways of tracking token ids owned by a wallet address, for example, an external ledger file.
-
-Let’s start by installing [Taquito](https://tezostaquito.io/) and creating a new instance of the Tezos toolkit:
-
-![](/images/nft-pinata/image8.png)
-
-Now, we can fetch the storage of the contract:
-
-![](/images/nft-pinata/image10.png)
-
-`await Tezos.wallet.at(contractAddress)` creates an instance of the contract with different useful methods to interact with the contract or get details about, like the storage, that you can get using `await contract.storage()`. After that, we have access to the whole storage.
-
-Now, we can look for the token ids owned by the user by searching the `reverse_ledger` bigmap with the `get` function:
-
-![](/images/nft-pinata/image19.png)
-
-`getTokenIds` is an array containing all the ids owned by the `address`. We can simply loop through the array to get each id and look for the id in the `ledger` bigmap:
-
-![](/images/nft-pinata/image37.png)
-
-The id is returned by Taquito as a `BigNumber`, so you have to call `.toNumber()` first before being able to use it. Once we have the id, we can look for its metadata in the `token_metadata` bigmap. The value returned is a Michelson map and the metadata path is going to be stored at the empty key. Because the path is stored as bytes, we use `bytes2Char()` provided by the `@taquito/utils` package to convert the returned `bytes` into a `string`. To finish, we return an object with 2 properties: the token id and the IPFS hash of the metadata.
-
-> Note: although the standard requires us to store the IPFS hash in the following manner => `ipfs://IPFS_HASH`, there is no safeguard and any kind of data can be stored there, this is why we make a simple check with `tokenInfo.slice(0, 7) === “ipfs://”` using the ternary operator to verify that at least this condition is fulfilled.
-
-_2- Sending the picture and metadata to the backend_
-
-First, we set up the HTML tags we need to get the picture, the name of the picture, and its description:
-
-![](/images/nft-pinata/image23.png)
-
-The `bind` attribute in Svelte makes it very easy to store the input in a variable that we can use later when we want to pin the NFT to the IPFS. A click on the `upload` button will trigger the upload of the picture, its title, and description to the server.
-
-Now, let’s see how uploading the user data works!
-
-![](/images/nft-pinata/image26.png)
-
-We define 2 boolean variables called `pinningMetadata` and `mintingToken` that we will update according to the result of the different steps of the upload to give some visual feedback to the users in the UI. Because we are not using a traditional form, we must build the form data manually. After instantiating a new `FormData`, we use the `append` method to add the different details of the form, the picture, the title, the description, and the creator of the NFT.
-
-Once the form is ready, we can use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make a POST request to the `/mint` endpoint of our server app. The request should include the required headers and the form in the `body`. The response from the server will include the hash for the picture and the hash for the metadata:
-
-![](/images/nft-pinata/image33.png)
-
-When the `response` comes, we can convert it to a usable JS object with the `json` method. We check that the `status` property is `200` and that the `metadataHash` and `imageHash` properties exist. If that’s the case, we can switch the UI from “pinning” to “minting” and send the transaction to the blockchain to save the NFT metadata:
-
-![](/images/nft-pinata/image15.png)
-
-This is a regular contract call. You create an instance of the contract by calling `Tezos.wallet.at(contractAddress)`, then you call the `mint` entrypoint in the `contract.methods` property. Because the entrypoint expects bytes, we have to convert the IPFS hash into bytes without forgetting to prefix `ipfs://` to make it valid. We pass the `userAddress` at the same time to identify the owner of the NFT in the contract. After the NFT is minted and the minting is confirmed, we save the data of the NFT into `newNft` to have it displayed in the interface, we reset the files, title, and description variables to give the opportunity to the user to mint another NFT and we refresh the list of NFTs owned by the user by querying them \(this is not absolutely necessary but getting up-to-date data from the contract never hurts\).
-
-Now, the NFT has been successfully minted, its metadata is pinned on the IPFS and it is available to the world 🥳
-
-### Suggested improvements
-
-The purpose of this tutorial is to build a simple NFT platform and introduce some concepts related to creating and minting NFTs, in general, and specifically on the Tezos blockchain. Here are a few additional features and design considerations you would like to take into account for a fully-featured NFT app:
-
-* Generate the IPFS hashes client-side first before pinning them: a failed transaction and other worst-case scenarios may leave unused content pinned into your Pinata account, to avoid this, you can spin up an IPFS node in the client browser, pin the data, mint the NFT and then pin it to your Pinata account
-* Add a `burn` endpoint: right now, your users can only create tokens, but you could also allow them to delete their NFTs
-* Display other NFTs of the platform in the front-end interface
-* Add a fee to mint new NFTs: when sending a call to the mint entrypoint, add `.send({ amount: fee })` to monetize your service.
-
-If you want to get your hands dirty, you can also improve the contract. You can add a marketplace to the contract where NFT creators can sell their artwork, you can implement royalties every time an NFT is sold, you can track the sales and their amount and create a “reputation” system for the artists, etc., the possibilities are endless!
-
-### Conclusion
-
-This tutorial introduced a lot of information about NFTs. You learned about the 3 different parts that make up an NFT platform: the contract that records the NFT ids and a link to their associated metadata, the backend that securely builds the metadata and pins it to the IPFS, and the frontend that collects the picture and the related information from the user before minting the NFT. These 3 elements work in concert to receive the user’s input, process it, format it, save it on the IPFS, and record it on the Tezos blockchain.
-
-These 3 parts of the minting and pinning process require 3 tools that are the cornerstones of building NFT platforms on Tezos: a smart contract language like [Ligo](https://ligolang.org/) to write the smart contract, an IPFS pinning service like [Pinata](https://pinata.cloud/) to easily save data to the IPFS, and a JavaScript library like [Taquito](https://tezostaquito.io/) to let the users interact with the smart contract. This is everything you need to build yourself the next Hic et Nunc!
-
diff --git a/src/pages/tutorials/create-an-nft/nft-taquito/index.md b/src/pages/tutorials/create-an-nft/nft-taquito/index.md
new file mode 100644
index 000000000..b2f2e841d
--- /dev/null
+++ b/src/pages/tutorials/create-an-nft/nft-taquito/index.md
@@ -0,0 +1,704 @@
+---
+id: nft-taquito
+title: Create a web app that mints NFTs
+authors: 'Sol Lederer, Tim McMackin'
+lastUpdated: 20th September 2023
+---
+
+This tutorial covers how to set up a decentralized web application (dApp) that allows users to create NFTs on Tezos.
+No prior knowledge of NFTs or Tezos is required, but because the tutorial application uses TypeScript, some familiarity with JavaScript or TypeScript makes it easier to understand.
+
+In this tutorial, you will learn:
+
+- What NFTs are
+- How to set up distributed storage for NFT metadata
+- How to deploy (originate) a smart contract to Tezos
+- How to use the [Taquito](https://tezostaquito.io/) JavaScript/TypeScript SDK to access Tezos and user wallets and to send transactions to Tezos
+
+## What is a non-fungible token (NFT)?
+
+An NFT is a special type of blockchain token that represents something unique.
+Fungible tokens such as XTZ and real-world currencies like dollars and euros are interchangeable; each one is the same as every other.
+By contrast, each NFT is unique and not interchangeable.
+NFTs can represent ownership over digital or physical assets like virtual collectibles or unique artwork, or anything that you want them to represent.
+
+Like other types of Tezos tokens, a collection of NFTs is managed by a smart contract.
+The smart contract defines what information is in each token and how the tokens behave, such as what happens when a user transfers an NFT to another user.
+It also keeps a ledger that records which account owns each NFT.
+
+In this tutorial, you create NFTs that comply with the FA2 standard (formally known as the [TZIP-12](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) standard), the current standard for tokens on Tezos.
+The FA2 standard creates a framework for how tokens behave on Tezos, including fungible, non-fungible, and other types of tokens.
+It provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata.
+
+## Tutorial application
+
+The application that you set up in this tutorial has three parts:
+
+- The **smart contract** runs on the Tezos blockchain to manage the NFTs, including creating, transferring, and destroying them
+- The **backend application** runs on a web server to upload the NFT data to the internet
+- The **frontend application** runs on a web server and allows the user to connect their wallet, enter the information for the NFT, and send a request to the smart contract to create the NFT
+
+This diagram shows what happens when the user creates an NFT with the application.
+The rest of this tutorial covers these steps in detail:
+
+![Flow diagram of the tutorial application and interaction between the user and the parts of the application](/images/nft-create/taquito-application-flow.png)
+
+The frontend application looks like this, with fields for the image to represent the NFT and for its metadata:
+
+![The home page of the frontend application](/images/nft-create/taquito-application-home.png)
+
+## Prerequisites
+
+To run this tutorial you need Node.JS and NPM installed.
+See .
+You can verify that they are installed by running these commands:
+
+ ```bash
+ node --version
+ npm --version
+ ```
+
+## Configure IPFS storage
+
+NFTs have metadata, usually including at least a title and description.
+Optionally, the NFT can include many other metadata fields, such as links to media and attributes in many different formats.
+
+Because storage space on blockchains is expensive, developers don't put entire token metadata files on Tezos.
+Instead, they configure decentralized storage for the NFT data and put only the link to that data on Tezos itself.
+In this section, you set up storage for the NFT metadata using the InterPlanetary File System (IPFS) protocol.
+
+IPFS requires authentication just like blockchain transactions, so in this section you set up an account with the Pinata IPFS provider and use it to upload (or _pin_) the NFT data to IPFS.
+
+1. Create a free Pinata account at .
+
+1. Go to the API Keys tab and click **New Key**.
+
+1. On the Create New API Key page, expand **API Endpoint Access > Pinning** and enable the `pinFileToIPFS` and `pinJSONToIPFS` permissions, as in this picture:
+
+ ![Selecting the permissions for the Pinata key](/images/nft-create/pinata-key-permissions.png)
+
+1. In the **Key Name** field, give the key a name, such as "My Key."
+
+1. Click **Create Key**.
+
+ The API Key Info window shows the API key and secret, which you must copy immediately, because they are not shown again.
+
+1. Copy the API Key and API Secret fields and save the values on your computer.
+You need these values in the next section.
+
+ You can see the new API key on the API Keys tab:
+
+ ![The new Pinata API key in the Pinata web app](/images/nft-create/created-pinata-key.png)
+
+Now your applications can use your Pinata account to pin NFT data to IPFS.
+
+## Download the tutorial files
+
+The tutorial application has three parts:
+
+- The smart contract that manages the NFTs
+- The backend application that handles uploading data to IPFS
+- The frontend application that connects to the user's wallet, sends the data to the backend application, and sends the transactions to the smart contract to mint the NFTs
+
+The tutorial application files are in this GiHub repository: .
+
+If you have the `git` program installed, you can clone the repository with this command:
+
+```bash
+git clone https://github.com/trilitech/tutorial-applications.git
+```
+
+If you don't have git installed, go to and click "Code > Download ZIP" and extra the ZIP file on your computer.
+
+Then, go to the application in the `nft-taquito` folder.
+
+## The tutorial contract
+
+The file `contract/NFTS_contract.mligo` contains the code for the smart contract that manages the NFTs.
+This contract is written in the CameLIGO version of the LIGO smart contract programming language, with a syntax similar to OCaml.
+This contract is already written for you, so do not need any experience with these languages to run the tutorial.
+
+This contract creates NFTs that comply with the FA2 standard (formally known as the [TZIP-12](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) standard), the current standard for tokens on Tezos.
+The FA2 standard creates a framework for how tokens behave on Tezos, including fungible, non-fungible, and other types of tokens.
+It provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata.
+
+The full details of the smart contract are beyond the scope of this tutorial, but the major parts of the contract have descriptions in comments.
+For more examples of smart contracts, see [oxheadalpha/smart-contracts](https://github.com/oxheadalpha/smart-contracts) on GitHub.
+
+### Contract entrypoints
+
+Like APIs, smart contracts have _entrypoints_, which are commands that transaction senders (client applications such as your web app or other contracts) can call.
+To comply with the TZIP-12 standard, the smart contract must have these entrypoints:
+
+- `transfer`: Transfers tokens from one account to another
+- `balance_of`: Provides information about the tokens that an account owns
+- `update_operators`: Changes the accounts that can transfer tokens
+
+This contract includes these additional entrypoints:
+
+- `mint`: Creates NFTs
+- `burn`: Destroys NFTs
+
+### Contract types
+
+Because Tezos uses strongly-typed languages, this contract's code starts by defining the types that the contract uses.
+These types are important for verifying that data is in the correct format, such as the parameters the transaction senders pass.
+
+For example, the `transfer` entrypoint accepts a list of the `transfer` type.
+This type includes the account to transfer tokens from and a list of the `transfer_destination` type, which includes the account to transfer tokens to, the ID of the token to transfer, and the amount to transfer:
+
+```ocaml
+type token_id = nat
+
+type transfer_destination =
+[@layout:comb]
+{
+ to_ : address;
+ token_id : token_id;
+ amount : nat;
+}
+
+type transfer =
+[@layout:comb]
+{
+ from_ : address;
+ txs : transfer_destination list;
+}
+```
+
+The type `fa2_entry_points` is a special type that the contract's `main` function uses to define the entrypoints.
+It maps the entry points to the type of parameter that they accept:
+
+```ocaml
+type fa2_entry_points =
+ | Transfer of transfer list
+ | Balance_of of balance_of_param
+ | Update_operators of update_operator list
+ | Mint of mint_params
+ | Burn of token_id
+```
+
+### Error messages
+
+The contract defines a series of error messages, and comments in the code describe what each error message means.
+For example, the `balance_of` and `transfer` entrypoints return this error if the client requests information about a token that does not exist or tries to transfer a token that does not exist:
+
+```ocaml
+(** One of the specified `token_id`s is not defined within the FA2 contract *)
+let fa2_token_undefined = "FA2_TOKEN_UNDEFINED"
+```
+
+### Internal functions
+
+The contract has many internal functions, such as this function, which gets the specified account's balance of tokens.
+In the case of NFTs, only one of each token exists, so the function returns a balance of 1 if the account owns the token and 0 if it does not.
+
+```ocaml
+(**
+Retrieve the balances for the specified tokens and owners
+@return callback operation
+*)
+let get_balance (p, ledger : balance_of_param * ledger) : operation =
+ let to_balance = fun (r : balance_of_request) ->
+ let owner = Big_map.find_opt r.token_id ledger in
+ match owner with
+ | None -> (failwith fa2_token_undefined : balance_of_response)
+ | Some o ->
+ let bal = if o = r.owner then 1n else 0n in
+ { request = r; balance = bal; }
+ in
+ let responses = List.map to_balance p.requests in
+ Tezos.transaction responses 0mutez p.callback
+```
+
+### Main function
+
+The `main` function is a special function that defines the entrypoints in the contract.
+In this case, it accepts the entrypoint that the transaction sender called and the current state of the contract's storage.
+Then the function branches based on the entrypoint.
+
+For example, if the sender calls the `balance_of` entrypoint, the function calls the `get_balance` function and passes the parameters that the sender passed and the current state of the contract's ledger:
+
+```ocaml
+ | Balance_of p ->
+ let op = get_balance (p, storage.ledger) in
+ [op], storage
+```
+
+Here is the complete code of the `main` function:
+
+```ocaml
+let main (param, storage : fa2_entry_points * nft_token_storage)
+ : (operation list) * nft_token_storage =
+ match param with
+ | Transfer txs ->
+ let (new_ledger, new_reverse_ledger) = transfer
+ (txs, default_operator_validator, storage.operators, storage.ledger, storage.reverse_ledger) in
+ let new_storage = { storage with ledger = new_ledger; reverse_ledger = new_reverse_ledger } in
+ ([] : operation list), new_storage
+
+ | Balance_of p ->
+ let op = get_balance (p, storage.ledger) in
+ [op], storage
+
+ | Update_operators updates ->
+ let new_ops = fa2_update_operators (updates, storage.operators) in
+ let new_storage = { storage with operators = new_ops; } in
+ ([] : operation list), new_storage
+
+ | Mint p ->
+ ([]: operation list), mint (p, storage)
+
+ | Burn p ->
+ ([]: operation list), burn (p, storage)
+```
+
+### Initial storage state
+
+An FA2 contract usually contains these properties in its storage:
+
+- A table named `ledger` that records the token IDs and the address of the current owner
+- A table named `metadata` that records the metadata associated with the contract itself, such as its name, creator, and what standards it meets
+- A table named `token_metadata` that records the metadata associated with every token stored in the contract
+
+In this case, the contract uses the CameLIGO `bigmap` type for these tables.
+
+This contract's storage includes other properties, including the list of operators and the next token ID.
+
+When you originate (deploy) the contract to Tezos, you must set the initial state of its storage.
+For this contract, the initial storage state is in the comment at the end of the file.
+
+## Originate (deploy) the smart contract to the testnet
+
+There are many ways to originate a contract on Tezos.
+For a tutorial on using the command line, see [Deploy a smart contract](../../deploy-your-first-smart-contract/).
+
+Before you originate your contract to the main Tezos network (referred to as *mainnet*), you can originate it to a testnet.
+Testnets are useful for testing Tezos operations because testnets provide tokens for free so you can work with Tezos without spending real tokens.
+
+This tutorial uses the online LIGO IDE at because you don't have to install any tools to use it.
+
+Follow these steps to originate the smart contract to Tezos:
+
+1. In a web browser, open the IDE at .
+
+1. At the top right, click the **Network** drop-down list and next to **Tezos**, select the **Ghostnet** testnet.
+The network changes to the Ghostnet testnet, as in this picture:
+
+ ![The IDE menu, showing the Ghostnet testnet selected](/images/nft-create/web-ligo-ide-ghostnet.png)
+
+1. Create an account to use to originate the contract:
+
+ 1. At the top right, click the **Keypair Manager** button.
+ The "Keypair Manager" window opens.
+
+ 1. Click **Create**.
+
+ 1. Give the keypair a name such as "My keys" and click **Create**.
+
+ 1. From the "Keypair Manager" window, copy the address of the new account, which begins with `kt1`.
+ You will need this address later.
+
+ 1. Click **Close**.
+
+1. Send funds to the account from the testnet faucet:
+
+ 1. Go to the Ghostnet faucet at .
+
+ 1. Put the new account address in the **Or fund any address** field.
+
+ 1. Click the button to request 100 tokens and wait for the browser to complete the operation.
+
+ 1. When you see a message that the tokens are sent to your address, go back to the web IDE, open the "Keypair Manager" window and verify that the account has tokens, as in this example:
+
+ ![The IDE Keypair Manager window, showing an account with funds](/images/nft-create/web-ligo-ide-account.png)
+
+1. In the IDE, Click the **New** button.
+
+1. In the "Create a New Project" window, give your project a name, such as "NFT tutorial," select "Empty Project" in the **Template** field, and select "CameLIGO" in the **Syntax** field.
+
+1. Click **Create Project**.
+The IDE opens a blank contract file and shows commands for the file on the left-hand side of the window.
+
+1. Paste the contents of the `contract/NFTS_contract.mligo` file into the editor.
+The IDE saves the file automatically.
+
+1. Click **Compile** and then in the "Compile" window, click **Compile**.
+The IDE compiles the contract code to Michelson, the base language that all Tezos contracts use.
+At the bottom of the window, it prints the message `wrote output to .workspaces/NFT tutorial/build/contracts/Contract.tz`.
+If you see an error, make sure that you copied the entire contract file.
+
+1. Originate the contract:
+
+ 1. Click **Deploy**.
+
+ 1. In the "Deploy contract" window, in the **Init storage** field, paste the initial storage value for the contract, which you can get from the comment at the end of the contract:
+
+ ```ocaml
+ {
+ ledger = (Big_map.empty: (token_id, address) big_map);
+ operators = (Big_map.empty: ((address * (address * token_id)), unit) big_map);
+ reverse_ledger = (Big_map.empty: (address, token_id list) big_map);
+ metadata = Big_map.literal [
+ ("", Bytes.pack("tezos-storage:contents"));
+ ("contents", ("7b2276657273696f6e223a2276312e302e30222c226e616d65223a2254555473222c22617574686f7273223a5b2240636c617564656261726465225d2c22696e7465726661636573223a5b22545a49502d303132222c22545a49502d303136225d7d": bytes))
+ ];
+ token_metadata = (Big_map.empty: (token_id, token_metadata) big_map);
+ next_token_id = 0n;
+ admin = ("tz1Me1MGhK7taay748h4gPnX2cXvbgL6xsYL": address);
+ }
+ ```
+
+ 1. In the **Signer** field, make sure your new account is selected.
+
+ 1. Leave the other fields blank and click **Estimate**.
+ The IDE calculates the fees for the deployment.
+
+ 1. Click **Deploy** and leave the window open to wait for the contract to be originated.
+
+ Originating the contract can take a few minutes.
+ When the transaction completes, the window shows a message that the contract was deployed.
+
+ 1. Copy the contract address, which starts with `KT1`, and then close the "Deploy contract" window.
+
+1. Verify that the contract is on the testnet by finding it on a block explorer:
+
+ 1. Open a Tezos block explorer such as [TzKT](https://tzkt.io) or [Better Call Dev](https://better-call.dev/).
+
+ 1. Set the explorer to Ghostnet instead of mainnet.
+
+ 1. Paste the contract address into the search field and press Enter.
+
+ 1. Go to the Entrypoints tab to see the entrypoints and their parameters.
+
+Now anyone can call the Tezos contract if they have tokens for the fees and send a valid request.
+
+## Run the backend application
+
+The backend application is responsible for uploading the NFT data to IPFS.
+In these steps, you configure the backend application with your Pinata information:
+
+1. In your command-line window, go to the `backend` folder of the tutorial application.
+
+1. Install its dependencies by running `npm install`.
+
+1. In the `src/PinataKeys.ts` file, replace `DUMMY_KEY` with your Pinata API key and replace `DUMMY_SECRET` with your Pinata secret.
+
+1. Open the `src/index.ts` file.
+
+ The `corsOptions` constant in this file contains the location of the frontend application.
+ It includes the default location of the frontend application (`http://localhost:5173`) and an example custom URL.
+
+ ```typescript
+ const corsOptions = {
+ origin: ["http://localhost:5173", "https://my-cool-nft-app.com"],
+ optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
+ };
+ ```
+
+1. If you intend to host the frontend application anywhere other than on your computer, add its URL to the `corsOptions` constant.
+
+ If you are not going to change where the frontend application is hosted, you can leave this code unchanged.
+
+1. Review the code for the `POST /mint` endpoint.
+
+ This endpoint is the only endpoint that the frontend application uses.
+ First, it accesses the image file that the user uploads and uses the Pinata `pinFileToIFPS` method to pin it to IPFS with a name and description:
+
+ ```typescript
+ const readableStreamForFile = fs.createReadStream(`./uploads/${fileName}`);
+ const options: any = {
+ pinataMetadata: {
+ name: req.body.title.replace(/\s/g, "-"),
+ keyvalues: {
+ description: req.body.description
+ }
+ }
+ };
+ const pinnedFile = await pinata.pinFileToIPFS(
+ readableStreamForFile,
+ options
+ );
+ ```
+
+ The app must pin the picture first because the token metadata includes a hash of the picture.
+
+ Then, it creates a metadata object with information about the NFT, including its name and description and the IPFS URI of the image and pins that metadata object to IPFS:
+
+ ```typescript
+ if (pinnedFile.IpfsHash && pinnedFile.PinSize > 0) {
+ // remove file from server
+ fs.unlinkSync(`./uploads/${fileName}`);
+ // pins metadata
+ const metadata = {
+ name: req.body.title,
+ description: req.body.description,
+ symbol: "TUT",
+ artifactUri: `ipfs://${pinnedFile.IpfsHash}`,
+ displayUri: `ipfs://${pinnedFile.IpfsHash}`,
+ creators: [req.body.creator],
+ decimals: 0,
+ thumbnailUri: "https://tezostaquito.io/img/favicon.png",
+ is_transferable: true,
+ shouldPreferSymbol: false
+ };
+
+ const pinnedMetadata = await pinata.pinJSONToIPFS(metadata, {
+ pinataMetadata: {
+ name: "TUT-metadata"
+ }
+ });
+ // ...
+ }
+ ```
+
+ This metadata object includes several standard fields for Tezos tokens, including a name, description, symbol to show in wallets, and URIs to preview pictures and thumbnails.
+ The `decimals` field is set to 0 because the NFT cannot be divided into decimal amounts like fungible tokens can.
+
+ If both of the pins were successful, the endpoint returns the IPFS URIs of the image and metadata to the frontend application:
+
+ ```typescript
+ if (pinnedMetadata.IpfsHash && pinnedMetadata.PinSize > 0) {
+ res.status(200).json({
+ status: true,
+ msg: {
+ imageHash: pinnedFile.IpfsHash,
+ metadataHash: pinnedMetadata.IpfsHash
+ }
+ });
+ } else {
+ res
+ .status(500)
+ .json({ status: false, msg: "metadata were not pinned" });
+ }
+ ```
+
+1. Start the backend application by running `npm run dev`.
+
+## Run the frontend application
+
+The front application accepts information about a new NFT from the user, sends the image and metadata to the backend, and calls the smart contract to mint the NFT.
+Follow these steps to configure and start the frontend application:
+
+1. In your command-line window, go to the `frontend` folder of the tutorial application.
+
+1. Install its dependencies by running `npm install`.
+
+1. Open the `src/App.svelte` file.
+
+1. Set the `contractAddress` constant to the address of the originated smart contract, which starts with `KT1`:
+
+ ```typescript
+ const contractAddress = "KT1XdU2tK5hoDhtToP4kSSR9HiCkie4mZqFp";
+ ```
+
+1. Review the code for the `onMount` function:
+
+ This function runs when the page loads.
+ It starts by creating an instance of the Taquito `TezosToolkit` object, which provides access to Tezos.
+ It also creates an object to unpack data from the map properties in the contract's storage:
+
+ ```typescript
+ Tezos = new TezosToolkit(rpcUrl);
+ Tezos.setPackerProvider(new MichelCodecPacker());
+ ```
+
+ It creates an instance of the Beacon `BeaconWallet` object to prepare to connect to the user's wallet, but it does not connect to the user's wallet immediately.
+ It could try to connect to the user's wallet immediately, but it's better programming practice to wait and let the user click a button to connect their wallet after the page loads.
+
+ ```typescript
+ wallet = new BeaconWallet(walletOptions);
+ ```
+
+ The application can remember if it has a connection to a wallet, so if the user has connected their wallet before, it connects to that wallet automatically:
+
+ ```typescript
+ if (await wallet.client.getActiveAccount()) {
+ userAddress = await wallet.getPKH();
+ Tezos.setWalletProvider(wallet);
+ await getUserNfts(userAddress);
+ }
+ ```
+
+1. Review the code for the `connect` function.
+
+ First, this function uses the [Beacon wallet SDK](https://docs.walletbeacon.io/) to prompt the user to connect their wallet if it is not already connected:
+
+ ```typescript
+ if (!wallet) {
+ wallet = new BeaconWallet(walletOptions);
+ }
+ ```
+
+ Then, it requests permission to swap to the Ghostnet testnet:
+
+ ```typescript
+ await wallet.requestPermissions({
+ network: {
+ type: NetworkType.GHOSTNET,
+ rpcUrl
+ }
+ });
+ ```
+
+ Finally, it retrieves the user's current NFTs with the `getUserNfts` function:
+
+ ```typescript
+ await getUserNfts(userAddress);
+ ```
+
+1. Review the code for the `getUserNfts` function:
+
+ This function receives the user's account address.
+ Then, it retrieves the current storage for the smart contract, including information about which account owns each NFT:
+
+ ```typescript
+ const getUserNfts = async (address: string) => {
+ // finds user's NFTs
+ const contract = await Tezos.wallet.at(contractAddress);
+ nftStorage = await contract.storage();
+ const getTokenIds = await nftStorage.reverse_ledger.get(address);
+ // ...
+ }
+ ```
+
+ The `reverse_ledger` property in the contract storage keeps a list of who owns each NFT.
+ However, this property is not a standard feature of NFT smart contracts.
+ According to the standard, the contract must have a `ledger` property that maps the ID of each token to the address that owns it.
+ The `reverse_ledger` property indexes this information in the opposite way, so the code can filter the list of tokens according to a given owner's address.
+ This property is for the convenience of apps accessing the storage and may not be available on other NFT contracts.
+
+ Now that it has the list of IDs of NFTs that the account owns, it retrieves the metadata for each token from the contract storage:
+
+ ```typescript
+ if (getTokenIds) {
+ userNfts = await Promise.all([
+ ...getTokenIds.map(async id => {
+ const tokenId = id.toNumber();
+ const metadata = await nftStorage.token_metadata.get(tokenId);
+ const tokenInfoBytes = metadata.token_info.get("");
+ const tokenInfo = bytes2Char(tokenInfoBytes);
+ return {
+ tokenId,
+ ipfsHash:
+ tokenInfo.slice(0, 7) === "ipfs://" ? tokenInfo.slice(7) : null
+ };
+ })
+ ]);
+ }
+ ```
+
+ The frontend uses this `userNfts` variable to show the user's NFTs on the page.
+
+1. Review the `upload` function:
+
+ This function starts by creating an HTML form data object with the image, title, and description for the NFT and the address of the creator, who will own the new NFT.
+ It sends this data to the backend's `POST /mint` endpoint:
+
+ ```typescript
+ const data = new FormData();
+ data.append("image", files[0]);
+ data.append("title", title);
+ data.append("description", description);
+ data.append("creator", userAddress);
+
+ const response = await fetch(`${serverUrl}/mint`, {
+ method: "POST",
+ headers: {
+ "Access-Control-Allow-Origin": "*"
+ },
+ body: data
+ });
+ ```
+
+ It receives the response from the backend with the URIs for the NFT metadata.
+ Then, it creates a Taquito `ContractAbstraction` object to represent the smart contract and provide access to the contract's methods and storage:
+
+ ```typescript
+ const contract = await Tezos.wallet.at(contractAddress);
+ ```
+
+ It uses this object to call the contract's `mint` entrypoint and pass the metadata and wallet address:
+
+ ```typescript
+ const contract = await Tezos.wallet.at(contractAddress);
+ const op = await contract.methods
+ .mint(char2Bytes("ipfs://" + data.msg.metadataHash), userAddress)
+ .send();
+ console.log("Op hash:", op.opHash);
+ await op.confirmation();
+ ```
+
+ Finally, it refreshes the list of the user's NFTs with the `getUserNfts` function:
+
+ ```typescript
+ await getUserNfts(userAddress);
+ ```
+
+1. Start the frontend application by running `npm run dev`.
+
+## Testing the application
+
+To test the application, you must have a Tezos wallet and a small amount of XTZ tokens to pay the transaction fees.
+
+1. Install any wallet that is compatible with Tezos, such as [Temple wallet](https://templewallet.com/) and switch to the Ghostnet network.
+
+1. Send funds to the wallet from the testnet faucet:
+
+ 1. Go to the Ghostnet faucet at .
+
+ 1. Put the wallet account address in the **Or fund any address** field.
+
+ 1. Click the button to request 100 tokens and wait for the browser to complete the operation.
+
+ 1. When you see a message that the tokens are sent to your address, open your wallet and verify that the tokens are there.
+ It can take a few minutes for them to appear.
+
+1. When the frontend application starts, open the web browser to .
+
+1. Click **Connect your wallet** and approve the connection in your wallet.
+
+1. Click **Choose file** and select any image for the NFT.
+
+1. Add a title and description for the new NFT in the **Title** and **Description** fields.
+
+1. Click **Mint NFT**.
+
+1. Confirm the transaction in your wallet, which requires a small amount of XTZ for the transaction fees.
+
+1. Wait for the transaction to complete.
+When it completes, the screen changes to a success message with links to the NFT information, as in this picture:
+
+ ![The success message, with links to the NFT information](/images/nft-create/taquito-application-created-nft.png)
+
+ In the "Your NFTs" list, you can click on a number (starting at 0) to see the information that is in the NFT, which includes the name and description that you added and links to data on IPFS.
+ The page also shows a link to a block explorer where you can see technical information about the minting transaction.
+
+To see information about the smart contract and NFT collection, go to a block explorer such as , set it to Ghostnet, and enter the address of the smart contract.
+The explorer shows information such a list of the tokens in the collection and who owns them.
+You can also see the current state of the storage for the contract.
+
+## Summary
+
+In this tutorial, you learned how to create an application that mints applications on Tezos on behalf of users.
+The parts are independent, so you could code a different frontend application to call the smart contract or use a different backend application to upload the NFT metadata.
+
+If you want to continue working with this application, try implementing the `burn` entrypoint in the frontend to allow users to destroy their NFTs.
+The contract has a `burn` entrypoint, but the frontend does not provide a way to call it.
+
+You can try adding your own entrypoints and originating a new contract, but you cannot update the existing contract after it is originated.
+
+You can add a fee to mint NFTs by sending funds along with the mint transaction.
+If you add the fee as in the following code, the transaction takes the fee amount along with the transaction fee from the user's wallet.
+Currently, the contract does not have a way to send this fee to any other account, so the fee is locked in the contract forever.
+You can add an entrypoint to the contract that allows authorized users to withdraw the fee later.
+
+```typescript
+const op = await contract.methods
+ .mint(char2Bytes("ipfs://" + data.msg.metadataHash), userAddress)
+ .send({ amount: 1 });
+```
+
+You can also optimize the application by generating the IPFS hashes on the client side before pinning them.
+If the mint transaction fails, it may leave unused metadata in your IPFS account.
+To avoid this problem, you can start an IPFS node in the browser, pin the metadata there, mint the NFT, and pin the metadata to your Pinata account only if the mint transaction succeeds.
diff --git a/src/pages/tutorials/create-an-nft/nft-tznft/index.md b/src/pages/tutorials/create-an-nft/nft-tznft/index.md
new file mode 100644
index 000000000..82e5d172a
--- /dev/null
+++ b/src/pages/tutorials/create-an-nft/nft-tznft/index.md
@@ -0,0 +1,523 @@
+---
+id: nft-tznft
+title: Create an NFT with the `tznft` tool
+authors: 'Sol Lederer, Tim McMackin'
+lastUpdated: 18th September 2023
+---
+
+This tutorial covers how to create a collection of NFTs on Tezos and manipulate them using the `tznft` command-line tool.
+No prior knowledge of NFTs or Tezos is required, but you need basic experience with your computer's command-line terminal to paste commands and run them.
+
+In this tutorial, you will learn:
+
+- What NFTs are
+- How to install and start a local Tezos sandbox environment
+- How to create metadata files to describe NFT collections and individual NFTs
+- How to create (or _mint_) the NFTs in the sandbox
+- How to transfer NFTs and change operator permissions for them
+- How to mint NFTs to a testnet
+
+## What is a non-fungible token (NFT)?
+
+An NFT is a special type of blockchain token that represents something unique.
+Fungible tokens such as XTZ and real-world currencies like dollars and euros are interchangeable; each one is the same as every other.
+By contrast, each NFT is unique and not interchangeable.
+NFTs can represent ownership over digital or physical assets like virtual collectibles or unique artwork, or anything that you want them to represent.
+
+Like other types of Tezos tokens, a collection of NFTs is managed by a smart contract.
+The smart contract defines what information is in each token and how the tokens behave, such as what happens when a user transfers an NFT to another user.
+It also keeps a ledger that records which account owns each NFT.
+
+In this tutorial, you create NFTs that comply with the FA2 standard (formally known as the [TZIP-12](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) standard), the current standard for tokens on Tezos.
+The FA2 standard creates a framework for how tokens behave on Tezos, including fungible, non-fungible, and other types of tokens.
+It provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata.
+
+## Prerequisites
+
+To run this tutorial you need Node.JS, NPM, and Docker Desktop to install and use the `tznft` CLI tool, which helps you create and test NFT collections on Tezos.
+
+- Install Node.JS version 18 (not 20) and NPM.
+See .
+You can verify that they are installed by running these commands:
+
+ ```bash
+ node --version
+ npm --version
+ ```
+
+ If you see a message with the versions of Node.JS and NPM, they are installed correctly.
+
+- To install Docker Desktop, see .
+Make sure to start Docker Desktop after you install it.
+
+- To install the `tznft` tool, run this command:
+
+ ```bash
+ npm install -g @oxheadalpha/tznft
+ ```
+
+ You can verify that it is installed by running this command:
+
+ ```bash
+ tznft --version
+ ```
+
+ If you see a message with the version of the `tznft` tool, it is installed correctly.
+
+## Create a project folder
+
+1. Create a folder to store your NFT configuration files:
+
+ ```bash
+ mkdir nft-tutorial
+ cd nft-tutorial
+ ```
+
+3. Create a starter NFT configuration file:
+
+ ```bash
+ tznft init
+ ```
+
+ The resulting file, named `tznft.config`, contains information about the Tezos networks that are available for you to work with, including the [Ghostnet](https://teztnets.xyz/ghostnet-about) test network and the local sandbox that you set up in the next steps.
+ The `tznft` tool requires this file, so the commands in the following steps work only from the directory that you ran `tznft init` in.
+
+4. Check that the default active network is "sandbox:"
+
+ ```bash
+ tznft show-network
+ ```
+
+ The response should show that the active network is "sandbox."
+ The sandbox is a local simulation of Tezos that you can use to test your work.
+
+5. Set up a local Tezos sandbox by running this command:
+
+ ```bash
+ tznft bootstrap
+ ```
+
+ This command can take time to run, so wait until you see the message "sandbox started."
+
+ This command uses the [Flextesa](https://tezos.gitlab.io/flextesa/) tool to create a local sandbox in a Docker container.
+ This sandbox is a local instance of Tezos that you can use to test your work before you send it to a live Tezos network.
+ The sandbox comes preconfigured with two account aliases named `bob` and `alice` that you can use to test account operations like creating and transferring NFTs.
+
+ You can verify that the sandbox is running by running the command `docker ps` and looking for a container named `flextesa-sandbox`.
+ To stop the container, run the command `tznft kill-sandbox`, but beware that stopping the container sets the sandbox back to its initial state, which removes any changes you made or contracts or tokens that you created.
+
+ Unlike the live Tezos networks, this sandbox bakes a new block every 5 seconds, by default.
+ Therefore, commands that you run on the sandbox can take a few seconds to complete.
+
+## Create NFT metadata
+
+The first step in creating NFTs is to create local metadata files that describe the collection and the individual NFTs:
+
+1. Create a collection metadata file by running this command:
+
+ ```bash
+ tznft create-collection-meta my_collection
+ ```
+
+ The new metadata file is named `my_collection.json` and has information such as the name, description, home page, and creator of the collection.
+ It also includes the interfaces that the NFTs support, including the TZIP-12 interface that was mentioned earlier.
+
+1. Optional: Edit the `my_collection.json` file to put your information in the `name`, `description`, and `authors` fields.
+
+1. Validate the collection by running this command:
+
+ ```bash
+ tznft validate-collection-meta my_collection.json
+ ```
+
+ If you did not change values in the file, this command may show warnings that the collection uses placeholder values.
+ You can continue with these placeholder values or insert your own information.
+ If there are any errors, make sure that the file is valid JSON.
+
+1. Create a metadata file for the first NFT in the collection by running this command:
+
+ ```bash
+ tznft create-nft-meta Token1 bob ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
+ ```
+
+ This command creates a metadata file named `Token1.json` with default information about the NFT.
+ It includes the minter's account address and URIs to pictures that represent the NFT.
+ In this case, the `ipfs` URI links to a picture of the Tezos logo, which you can see at this link: .
+
+1. Optional: Edit the metadata such as the name and description fields in the `Token1.json` file.
+
+1. Optional: Edit other fields in the metadata based on the FA2 standard.
+
+ For example, you can expand the `attributes` section with other attributes.
+ Each attribute must have the `name` and `value` fields and can optionally have a `type` field, as in this example:
+
+ ```json
+ "attributes": [
+ {
+ "name": "My string attribute",
+ "value": "String attribute value"
+ },
+ {
+ "name": "My integer attribute",
+ "value": "5",
+ "type": "integer"
+ },
+ {
+ "name": "My number attribute",
+ "value": "12.3",
+ "type": "number"
+ },
+ {
+ "name": "My percentage attribute",
+ "value": "19",
+ "type": "percentage"
+ }
+ ]
+ ```
+
+ By default the `artifactUri`, `displayUri`, and `thumbnailUri` fields are set to the picture that you passed in the `tznft create-nft-meta` command.
+ You can update these to different images to allow applications to show media to represent the NFT.
+ You can also add a `formats` object to provide media in different formats, such as different image, video, or audio formats:
+
+ ```json
+ "formats": [
+ {
+ "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
+ "hash": "a56017a1317b1bc900acdaf600874c00e5c048d30894f452049db6dcef6e4f0d",
+ "mimeType": "image/svg+xml"
+ },
+ {
+ "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
+ "hash": "8968db6bde43255876c464613a31fbd0416ca7d74be4c5ae86c1450418528302",
+ "mimeType": "image/png",
+ "dimensions": {
+ "value": "512x512",
+ "unit": "px"
+ }
+ },
+ {
+ "uri": "ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj",
+ "hash": "d4a93fc8d8991caa9b52c04c5ff7edf5c4bc29317a373e3a97f1398c697d6714",
+ "mimeType": "model/gltf+json"
+ }
+ ]
+ ```
+
+ For specifics about what is allowed in an NFT metadata file, see the [TZIP-21](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-21/tzip-21.md) standard.
+
+1. Validate the NFT metadata file with this command:
+
+ ```bash
+ tznft validate-nft-meta Token1.json
+ ```
+
+ If the file does not validate, verify that it is valid JSON and has only the fields listed in the [TZIP-21](https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-21/tzip-21.md) standard.
+
+1. Create at least one more metadata file for other NFTs by running commands like this example:
+
+ ```bash
+ tznft create-nft-meta Token2 bob ipfs://QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
+ ```
+
+## Configure IPFS storage
+
+Because storage space on blockchains is expensive, developers don't put entire token metadata files on Tezos.
+Instead, they configure decentralized storage for the NFT data and put only the link to that data on Tezos itself.
+In this section, you set up storage for the NFT metadata using the InterPlanetary File System (IPFS) protocol.
+
+There are many services that provide access to IPFS, but in this tutorial you use the [Pinata](https://www.pinata.cloud/) IPFS provider.
+Pinata requires authentication, so in this section you set up an account with Pinata and use it to upload (or _pin_) the NFT data to IPFS.
+
+1. Create a free Pinata account at .
+
+1. Go to the API Keys tab and click **New Key**.
+
+1. On the Create New API Key page, expand **API Endpoint Access > Pinning,** and enable the `pinFileToIPFS` permission, as in this picture:
+
+ ![Selecting the permissions for the Pinata key](/images/nft-create/pinata-key-permissions.png)
+
+1. In the **Key Name** field, give the key a name, such as "My Key."
+
+1. Click **Create Key**.
+
+ The API Key Info window shows the API key and secret, which you must copy immediately, because they are not shown again.
+
+1. Copy the API Key and API Secret fields and save the values on your computer.
+You need these values in the next section.
+
+ You can see the new API key on the API Keys tab:
+
+ ![The new Pinata API key in the Pinata web app](/images/nft-create/created-pinata-key.png)
+
+1. Add the API key and secret to your local `tznft` configuration by running this command, replacing `$PINATA_KEY` and `$PINATA_SECRET` with your API key and secret:
+
+ ```bash
+ tznft set-pinata-keys $PINATA_KEY $PINATA_SECRET --force
+ ```
+
+ This command stores the key and secret in the `tznft.json` file, so be careful not to share this file.
+
+1. Pin the first NFT metadata file to IPFS by running this command and passing the file name and a tag for the NFT, which can be the same as the file name:
+
+ ```bash
+ tznft pin-file Token1.json --tag Token1
+ ```
+
+ The command returns the URI for the data on IPFS, which starts with `ipfs://`.
+
+1. Copy the IPFS URI, because you will need it later.
+
+1. In the same way, pin the other NFT metadata files with the `tznft pin-file` command and save their URIs.
+
+1. Optional: Verify that the files are pinned successfully by opening the Pinata app to the Files page, as in this picture:
+
+ ![The Files tab on Pinata, showing three NFT metadata files](/images/nft-create/pinned-nft-meta.png)
+
+Now that the metadata is pinned to IPFS, you can create NFTs that link to this metadata.
+
+## Mint NFTs
+
+Creating NFTs is called _minting_.
+First, you create the smart contract to manage the NFTs.
+Then, you mint one or more NFTs with that contract.
+The related `tznft` commands use the configuration files that you created earlier.
+
+1. Create the collection contract from the metadata file by running this command:
+
+ ```bash
+ tznft create-collection bob --meta_file my_collection.json --alias my_collection
+ ```
+
+ This command takes the alias of the user who is the owner of the collection.
+ In this case, the owner is one of the default accounts in the sandbox.
+ The command also includes the metadata file and an optional local alias for the collection.
+
+ The command also updates the `tznft.json` file with information about the new collection, including the address of the smart contract that manages the collection.
+ This smart contract is a pre-compiled FA2 NFT contract written in the [LIGO](https://ligolang.org/) smart contract language.
+ You can write your own smart contracts to manage NFTs, but using this contract prevents errors and provides all of the functionality needed to create, transfer, and manage NFTs.
+
+1. Run this command to create a token and set Bob as the owner, replacing the IPFS URI with the URI that the `tznft pin-file` command returned in the previous section:
+
+ ```bash
+ tznft mint bob my_collection --tokens '1, ipfs://abcde12345'
+ ```
+
+ This command includes these parameters:
+
+ - The alias or address of the initial owner.
+ - The alias of the collection from the `tznft create-collection` command.
+ - The ID number and IPFS URI for the NFTs in a comma-delimited string.
+
+ If you forgot the IPFS URI, you can look it up in the Pinata app on the Files tab.
+ This tab has a column labeled "Content Identifier (CID)."
+ To create the IPFS URI, add the content identifier to the string `ipfs://`.
+
+ The response in the terminal says that the token was minted.
+
+1. Run the `tznft mint` command to mint the other NFTs.
+You can create more than one NFT in a single command by providing more than one string after the `--tokens` switch, as in this example:
+
+ ```bash
+ tznft mint bob my_collection --tokens '2, ipfs://defgh12345' '3, ipfs://ijklm12345'
+ ```
+
+1. Verify that the NFTs were minted successfully by getting their metadata with the `tznft show-meta` command:
+
+ ```bash
+ tznft show-meta bob --nft my_collection --tokens 1 2
+ ```
+
+ If the NFTs were created successfully, the command prints the metadata that you pinned to IPFS.
+
+Now the NFTs are minted to the sandbox.
+Because these NFTs are only on your local computer, in the Flextesa sandbox, you can interact with them only locally.
+They exist as long as you keep the Flextesa Docker container running, which you started with the `tznft bootstrap` command.
+
+## Transferring and manipulating NFTs
+
+The `tznft` command provides commands to manipulate NFTs locally, including transferring them between accounts.
+Just like transactions on live blockchain networks, the transaction signer must have permission to transfer or manipulate the NFTs.
+Currently, only Bob has access to the NFTs, so the `tznft` commands include him as the signer of most transactions.
+
+1. Use the `tznft show-balance` command to print information about Bob's NFTs.
+This command takes the alias or address of the collection, the signer of the transaction, the owner of the NFTs, and the IDs of one or more NFTs.
+
+ ```bash
+ tznft show-balance --nft my_collection --signer bob --owner bob --tokens 1 2
+ ```
+
+ Because NFTs are unique, the response shows a balance of 1 if the account owns the token and 0 if it does not, as in this picture:
+
+ ![THe results of the `show-balance` command, with two NFTs in Bob's account](/images/nft-create/show-balance-bob.png)
+
+1. Use the `tznft show-balance` command to print information about Alice's NFTs:
+
+ ```bash
+ tznft show-balance --nft my_collection --signer alice --owner alice --tokens 1 2
+ ```
+
+ Because Bob is the initial owner of all of the NFTs, Alice's balance is 0 for each NFT.
+
+1. Use the `tznft transfer` command to transfer one or more NFTs from Bob to Alice.
+This command takes the alias or address of the collection, the signer, and one or more comma-separated strings with the current owner, the new owner, and the ID of the NFT to transfer.
+For example, this command transfers NFTs 1 and 2 from Bob to Alice:
+
+ ```bash
+ tznft transfer --nft my_collection --signer bob --batch 'bob, alice, 1' 'bob, alice, 2'
+ ```
+
+1. Verify that the transfer worked by checking Alice's balance with the `tznft show-balance` command:
+
+ ```bash
+ tznft show-balance --nft my_collection --signer alice --owner alice --tokens 1 2
+ ```
+
+ Now Alice's balance is 1 for each token that transferred.
+ Alice is in control of these NFTs and Bob can no longer transfer them.
+
+1. Verify that Bob does not have control over the transferred NFTs by trying to transfer them back from Alice's account to Bob's account:
+
+ ```bash
+ tznft transfer --nft my_collection --signer bob --batch 'alice, bob, 1' 'alice, bob, 2'
+ ```
+
+ The response shows the error "FA2_NOT_OPERATOR" because Bob's account is not in control of these NFTs.
+
+ You can give Bob's account control over the NFTs by making his account an operator of those NFTs.
+
+1. Make Bob an operator of Alice's NFTs by passing the token IDs to the `tznft update-ops` command:
+
+ ```bash
+ tznft update-ops alice --nft my_collection --add 'bob, 1' 'bob, 2'
+ ```
+
+1. Try again to transfer the NFTs from Alice's account to Bob's account with a transaction signed by Bob:
+
+ ```bash
+ tznft transfer --nft my_collection --signer bob --batch 'alice, bob, 1' 'alice, bob, 2'
+ ```
+
+1. Check Bob's account to see that he now owns the NFTs:
+
+ ```bash
+ tznft show-balance --nft my_collection --signer bob --owner bob --tokens 1 2
+ ```
+
+## Freeze the collection
+
+When you have created all of the NFTs that you want, freeze the collection so it cannot be changed and no more NFTs can be added by running this command:
+
+```bash
+tznft mint-freeze bob my_collection
+```
+
+## Mint tokens on a testnet
+
+So far, the NFTs that you have created are available only in your local sandbox.
+When you are satisfied with the NFTs and how they behave, you can send them to a testnet and test them there.
+You can use the same configuration files and IPFS data as you used on the sandbox.
+
+By default, the `tznft.json` file has configuration information for the Tezos Ghostnet testnet, where you can test your tokens on a running Tezos network.
+
+1. Show the available networks by running the command `tznft show-network --all` and verify that the testnet is in the list.
+
+1. Change the `tznft` tool to use the testnet instead of your local sandbox:
+
+ ```bash
+ tznft set-network testnet
+ ```
+
+1. Run the `tznft bootstrap` command to get the testnet ready for your use.
+Now that the network is set to testnet, this command deploys a helper balance inspector contract to testnet that allows the `tznft` command to get information from the testnet.
+You only need to run this command for testnet once.
+
+1. Create an alias on the testnet to own the NFTs.
+You can do this in either of these two ways:
+
+ - If you have an existing Tezos wallet that supports testnets (such as Temple wallet), copy the private key from that wallet and use the `tznft add-alias` command to create a local alias for it.
+ For example, this command creates a wallet with the alias `my-account`:
+
+ ```bash
+ tznft add-alias my-account $TEZOS_PRIVATE_KEY
+ ```
+
+ - Create a local wallet with the installation of the `octez-client` command within the Flextesa Docker container:
+
+ 1. Generate local keys with the `octez-client gen keys` command.
+ For example, this command creates keys for a wallet with the alias `my-account`:
+
+ ```bash
+ docker exec flextesa-sandbox octez-client gen keys my-account
+ ```
+
+ 1. Get the keys for the wallet with this command:
+
+ ```bash
+ docker exec flextesa-sandbox octez-client show address my-account -S
+ ```
+
+ The response includes the hash, public key, and secret key for the wallet.
+ For example, in this response, the secret key starts with "edsk3WR":
+
+ ![The keys for the new account](/images/nft-create/new-key-output.png)
+
+ 1. Add the secret key as an alias with the `tznft` command, replacing `$TEZOS_PRIVATE_KEY` with the value of the secret key from the previous command:
+
+ ```bash
+ tznft add-alias my-account $TEZOS_PRIVATE_KEY
+ ```
+
+ 1. Add funds to the new wallet by going to the Ghostnet faucet at , pasting the wallet's hash in the "Or fund any address" field, and clicking a button to request tokens.
+ The wallet needs tokens to pay the fees to create the collection and mint the tokens on Ghostnet.
+
+1. Create the collection on the testnet.
+The command is the same as for the sandbox, and you can create a new collection file or use the file from the sandbox.
+Similarly, you can use the same collection alias because `tznft` keeps aliases separate on different networks, but be sure not to get the aliases confused.
+
+ ```bash
+ tznft create-collection my-account --meta_file my_collection.json --alias my_collection
+ ```
+
+1. Mint the tokens on the testnet.
+The command is the same as for the sandbox:
+
+ ```bash
+ tznft mint my-account my_collection --tokens '1, ipfs://abcde12345'
+ ```
+
+ You can add more NFTs until you freeze the collection.
+
+1. View your token balances.
+The command is the same as for the sandbox:
+
+ ```bash
+ tznft show-balance --nft my_collection --signer my-account --owner my-account --tokens 1
+ ```
+
+1. View the tokens on a block explorer:
+
+ 1. Get the address of the collection on the testnet from the `testnet` section of the `tznft.json` file.
+ The address starts with "KT1".
+
+ 1. Go to a block explorer, such as .
+
+ 1. Set the block explorer to use testnet instead of Tezos mainnet.
+
+ 1. In the search field, paste the address of the collection and press Enter.
+
+ The block explorer shows information about the contract that manages the NFTs, including a list of all NFTs in the contract, who owns them, and a list of recent transactions.
+
+Now the NFTs are on Tezos ghostnet and you can transfer and manipulate them just like you did in the sandbox.
+You may need to create and fund more account aliases to transfer them, but the commands are the same.
+For example, to transfer NFTs to an account with the alias `other-account`, run this command:
+
+```bash
+tznft transfer --nft my_collection --signer my-account --batch 'my-account, other-account, 1' 'my-account, other-account, 2'
+```
+
+## Summary
+
+Now you can create, test, and deploy NFTs locally and to testnets.
+The process for minting NFTs to Tezos mainnet is the same, but you must use an account with real XTZ in it to pay the transaction fees.
+
+If you want to continue working with these NFTs, try creating a marketplace for them as described in the tutorial [Build an NFT Marketplace](../build-an-nft-marketplace).