Create an NFT (Part 1)

Introduction

On Avalanche, digital goods are represented as tokens. Some tokens are fungible, which means that one token is interchangeable for any other one token. Real-world currency is fungible, for example; one $5 note is treated as being the same as any other $5 note.
Avalanche also supports non-fungible tokens (NFTs). By definition, each NFT is unique and not perfectly interchangeable for any other NFT. For example, there could be an NFT that represents ownership of a real-world piece of art; each piece of art, like each NFT, is unique. NFTs represent digital scarcity and may prove to have even greater utility than traditional fungible tokens.
In this tutorial, we’ll create and send NFTs using AvalancheGo’s API. In a future tutorial, we’ll create a custom NFT family using AvalancheJS and explore NFTs in more detail.

Requirements

You've completed Run an Avalanche Node and are familiar with Avalanche's architecture. In this tutorial, we use Avalanche’s Postman collection to help us make API calls.

Create the NFT Family

Each NFT belongs to a family, which has a name and a symbol. Each family is composed of groups. The number of groups in a family is specified when the family is created. Our NFT will exist on the X-Chain, so to create our NFT family we’ll call avm.createNFTAsset, which is a method of the X-Chain’s API.
The signature for this method is:
1
avm.createNFTAsset({
2
name: string,
3
symbol: string,
4
minterSets []{
5
minters: []string,
6
threshold: int
7
},
8
from: []string,
9
changeAddr: string,
10
username: string,
11
password: string
12
}) ->
13
{
14
assetID: string,
15
changeAddr: string,
16
}
Copied!

Method

Parameters
    name is a human-readable name for our NFT family. Not necessarily unique. Between 0 and 128 characters.
    symbol is a shorthand symbol for this NFT family. Between 0 and 4 characters. Not necessarily unique. May be omitted.
    minterSets is a list where each element specifies that threshold of the addresses in minters may together mint more of the asset by signing a minting operation.
    Performing a transaction on the X-Chain requires a transaction fee paid in AVAX. username and password denote the user paying the fee.
    from are the addresses that you want to use for this operation. If omitted, uses any of your addresses as needed.
    changeAddr is the address any change will be sent to. If omitted, change is sent to any of your addresses.

Response

    assetID is the ID of the new asset that we’ll have created.
    changeAddr in the result is the address where any change was sent.
Later in this example, we’ll mint an NFT, so be sure to replace at least 1 address in the minter set with an address which your user controls.
1
curl -X POST --data '{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"method" :"avm.createNFTAsset",
5
"params" :{
6
"name":"Family",
7
"symbol":"FAM",
8
"minterSets":[
9
{
10
"minters": [
11
"X-avax1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7"
12
],
13
"threshold": 1
14
}
15
],
16
"username":"USERNAME GOES HERE",
17
"password":"PASSWORD GOES HERE"
18
}
19
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/X
Copied!
The response should look like this:
1
{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"result" :{
5
"assetID":"2X1YV4jpGpqezvj2khQdj1yEiXU1dCwsJ7DmNhQRyZZ7j9oYBp",
6
"changeAddr":"X-avax1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7"
7
}
8
}
Copied!
A couple things to note: first, in addition to creating an NFT family, AvalancheGo’s avm.createNFTAsset also creates a group for each of the minterSets, which are passed in. For example, if minterSets has 3 elements, the NFT family has 3 groups. Second, take note of the assetID which is returned in the response. This is the assetID of the newly created NFT family, and you’ll need it later to issue NFTs.
You may be wondering why we specify sets of addresses that can mint more units of the asset rather than a single address. Here's why:
    Security: if only one address can mint more of the asset, and the private key for that address is lost, no more units can ever be minted. Similarly, if only one address can mint more of the asset, nothing stops the holder of that address from unilaterally minting as much as they want.
    Flexibility: it’s nice to be able to encode logic like, "Alice can unilaterally mint more units of this asset, or 2 of Dinesh, Ellin, and Jamie can together mint more."

Get UTXOs for NFT

