Skip to main content
Version: Next

NFT

Module name and store key: nftrwa. Consensus version 1.

x/nft is an overlay on the stock cosmossdk.io/x/nft keeper, which is vendored under third_party/nft in the chain repository. The class and token records live in the upstream module's store; this module keeps a parallel metadata store and adds the rules the RWA use case needs: an issuer-scoped class id, a technical issuer per class, sequential token ids, immutable metadata, and a lock that other modules use to escrow a token.

Class ids are self-scoping​

{issuer}/{product_ref}

The first segment must be the signer's own bech32 address, so no account can create a class inside another account's namespace. Both segments are printable ASCII, the separator appears exactly once, and neither side may contain a /.

FieldRule
Class idPrintable ASCII, at most 256 bytes, exactly one /, both sides non-empty.
product_ref1 to 128 printable ASCII bytes, no /.
name1 to 128 characters.
symbolAt most 32 characters.
description, uriAt most 512 characters each.
uri_hashAt most 128 characters.

Class metadata​

ClassMeta {
class_id // the full {issuer}/{product_ref} string
issuer // the technical issuer for this class
restricted // transfers require a known counterparty
token_metadata_immutable // set once, at creation, never changed
}

issuer is the class's technical issuer: it can mint tokens, burn tokens it itself owns, set metadata, and answer the nftmarket class-configuration message. Only governance can move it, and only through MsgChangeClassIssuer.

restricted is read by tokenfactory's NFT send check rather than by this module. Under it, a transfer to an address that holds no token of that class is rejected unless the recipient is the class issuer or the module authority.

Token ids​

Token ids are assigned by the module, never chosen by the sender:

{class_id}/{sequence}

The sequence is a per-class counter that starts at 1 and only ever increases. The stored class id and the class id embedded in the token id always agree, and nftmarket re-checks that agreement when it validates a listing.

Messages​

MessageSignerRequirement
MsgCreateClasssenderNothing. The sender becomes the issuer. Charges class_creation_fee and consumes the class-creation gas.
MsgMintNFTsenderThe class issuer.
MsgBurnNFTsenderThe token's own owner. Since v1.1 neither the class issuer nor the module authority can burn a token that belongs to another holder — they are rejected with ErrHolderAssetProtection.
MsgSetNFTMetadatasenderThe class issuer, and the class must not be token_metadata_immutable.
MsgChangeClassIssuerauthorityThe module authority, which is the gov account by default.

There is no transfer message in this module. NFT transfers go through the upstream nft module's MsgSend, and this module decides whether they are allowed. Since v1.2, a holder can also transfer with a single-transaction MsgClaimSendNFT permit submitted by an executor; see Claim permits.

Metadata immutability is set once​

token_metadata_immutable is fixed when the class is created and no message changes it. When it is set, MsgSetNFTMetadata fails with ErrImmutableTokenMetadata — but only after the sender has been confirmed as the class issuer, so an unauthorized caller still gets ErrUnauthorized rather than learning the policy.

:::note MsgSetNFTMetadata emits no event The metadata update is applied and the message succeeds, but nothing is written to the event stream. A consumer that mirrors token state must re-query rather than wait for an event. :::

The lock​

A lock escrows a token without transferring it. The holder stays the owner; the token simply cannot be moved or burned by anyone else.

NFTLock { class_id, token_id, order_id, holder }
  • One lock per token. Locking a locked token returns ErrNFTLocked.
  • UnlockNFT is a no-op when no lock exists, and returns ErrLockOrderMismatch if the order id does not match. That lets each consumer release only its own lock.
  • Burning clears the lock first, so the owner's own burn always succeeds over a lock. Since v1.1 only the owner may burn, so a burn can no longer be driven by the issuer over the holder's lock.

Two modules used to take locks. Since v1.2 only nftmarket does: it escrows a listed token under the order id nftmarket/{listing_id}. A tokenfactory authorization no longer locks an NFT input. TX1 performs a read-only check that the token is not already locked by the market, and settlement fails closed if ownership or the lock changed. The claim permit path (MsgClaimSendNFT) re-runs the same checks.

Burn hooks​

The module keeps a named hook registry that runs on every burn, in sorted name order. The first error aborts the burn and rolls the transaction back.

HookRegistered byEffect
incomex/incomeReleases the token's earmarked claimable back into the shared yield pool.
nftmarketx/nftmarketRefunds the seller's listing deposit and deletes the listing.

Because the registry is sorted, income runs before nftmarket.

Queries​

QueryReturns
ParamsThe class creation fee and gas.
ClassIssuerThe ClassMeta for a class.
NFTLockWhether a token is locked, and by which order.
ClassesByIssuerPaginated classes for an address.
TokensByClassPaginated tokens in a class.
TokensByOwnerPaginated tokens held by an address.
ClassDetailThe class metadata, total supply, and a page of tokens.

ClassDetail also returns holder_count, which is deprecated and always zero.

Every query is also served over the gRPC gateway under /centerchain/nft/v1/.... A class id can contain /, so the gateway maps these queries to a fixed path and binds the identifiers as query parameters instead of path segments.

Events​

EventAttributes
class_createdclass_id, technical_issuer, uri_hash, token_metadata_immutable, fee
nft_mintedclass_id, token_id, owner, uri_hash
nft_burnedclass_id, token_id, owner
nft_lockedclass_id, token_id, order_id, holder
nft_unlockedclass_id, token_id
class_issuer_changedclass_id, old_issuer, new_issuer

Errors​

CodeIdentifierRaised when
1200ErrInvalidSignerA governance message was not signed by the authority.
1201ErrUnauthorizedThe sender is not the class issuer.
1202ErrInvalidClassIDThe class id is malformed or not self-scoping.
1203ErrClassExistsThe class already exists.
1204ErrClassNotFoundNo such class.
1205ErrInvalidTokenIDThe token id is malformed or belongs to another class.
1206ErrNFTNotFoundNo such token.
1207ErrNFTLockedThe token is locked.
1210ErrUnknownHolderA restricted transfer targeted a non-holder.
1211ErrInvalidAuthorityA governance message was signed by the wrong account.
1212ErrLockOrderMismatchThe unlock used a different order id.
1213ErrImmutableTokenMetadataThe class forbids metadata changes.
1214ErrHolderAssetProtectionA burn targeted a token the signer does not own. Added in v1.1.

CLI​

x/nftrwa exposes its queries and messages through autocli under the nftrwa prefix. The underlying cosmossdk.io/x/nft module keeps its own CLI surface under the separate nft prefix.

centerchaind query nftrwa params
centerchaind query nftrwa class-issuer [class-id]
centerchaind query nftrwa nft-lock [class-id] [token-id]
centerchaind query nftrwa classes-by-issuer [issuer]
centerchaind query nftrwa tokens-by-class [class-id]
centerchaind query nftrwa tokens-by-owner [owner]
centerchaind query nftrwa class-detail [class-id]

centerchaind tx nftrwa create-class [class-id] [name] [symbol]
centerchaind tx nftrwa mint [class-id] [to]
centerchaind tx nftrwa burn [class-id] [token-id]
centerchaind tx nftrwa set-nft-metadata [class-id] [token-id]
centerchaind tx nftrwa change-class-issuer [class-id] [new-issuer]