Links

SNIP-722

This document describes several optional enhancements to the SNIP-721 specification. Contracts that support the existing SNIP-721 standard are still considered SNIP-721 compliant. Clients should be written so as to benefit from these SNIP-722 features when available, but provide fallbacks for when these features are not available.
The features specified in this document enable a SNIP-722 compliant contract to be used for badges and POAPs as well as non-transferable tokens.

Badges/POAPs

The primary update for Badges/POAPs is the addition of a token_subtype field in the Metadata Extension struct. This field is intended to be used by applications in order to differentiate NFTs that are used as Badges/POAPs so that they can be displayed as such because they will be used for things like trophies, achievements, proof of attendence, etc... An example of the definition of Metadata and Extension as used by Stashh is included below.

Metadata

This is the metadata for a token that follows CW-721 metadata specification, which is based on ERC721 Metadata JSON Schema. The reference implementation will throw an error if both token_uri and extension are provided.
{
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
}
Name
Type
Description
Optional
Value If Omitted
token_uri
string
Uri pointing to off-chain JSON metadata
yes
nothing
extension
Data structure defining on-chain metadata
yes
nothing
The reference implementation will throw an error if both token_uri and extension are provided.

Extension

This is an on-chain metadata extension struct that conforms to the Stashh metadata standard (which in turn implements https://docs.opensea.io/docs/metadata-standards). Urls should be prefixed with http://, https://, ipfs://, or ar://.
{
"image": "optional_image_url",
"image_data": "optional_raw_svg_image_data",
"external_url": "optional_url_to_view_token_on_your_site",
"description": "optional_token_description",
"name": "optional_token_name",
"attributes": [
{
"display_type": "optional_display_format_for_numerical_traits",
"trait_type": "optional_name_of_the_trait",
"value": "trait value",
"max_value": "optional_max_value_for_numerical_traits"
},
{
"...": "...",
},
],
"background_color": "optional_six-character_hexadecimal_background_color_(without_pre-pended_`#`)",
"animation_url": "optional_url_to_multimedia_file",
"youtube_url": "optional_url_to_a_YouTube_video",
"media": [
{
"file_type": "optional_file_type",
"extension": "optional_file_extension",
"authentication": {
"key": "optional_decryption_key_or_password",
"user": "optional_username_for_authentication"
},
"url": "url_pointing_to_the_multimedia_file"
},
{
"...": "...",
},
],
"protected_attributes": [ "list", "of_attributes", "whose_types", "are_public", "but_values", "are_private" ],
"token_subtype": "badge"
}
Name
Type
Description
Optional
Value If Omitted
image
string
Url to the token's image
yes
nothing
image_data
string
Raw SVG image data that should only be used if there is no image field
yes
nothing
external_url
string
Url to view the token on your site
yes
nothing
description
string
Text description of the token
yes
nothing
name
string
Name of the token
yes
nothing
attributes
array of Trait (see below)​
Token's attributes
yes
nothing
background_color
string
Background color represented as a six-character hexadecimal without a pre-pended #
yes
nothing
animation_url
string
Url to a multimedia file
yes
nothing
youtube_url
string
Url to a YouTube video
yes
nothing
media
array of MediaFile (see below)​
List of multimedia files using Stashh specifications
yes
nothing
protected_attributes
array of string
List of attributes whose types are public but whose values are private
yes
nothing
token_subtype
string
token subtype used to signify what the NFT is used for, such as "badge"
yes
nothing

Trait

Trait describes a token attribute as defined in https://docs.opensea.io/docs/metadata-standards.
{
"display_type": "optional_display_format_for_numerical_traits",
"trait_type": "optional_name_of_the_trait",
"value": "trait value",
"max_value": "optional_max_value_for_numerical_traits"
}
Name
Type
Description
Optional
Value If Omitted
display_type
string
Display format for numerical traits
yes
nothing
trait_type
string
Name of the trait
yes
nothing
value
string
Trait value
no
​
max_value
string
Maximum value for this numerical trait
yes
nothing

MediaFile

MediaFile is the data structure used by Stashh to reference off-chain multimedia files. It allows for hosted files to be encrypted or authenticated with basic authentication, and for the decryption key or username/password to also be included in the on-chain private metadata. Urls should be prefixed with http://, https://, ipfs://, or ar://.
{
"file_type": "optional_file_type",
"extension": "optional_file_extension",
"authentication": {
"key": "optional_decryption_key_or_password",
"user": "optional_username_for_authentication"
},
"url": "url_pointing_to_the_multimedia_file"
}
Name
Type
Description
Optional
Value If Omitted
file_type
string
File type. Stashh currently uses: "image", "video", "audio", "text", "font", "application"
yes
nothing
extension
string
File extension
yes
nothing
authentication
Credentials or decryption key for a protected file
yes
nothing
url
string
Url to the multimedia file
no
​

Authentication

Authentication is used to provide the decryption key or username/password for protected files.
{
"key": "optional_decryption_key_or_password",
"user": "optional_username_for_authentication"
}
Name
Type
Description
Optional
Value If Omitted
key
string
Decryption key or password
yes
nothing
user
string
Username for basic authentication
yes
nothing

Queries

ImplementsTokenSubtype

ImplementsTokenSubtype indicates whether the contract implements the token_subtype Extension field. Because legacy SNIP-721 contracts do not implement this query and do not implement token subtypes, any use of this query should always check for an error response, and if the response is an error, it can be considered that the contract does not implement subtypes. Because message parsing ignores input fields that a contract does not expect, this query should be used before attempting a message that uses the token_subtype Extension field. If the message is sent to a SNIP-721 contract that does not implement token_subtype, that field will just be ignored and the resulting NFT will still be created/updated, but without a token_subtype.
Request
{
"implements_token_subtype": {}
}
Response
{
"implements_token_subtype": {
"is_enabled": true | false
}
}
Name
Type
Description
Optional
is_enabled
bool
True if the contract implements token subtypes
no

Non-Transferable Tokens

Non-transferable tokens are NFTs that can never have an owner other than the address it was minted to. In order to provide the owner of a non-transferable token a way to get rid of an unwanted NFT, non-transferable tokens must always have the ability to be burned (see here). Also, it should be noted that setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale. Because the intent of VerifyTransferApproval is to provide contracts a way to know before-hand whether an attempt to transfer tokens will fail, the reference implementation has modified VerifyTransferApproval to consider any non-transferable token as unapproved for transfer.

Messages

The following are examples of how the reference implementation of non-transferable tokens provides the ability to specify whether an NFT should be transferable at the time it is minted.

MintNft

MintNft mints a single token.
Request
{
"mint_nft": {
"token_id": "optional_ID_of_new_token",
"owner": "optional_address_the_new_token_will_be_minted_to",
"public_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"private_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"serial_number": {
"mint_run": 3,
"serial_number": 67,
"quantity_minted_this_run": 1000,
},
"royalty_info": {
"decimal_places_in_rates": 4,
"royalties": [
{
"recipient": "address_that_should_be_paid_this_royalty",
"rate": 100,
},
{
"...": "..."
}
],
},
"transferable": true | false,
"memo": "optional_memo_for_the_mint_tx",
"padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
}
}
Name
Type
Description
Optional
Value If Omitted
token_id
string
Identifier for the token to be minted
yes
minting order number
owner
string (HumanAddr)
Address of the owner of the minted token
yes
env.message.sender
public_metadata
The metadata that is publicly viewable
yes
nothing
private_metadata
The metadata that is viewable only by the token owner and addresses the owner has whitelisted
yes
nothing
serial_number
​SerialNumber​
The SerialNumber for this token
yes
nothing
royalty_info
​RoyaltyInfo​
RoyaltyInfo for this token
yes
default RoyaltyInfo
transferable
bool
True if the minted token should be transferable
yes
true
memo
string
memo for the mint tx that is only viewable by addresses involved in the mint (minter, owner)
yes
nothing
padding
string
An ignored string that can be used to maintain constant message length
yes
nothing
Setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale.
Response
{
"mint_nft": {
"token_id": "ID_of_minted_token",
}
}
The ID of the minted token should also be returned in a LogAttribute with the key minted.

BatchMintNft

BatchMintNft mints a list of tokens.
Request
{
"batch_mint_nft": {
"mints": [
{
"token_id": "optional_ID_of_new_token",
"owner": "optional_address_the_new_token_will_be_minted_to",
"public_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"private_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"serial_number": {
"mint_run": 3,
"serial_number": 67,
"quantity_minted_this_run": 1000,
},
"royalty_info": {
"decimal_places_in_rates": 4,
"royalties": [
{
"recipient": "address_that_should_be_paid_this_royalty",
"rate": 100,
},
{
"...": "..."
}
],
},
"transferable": true | false,
"memo": "optional_memo_for_the_mint_tx"
},
{
"...": "..."
}
],
"padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
}
}
Name
Type
Description
Optional
Value If Omitted
mints
array of Mint (see below)​
A list of all the mint operations to perform
no
​
padding
string
An ignored string that can be used to maintain constant message length
yes
nothing
Response
{
"batch_mint_nft": {
"token_ids": [
"IDs", "of", "tokens", "that", "were", "minted", "..."
]
}
}
The IDs of the minted tokens should also be returned in a LogAttribute with the key minted.

Mint

The Mint object defines the data necessary to mint one token.
{
"token_id": "optional_ID_of_new_token",
"owner": "optional_address_the_new_token_will_be_minted_to",
"public_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"private_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"serial_number": {
"mint_run": 3,
"serial_number": 67,
"quantity_minted_this_run": 1000,
},
"royalty_info": {
"decimal_places_in_rates": 4,
"royalties": [
{
"recipient": "address_that_should_be_paid_this_royalty",
"rate": 100,
},
{
"...": "..."
}
],
},
"transferable": true | false,
"memo": "optional_memo_for_the_mint_tx"
}
Name
Type
Description
Optional
Value If Omitted
token_id
string
Identifier for the token to be minted
yes
minting order number
owner
string (HumanAddr)
Address of the owner of the minted token
yes
env.message.sender
public_metadata
The metadata that is publicly viewable
yes
nothing
private_metadata
The metadata that is viewable only by the token owner and addresses the owner has whitelisted
yes
nothing
serial_number
​SerialNumber​
The SerialNumber for this token
yes
nothing
royalty_info
​RoyaltyInfo​
RoyaltyInfo for this token
yes
default RoyaltyInfo
transferable
bool
True if the minted token should be transferable
yes
true
memo
string
memo for the mint tx that is only viewable by addresses involved in the mint (minter, owner)
yes
nothing
Setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale.

Queries

NftDossier

SNIP-722 adds a transferable field to the NftDossier response of SNIP-721. NftDossier returns all the information about a token that the viewer is permitted to view. If no viewer is provided, NftDossier will only display the information that has been made public. The response may include the owner, the public metadata, the private metadata, the reason the private metadata is not viewable, the royalty information, the mint run information, whether the token is transferable, whether ownership is public, whether the private metadata is public, and (if the querier is the owner,) the approvals for this token as well as the inventory-wide approvals for the owner. The implementation may choose to hide royalty recipient addresses. See here for a description of how the reference implementation determines who is permitted to view royalty recipient addresses.
Request
{
"nft_dossier": {
"token_id": "ID_of_the_token_being_queried",
"viewer": {
"address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
"viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
},
"include_expired": true | false
}
}
Name
Type
Description
Optional
Value If Omitted
token_id
string
ID of the token being queried
no
​
viewer
​ViewerInfo​
The address and viewing key performing this query
yes
nothing
include_expired
bool
True if expired approvals should be included in the response
yes
false
Response
{
"nft_dossier": {
"owner": "address_of_the_token_owner",
"public_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"private_metadata": {
"token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
"extension": {
"...": "..."
}
},
"display_private_metadata_error": "optional_error_describing_why_private_metadata_is_not_viewable_if_applicable",
"royalty_info": {
"decimal_places_in_rates": 4,
"royalties": [
{
"recipient": "optional_address_that_should_be_paid_this_royalty",
"rate": 100,
},
{
"...": "..."
}
],
},
"mint_run_info": {
"collection_creator": "optional_address_that_instantiated_this_contract",
"token_creator": "optional_address_that_minted_this_token",
"time_of_minting": 999999,
"mint_run": 3,
"serial_number": 67,
"quantity_minted_this_run": 1000,
},
"transferable": true | false,
"owner_is_public": true | false,
"public_ownership_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"private_metadata_is_public": true | false,
"private_metadata_is_public_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"token_approvals": [
{
"address": "whitelisted_address",
"view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
},
{
"...": "..."
}
],
"inventory_approvals": [
{
"address": "whitelisted_address",
"view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
"transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
},
{
"...": "..."
}
]
}
}
Name
Type
Description
Optional
owner
string (HumanAddr)
Address of the token's owner
yes
public_metadata
The token's public metadata
yes
private_metadata
The token's private metadata
yes
display_private_metadata_error
string
If the private metadata is not displayed, the corresponding error message
yes
royalty_info
​RoyaltyInfo​
The token's RoyaltyInfo
yes
mint_run_info
​MintRunInfo​
The token's MintRunInfo
yes
transferable
bool
True if this token is transferable
no*
owner_is_public
bool
True if ownership is public for this token
no
public_ownership_expiration
​Expiration​
When public ownership expires for this token. Can be a blockheight, time, or never
yes
private_metadata_is_public
bool
True if private metadata is public for this token
no
private_metadata_is_public_expiration
​Expiration​
When public display of private metadata expires. Can be a blockheight, time, or never
yes
token_approvals
array of Snip721Approval​
List of approvals for this token
yes
inventory_approvals
array of Snip721Approval​
List of inventory-wide approvals for the token's owner
yes
The transferable field is mandatory for SNIP-722 compliant contracts, but because SNIP-722 is an optional extension to SNIP-721, any NftDossier response that does not include the field can be considered to come from a contract that only implements transferable tokens (considered equivalent to transferable = true)