NFT outputs don’t show up in calls to avm.getBalance or avm.getAllBalances. To see your NFTs, you have to call avm.getUTXOs and then parse the utxo to check for the type ID. NFT Mint Outputs have a type id of 00 00 00 0a in hexidecimal (10 in decimal) and NFT Transfer Outputs have a type id of 00 00 00 0b in hexdecimal (11 in decimal).

Method

Parameters

    addresses are the addresses to fetch UTXOs for.
Response:
    numFetched is the total number of UTXOs in the response.
    utxos is an array of CB58 encoded strings.
    endIndex This method supports pagination. endIndex denotes the last UTXO returned.
1
curl -X POST --data '{
2
"jsonrpc":"2.0",
3
"id" : 1,
4
"method" :"avm.getUTXOs",
5
"params" :{
6
"addresses":["X-avax1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7"]
7
}
8
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/X
Copied!
The response contains a list of UTXOs:
1
{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"result" :{
5
"numFetched": "2",
6
"utxos": [
7
"116VhGCxiSL4GrMPKHkk9Z92WCn2i4qk8qdN3gQkFz6FMEbHo82Lgg8nkMCPJcZgpVXZLQU6MfYuqRWfzHrojmcjKWbfwqzZoZZmvSjdD3KJFsW3PDs5oL3XpCHq4vkfFy3q1wxVY8qRc6VrTZaExfHKSQXX1KnC",
8
"11cxRVipJgtuHy1ZJ6qM7moAf3GveBD9PjHeZMkhk7kjizdGUu5RxZqhViaWh8dJa9jT9sS62xy73FubMAxAy8b542v3k8frTnVitUagW9YhTMLmZ6nE48Z9qXB2V9HHzCuFH1xMvUEj33eNWv5wsP3JvmywkwkQW9WLM"
9
],
10
"endIndex": {
11
"address": "X-avax1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7",
12
"utxo": "2iyUVo8XautXpZwVfp5vhSh4ASWbo67zmHbtx7SUJg2Qa8BHtr"
13
}
14
}
15
}
Copied!
avm.getUTXOs returns 2 UTXOs. Let’s take the first one and decode it to confirm that it’s an NFT Mint Output. First, we convert the Base58Check encoded string which is returned from avm.getUTXOs in to hex. The following CB58 string:
1
116VhGCxiSL4GrMPKHkk9Z92WCn2i4qk8qdN3gQkFz6FMEbHo82Lgg8nkMCPJcZgpVXZLQU6MfYuqRWfzHrojmcjKWbfwqzZoZZmvSjdD3KJFsW3PDs5oL3XpCHq4vkfFy3q1wxVY8qRc6VrTZaExfHKSQXX1KnC
Copied!
is expressed in hexadecimal as:
1
00 00 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57 00 00 00 01 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57 00 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 3c b7 d3 84 2e 8c ee 6a 0e bd 09 f1 fe 88 4f 68 61 e1 b2 9c
Copied!
Now, we can decompose the hex into the UTXO’s individual components by referring to the transaction serialization format:
1
NFT Mint Output
2
3
CodecID: 00 00
4
TXID: 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57
5
Output Index: 00 00 00 01
6
AssetID: 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57
7
TypeID: 00 00 00 0a
8
GroupID: 00 00 00 00
9
Locktime: 00 00 00 00 00 00 00 00
10
Threshold: 00 00 00 01
11
Address Count: 00 00 00 01
12
Addresses[0]: 3c b7 d3 84 2e 8c ee 6a 0e bd 09 f1 fe 88 4f 68 61 e1 b2 9c
Copied!
Note that the TypeID is 00 00 00 0a which is the correct type ID for an NFT Mint Output. Also note that the GroupID is 00 00 00 00. This GroupID was created based on the number of MinterSets which I passed in to avm.createNFTAsset.

Mint the Asset

Now that we have an NFT family and a group for the single MinterSet we’re able to create NFTs belonging to this group. To do that we call avm.mintNFT:

Method

