v5 (LATEST)
Devnet
Testnet
🚧 Development Version Notice
You are currently viewing documentation for the version that is under active development.
🔹 This version has not been deployed to mainnet yet
🔹 Features and APIs may change before final release
Create Non-Fungible Token with CLI
This tutorial will guide you through the process of using the assetnft module to create and manage Non-Fungible Tokens.
Please note that each subsequent section depends on the previous one.
Prerequisites
-
We are going to interact with testnet, so we need to set
--node
and--chain-id
flag in each request.export CHAIN_ID="coreum-testnet-1" export RPC_URL="https://full-node.testnet-1.coreum.dev:26657"
If you want to use other network, find relevant values at network variables page.
-
Two accounts at your local keychain. If you don't have them, follow these steps - go to faucet page and generate two accounts there. Then, having the mnemonics, import them with the following commands
cored keys add nft-issuer-wallet --recover --chain-id=$CHAIN_ID # put first mnemonic here cored keys add nft-receiver-wallet --recover --chain-id=$CHAIN_ID # put second mnemonic here
-
Also, since network operates with raw addresses, not your local names, we are going to bind raw addresses to env vars. We will export the following values:
export NFT_ISSUER_ADDRESS=$(cored keys show nft-issuer-wallet --address --chain-id=$CHAIN_ID) export NFT_RECEIVER_ADDRESS=$(cored keys show nft-receiver-wallet --address --chain-id=$CHAIN_ID)
Create your first NFT
- We will export one more environment variable to store NFT class id, which is a uniquely named
group of NFT objects. It can be defined with different characteristics (features) that are
common for all NFTs belonging to the class.
export NFT_CLASS_ID=$(echo "puppysmartnft1-"$NFT_ISSUER_ADDRESS)
Create NFT class
-
Let's create new NFT class:
# cored tx assetnft issue-class [symbol] [name] [description] [uri] [uri_hash] --from [issuer] --features=burning,freezing,whitelisting,disable_sending [flags] cored tx assetnft issue-class puppysmartnft1 "Puppy NFTs" "A collection of awesome puppy NFTs" "http://puppy-nfts.com" "somehash" --from $NFT_ISSUER_ADDRESS --features=burning,freezing,whitelisting --node=$RPC_URL --chain-id=$CHAIN_ID # confirm transaction before signing and broadcasting [y/N]: y
As an output, you should receive tx hash, copy it and go to Block explorer to see the tx status. Features:
burning
,freezing
andwhitelisting
are explained later in this guide. -
Now, let's check what NFT classes reside on the Coreum blockchain:
cored q nft classes --node=$RPC_URL --chain-id=$CHAIN_ID #classes: #- data: null # description: A collection of awesome puppy NFTs # id: puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7 # name: Puppy NFTs # symbol: puppysmartnft1 # uri: http://puppy-nfts.com # uri_hash: somehash
As you see from the output, your new token has got unique class
id
which consists of thesymbol
provided and issuer account address.
Mint NFTs
-
We will now mint 2 NFTs within created class
#cored tx assetnft mint [class-id] [id] [uri] [uri_hash] --from [sender] [flags] cored tx assetnft mint $NFT_CLASS_ID puppysmartnft-1 "http://puppy-nfts.com/puppynft-1" "somehash" --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID cored tx assetnft mint $NFT_CLASS_ID puppysmartnft-2 "http://puppy-nfts.com/puppynft-2" "somehash" --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
Again, we can check transaction status by providing transaction hashes on the Block explorer page
-
We can query all NFTs of a given class with the following command
cored q nft nfts --class-id=$NFT_CLASS_ID --node=$RPC_URL --chain-id=$CHAIN_ID
#nfts:
#- class_id: puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7
# data: null
# id: puppysmartnft-1
# uri: http://puppy-nfts.com/puppynft-1
# uri_hash: somehash
#- class_id: puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7
# data: null
# id: puppysmartnft-2
# uri: http://puppy-nfts.com/puppynft-2
# uri_hash: somehash
As you can see, 2 NFTs were properly minted.
Whitelisting
Whitelisting functionality allows transferring NFTs of a certain class only to specific addresses. Since we enabled that feature for our NFT class, in order to send one of our minted NFTs to a receiver, we need to first whitelist them.
cored tx assetnft whitelist $NFT_CLASS_ID puppysmartnft-1 $NFT_RECEIVER_ADDRESS --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
Sending and querying balance
Our NFT is now ready to be transferred to the receiving account.
Send
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_RECEIVER_ADDRESS --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
Query the balance
We can query NFT balances in multiple ways. These could be as follows
cored q nft balance $NFT_RECEIVER_ADDRESS $NFT_CLASS_ID --node=$RPC_URL --chain-id=$CHAIN_ID
# amount: "1"
That way, we can query the number of NFTs of a given class owned by the owner. Our receiving account owns 1 NFT of this class.
We can also query the owner of the NFT based on its class and id.
cored q nft owner $NFT_CLASS_ID puppysmartnft-1 --node=$RPC_URL --chain-id=$CHAIN_ID
# owner: testcore1k9575m9egrlmymnyd29p5g0p5e94d930tg67sv
Freezing
Freezing, if enabled for NFT class, allows issuer to freeze specific NFT of a given class. A frozen token cannot be transferred until it is unfrozen by the issuer.
Freeze
cored tx assetnft freeze $NFT_CLASS_ID puppysmartnft-1 --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
Our NFT of this class is now frozen. Let's verify if this is actually the case
- Query frozen
Using the following command, we can check frozen
flag set for an NFT in a given class
cored q assetnft frozen $NFT_CLASS_ID puppysmartnft-1 --node=$RPC_URL --chain-id=$CHAIN_ID
#frozen: true
- Let's now try to send the NFT from the receiver back to the issuer. Such transaction should not succeed.
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_ISSUER_ADDRESS --from $NFT_RECEIVER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID -b=block
#at the end of the raw_log section, notice an error: "raw_log: 'failed to execute message; message index: 0: nft with classID:puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7 and ID:puppysmartnft-1 is frozen: unauthorized'
Notice we used -b=block
argument which waits for the tx to pass/fail checks and be committed in a
block.
Unfreeze
- We can unfreeze the token and make it transferable again
cored tx assetnft unfreeze $NFT_CLASS_ID puppysmartnft-1 --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
- Sending the token back to the issuer succeeds this time
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_ISSUER_ADDRESS --from $NFT_RECEIVER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID -b=block
- Let's query the current owner of the token
cored q nft owner $NFT_CLASS_ID puppysmartnft-1 --node=$RPC_URL --chain-id=$CHAIN_ID
#owner: testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7
The address shown belongs to the issuer.
Class Freeze
- Let's send the NFT back to receiver first.
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_RECEIVER_ADDRESS --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
- Now we can freeze all the NFTs of a class held by an account
cored tx assetnft class-freeze $NFT_CLASS_ID $NFT_RECEIVER_ADDRESS --from $NFT_ISSUER_ADDRESS --chain-id=$CHAIN_ID -b=block
- Let's now try to send the NFT from the issuer to the receiver. Such transaction should not succeed.
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_ISSUER_ADDRESS --from $NFT_RECEIVER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID -b=block
#at the end of the raw_log section, notice an error: "raw_log: 'failed to execute message; message index: 0: nft with classID:puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7 and ID:puppysmartnft-1 is frozen: unauthorized'
Class Unfreeze
- We can remove the class freeze of an account
cored tx assetnft class-unfreeze $NFT_CLASS_ID $NFT_RECEIVER_ADDRESS --from $NFT_ISSUER_ADDRESS --chain-id=$CHAIN_ID -b=block
- Sending the token back to the issuer succeeds this time
cored tx nft send $NFT_CLASS_ID puppysmartnft-1 $NFT_ISSUER_ADDRESS --from $NFT_RECEIVER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID -b=block
Burning
If burning
feature is enabled on NFT class level, it allows an owner of a token to burn it.
- First, let's query an nft we want to burn
cored q nft nft $NFT_CLASS_ID puppysmartnft-2 --node=$RPC_URL --chain-id=$CHAIN_ID
#nft:
# class_id: puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7
# data: null
# id: puppysmartnft-2
# uri: http://puppy-nfts.com/puppynft-2
# uri_hash: somehash
- Now, then token can be burnt
cored tx assetnft burn $NFT_CLASS_ID puppysmartnft-2 --from $NFT_ISSUER_ADDRESS --node=$RPC_URL --chain-id=$CHAIN_ID
- As a verification step, let's check if the token still exists
cored q nft nft $NFT_CLASS_ID puppysmartnft-2 --node=$RPC_URL --chain-id=$CHAIN_ID
#notice an error at the end of the output
#not found nft: class: puppysmartnft1-testcore105hmczwh0tkha2h5lu9rr07xtegzsm49d3hxq7, id: puppysmartnft-2: nft does not exist: invalid request
Update Data
update the data of the specified NFT with the information provided in the data file
cored tx assetnft update-data $NFT_CLASS_ID puppysmartnft-1 --from $NFT_ISSUER_ADDRESS --data-file /path/to/your/data-file.json --node=$RPC_URL --chain-id=$CHAIN_ID
FAQ
Can a token be always burnt by an owner?
No, when a token is frozen, it cannot be burnt. Also, burning
feature needs to be enabled on NFT
class id level.
In order to burn a token, does burning
feature always need to be enabled?
In general yes. However, an issuer of a token, can always burn it (while they own it), regardless of the feature setting.
Can all tokens within an NFT class be frozen/unfrozen at once?
No. There's no single command allowing to achieve that. It can, though, be done programmatically
(for example, by utilizing the output returned by
cored q nft nfts --class-id=$NFT_CLASS_ID --node=$RPC_URL --chain-id=$CHAIN_ID
command).
What is next?
You can read more about Non-Fungible Tokens at Smart Token Overview and assetnft spec pages
Create Non-Fungible Token with Golang
This document gives instructions and examples on how to use our pkg/client
package to create and
manage non-fungible token.
Complete code
Complete code with go.mod
file you can find
here
P.S. If you have issues with go mod tidy
command, just copy go.mod
file from the example above.
!!!include(/docs/next/tutorials/get-started/golang)!!!
Creating NFT class
First we create class which is a container for a set of NFTs having the same purpose:
senderAddress, err := senderInfo.GetAddress()
if err != nil {
panic(err)
}
const classSymbol = "NFTClass"
msgIssueClass := &assetnfttypes.MsgIssueClass{
Issuer: senderAddress.String(),
Symbol: classSymbol,
Name: "NFT Class",
Description: "NFT Class",
Features: []assetnfttypes.ClassFeature{assetnfttypes.ClassFeature_freezing},
}
ctx := context.Background()
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgIssueClass,
)
if err != nil {
panic(err)
}
Minting NFT
Then we mint new NFT for that class:
classID := assetnfttypes.BuildClassID(classSymbol, senderAddress)
const nftID = "myNFT"
msgMint := &assetnfttypes.MsgMint{
Sender: senderAddress.String(),
ClassID: classID,
ID: nftID,
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgMint,
)
if err != nil {
panic(err)
}
Querying the owner
We query the owner of the NFT to verify that it is owned by the creator:
nftClient := nft.NewQueryClient(clientCtx)
resp, err := nftClient.Owner(ctx, &nft.QueryOwnerRequest{
ClassId: classID,
Id: nftID,
})
if err != nil {
panic(err)
}
fmt.Printf("Owner: %s\n", resp.Owner)
Sending NFT
Now we send NFT to someone else:
recipientInfo, _, err := clientCtx.Keyring().NewMnemonic(
"recipient",
keyring.English,
sdk.GetConfig().GetFullBIP44Path(),
"",
hd.Secp256k1,
)
if err != nil {
panic(err)
}
recipientAddress, err := recipientInfo.GetAddress()
if err != nil {
panic(err)
}
msgSend := &nft.MsgSend{
Sender: senderAddress.String(),
Receiver: recipientAddress.String(),
Id: nftID,
ClassId: classID,
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgSend,
)
if err != nil {
panic(err)
}
Let's verify that recipient is the owner now:
resp, err = nftClient.Owner(ctx, &nft.QueryOwnerRequest{
ClassId: classID,
Id: nftID,
})
if err != nil {
panic(err)
}
fmt.Printf("Owner: %s\n", resp.Owner)
Freezing
Because issuer enabled freezing
feature during class issuance, he/she might freeze the NFT:
msgFreeze := &assetnfttypes.MsgFreeze{
Sender: senderAddress.String(),
ClassID: classID,
ID: nftID,
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgFreeze,
)
if err != nil {
panic(err)
}
Update Data
update the data of the specified NFT with the information provided in the data file
dataDynamic := assetnfttypes.DataDynamic{
Items: []assetnfttypes.DataDynamicItem{
{
Editors: []assetnfttypes.DataEditor{
assetnfttypes.DataEditor_owner,
},
Data: jsonData,
},
},
}
data, err = codectypes.NewAnyWithValue(&dataDynamic)
if err != nil {
panic(err)
}
// Broadcast transaction minting new dynamic nft
classID = assetnfttypes.BuildClassID(classSymbol, senderAddress)
const dynamicNftID = "myDynamicNFT"
msgMint = &assetnfttypes.MsgMint{
Sender: senderAddress.String(),
ClassID: classID,
ID: dynamicNftID,
Data: data,
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgMint,
)
if err != nil {
panic(err)
}
// Query the nft
storedNFT, err := nftClient.NFT(ctx, &nft.QueryNFTRequest{
ClassId: classID,
Id: dynamicNftID,
})
if err != nil {
panic(err)
}
var storedDataDynamic assetnfttypes.DataDynamic
err = storedDataDynamic.Unmarshal(storedNFT.Nft.Data.Value)
if err != nil {
panic(err)
}
fmt.Printf("Data: %s\n", string(storedDataDynamic.Items[0].Data))
// update stored NFT
msgUpdateData := &assetnfttypes.MsgUpdateData{
Sender: senderAddress.String(),
ClassID: classID,
ID: dynamicNftID,
Items: []assetnfttypes.DataDynamicIndexedItem{
{
Index: 0,
Data: []byte(`{"name": "Updated Name", "description": "Updated Description"}`),
},
},
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderAddress),
txFactory,
msgUpdateData,
)
if err != nil {
panic(err)
}
storedNFT, err = nftClient.NFT(ctx, &nft.QueryNFTRequest{
ClassId: classID,
Id: dynamicNftID,
})
if err != nil {
panic(err)
}
var storedDataDynamic2 assetnfttypes.DataDynamic
err = storedDataDynamic2.Unmarshal(storedNFT.Nft.Data.Value)
if err != nil {
panic(err)
}
fmt.Printf("Data: %s\n", string(storedDataDynamic2.Items[0].Data))
After doing this, recipient is not allowed to transfer the NFT from its account.
All the other features may be used in a similar fashion. More info is available in the documentation