IsTransferable

IsTransferable indicates whether the token is transferable. This query is not authenticated.
Request
{
"is_transferable": {
"token_id": "ID_of_the_token_whose_transferability_is_being_queried"
}
}
Name
Type
Description
Optional
Value If Omitted
token_id
string
The ID of the token whose transferability is being queried
no
​
Response
{
"is_transferable": {
"token_is_transferable": true | false
}
}
Name
Type
Description
Optional
token_is_transferable
bool
True if the token is transferable
no

ImplementsNonTransferableTokens

ImplementsNonTransferableTokens indicates whether the contract implements non-transferable tokens. Because legacy SNIP-721 contracts do not implement this query and do not implement non-transferable tokens, any use of this query should always check for an error response, and if the response is an error, it can be considered that the contract does not implement non-transferable tokens. Because message parsing ignores input fields that a contract does not expect, this query should be used before attempting to mint a non-transferable token. If the message is sent to a SNIP-721 contract that does not implement non-transferable tokens, the transferable field will just be ignored and the resulting NFT will still be created, but will always be transferable.
Request
{
"implements_non_transferable_tokens": {}
}
Response
{
"implements_non_transferable_tokens": {
"is_enabled": true | false
}
}
Name
Type
Description
Optional
is_enabled
bool
True if the contract implements non-transferable tokens
no