Parameters

    assetID is the ID of the NFT family.
    payload is an arbitrary CB58 encoded payload of up to 1024 bytes. In Part 2 (COMING SOON) we’ll explore creating a protocol around the NFT payload. For this tutorial, the payload is the string "AVA Labs".
    to is the address that will receive the newly minted NFT. Replace to with an address your user controls so that later you’ll be able to send some of the newly minted NFT.
    username must be a user that holds keys giving it permission to mint more of this NFT. That is, it controls at least threshold keys for one of the minter sets we specified above.
    password is the valid password for username

Response

    txID is the transaction ID.
    changeAddr in the result is the address where any change was sent.
1
curl -X POST --data '{
2
"jsonrpc":"2.0",
3
"id" : 1,
4
"method" :"avm.mintNFT",
5
"params" :{
6
"assetID":"2X1YV4jpGpqezvj2khQdj1yEiXU1dCwsJ7DmNhQRyZZ7j9oYBp",
7
"payload":"2EWh72jYQvEJF9NLk",
8
"to":"X-avax1a202a8pu5w4vnerwzp84j68yknm6lf47drfsdv",
9
"username":"USERNAME GOES HERE",
10
"password":"PASSWORD GOES HERE"
11
}
12
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/X
Copied!
The response contains the transaction’s ID:
1
{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"result" :{
5
"txID":"x4fKx95KirTvqWCeiPZfnjB4xFdrTduymRKMouXTioXojdnUm",
6
"changeAddr": "X-avax1a202a8pu5w4vnerwzp84j68yknm6lf47drfsdv"
7
}
8
}
Copied!
Similar to the previous step, we can now confirm that an NFT was minted by calling avm.getUTXOs and parsing the UTXO to confirm that we now have an NFT Transfer Output.
1
curl -X POST --data '{
2
"jsonrpc":"2.0",
3
"id" : 1,
4
"method" :"avm.getUTXOs",
5
"params" :{
6
"addresses":["X-avax1a202a8pu5w4vnerwzp84j68yknm6lf47drfsdv"]
7
}
8
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/X
Copied!
This should give:
1
{
2
"jsonrpc": "2.0",
3
"result": {
4
"numFetched": "2",
5
"utxos": [
6
"11Do4RK6FchGXeoycKujR7atm3tvBz3qc64uoipCc5J74Sj1U4orM6vbBGSES8hnjgjZava9oPgmnbHxh2mBKjeXdvAqTRtYMHEacrveSzKgk7F8h8xi8JB9CddoiX8nbjZMYt1keGo5Rvpjh8dGymDWwRbV1FdnG5uDiiyU8uidc3P24",
7
"11JL98R9yVoCaekrzP2PoCKJfCTin6vhTWU4h9TxqevEUnhiMo2j7F4DHxRpHq6BnFnHGAajhmiXgrdfUbbNd1izmdLVMwqe3UCTJWWLaJ6XUZ46R243T8NdhKXXJWC9GvcjFYMyiKRWvVnvFt7duzq8P8D53uhv1QfdQ9"
8
],
9
"endIndex": {
10
"address": "X-avax1a202a8pu5w4vnerwzp84j68yknm6lf47drfsdv",
11
"utxo": "2qs3A1sBhVjFcXqRADJ7AorvoawVgMkNdgJi8eYNPABMKmdBYq"
12
}
13
},
14
"id": 1
15
}
Copied!
As in the previous step, we can now decode the CB58 encoded UTXO to hexidecimal and then decompose it to its individual components to confirm that we have the correct UTXO and type.
First, we convert the Base58Check encoded string which is returned from avm.getUTXOs in to hex. The following CB58 string:
1
11Do4RK6FchGXeoycKujR7atm3tvBz3qc64uoipCc5J74Sj1U4orM6vbBGSES8hnjgjZava9oPgmnbHxh2mBKjeXdvAqTRtYMHEacrveSzKgk7F8h8xi8JB9CddoiX8nbjZMYt1keGo5Rvpjh8dGymDWwRbV1FdnG5uDiiyU8uidc3P24
Copied!
is expressed in hexadecimal as:
1
00 00 7d 07 0d 1e fe a6 4e 45 09 05 c6 11 ee b1 cf 61 9f 21 22 eb 17 db aa ea 9a fe 2d ff 17 be 27 6b 00 00 00 01 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57 00 00 00 0b 00 00 00 00 00 00 00 08 41 56 41 20 4c 61 62 73 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 3c b7 d3 84 2e 8c ee 6a 0e bd 09 f1 fe 88 4f 68 61 e1 b2 9c
Copied!
Now, we can decompose the hex into the UTXO’s individual components:
1
NFT Mint Output
2
3
CodecID: 00 00
4
TXID: 7d 07 0d 1e fe a6 4e 45 09 05 c6 11 ee b1 cf 61 9f 21 22 eb 17 db aa ea 9a fe 2d ff 17 be 27 6b
5
Output Index: 00 00 00 01
6
AssetID: 04 78 f2 39 8d d2 16 3c 34 13 2c e7 af a3 1f 0a c5 03 01 7f 86 3b f4 db 87 ea 55 53 c5 2d 7b 57
7
TypeID: 00 00 00 0b
8
GroupID: 00 00 00 00
9
Payload Length: 00 00 00 08
10
Payload: 41 56 41 20 4c 61 62 73
11
Locktime: 00 00 00 00 00 00 00 00
12
Threshold: 00 00 00 01
13
Address Count: 00 00 00 01
14
Addresses[0]: 3c b7 d3 84 2e 8c ee 6a 0e bd 09 f1 fe 88 4f 68 61 e1 b2 9c
Copied!
Note that the TypeID is 00 00 00 0b which is the correct type id for an NFT Transfer Output. Also, note that the Payload is included.

Send the NFT

Now, you can send the NFT to anyone. To do that, use AvalancheGo’s avm.sendNFT API method.
Method
Parameters
    assetID is the ID of the NFT we’re sending.
    to is the address that will receive the newly minted NFT.
    groupID is the NFT group from which to send the NFT.
    username is the user that controls the NFT.
    password is the valid password for username
Response
    txID is the transaction ID.
    changeAddr in the result is the address where any change was sent.
1
curl -X POST --data '{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"method" :"avm.sendNFT",
5
"params" :{
6
"assetID" :"2X1YV4jpGpqezvj2khQdj1yEiXU1dCwsJ7DmNhQRyZZ7j9oYBp",
7
"to" :"X-avax1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7",
8
"groupID" : 0,
9
"username":"USERNAME GOES HERE",
10
"password":"PASSWORD GOES HERE"
11
}
12
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/X
Copied!
The response confirms that our NFT Transfer Operation was successful:
1
{
2
"jsonrpc":"2.0",
3
"id" :1,
4
"result" :{
5
"txID": "txtzxcrzPx1sn38HWKU9PB52EpbpXCegbdHNxPNAYd9ZvezJq",
6
"changeAddr": "X-avax1a202a8pu5w4vnerwzp84j68yknm6lf47drfsdv"0
7
}
8
}
Copied!
You can call avm.getUTXOs for the address which you sent the NFT to and decompose the returned UTXO, after converting from CB58 to hex, to confirm that there is a UTXO with type id 00 00 00 0b in hex or 11 in decimal.

Wrapping up

Blockchain technology and tokenomics represent a radical new way of representing digital assets. Non-fungible tokens allow scarce assets to be tokenized. In this tutorial, we:
    Used createNFTAsset to create a non-fungible asset family and group.
    Used mintNFT to mint units of an NFT to the group.
    Used getUTXOs to fetch UTXOs for an address. We then converted the CB58 encoded UTXO to hex and decomposed it to its individual components.
    Used sendNFT to transfer NFTs between addresses.
In Part 2 of this series, we’ll go more in-depth by using AvalancheJS to create a protocol for our NFT payload by issuing it to multiple groups.
Last modified 2mo ago