NFT module
These data structures describe module capabilities, not an announcement of user-facing NFT staking or rewards. Release availability and participation rules must be confirmed separately; see planned staking and governance.
The NFT module provides an opportunity to manage non-fungible tokens (NFTs) with token-owned native balance, incorporating features such as delegation, withdrawal, and delegation reward collection. The module is designed to ensure secure and efficient handling of tokens, with a focus on locking mechanisms, linear unlock periods, and adherence to treasury constraints.
Key Features and Functionalities
-
NFT-Based Token Management
- Implements methods for handling non-fungible tokens.
- Each token is associated with independent balances, comprising:
- Locked Tokens: Tokens subject to a specified unlock period.
- Available Tokens: Tokens eligible for immediate withdrawal.
-
Genesis Token Distribution and Minting
- Configured to distribute NFTs during the genesis block initialization.
- Supports future token minting by the System master admin.
-
Token Characteristics
- Each token includes the following attributes:
- Owner: Designated account managing the token.
- Metadata URL: URL containing descriptive or associated data.
- Locked Tokens: Number of tokens locked within the NFT.
- Unlock Period: Period during which locked tokens unlock linearly over time.
- Each token includes the following attributes:
-
Delegation and Undelegation
- Tokens can be delegated or undelegated using built-in methods.
- Delegation involves the entire balance of a token and is controlled by the token owner.
- Rewards generated by delegated tokens are available for immediate withdrawal by the token owner.
- Rewards from NFT delegation are higher than the same rewards in the raw token (configured in distribution module).
-
Token Withdrawal
- Owners can withdraw unlocked tokens directly from the token balance.
- Withdrawal operations transfer tokens from the NFT shard treasury to the owner’s account.
-
Ownership and Transfer Restrictions
- Delegated tokens cannot be transferred to a new owner while in a delegated state.
- Ownership transfer is restricted to ensure delegation integrity.
-
Treasury Constraints
- The locked amount within minted NFTs cannot exceed the total available balance in the NFT shard treasury.
- The NFT shard treasury tokens are accessible only through withdrawals of unlocked tokens. No alternative access mechanisms are allowed.
-
Token Deposit
- Owners can deposit additional tokens directly to the token balance (by locking them for the corresponding period).
- The deposited amount must be a multiple of the period payment.
Architectural Perspective
Each NFT represents an independent balance system, ensuring clear segregation of:
- Locked tokens that are subject to unlock conditions.
- Tokens available for withdrawal, which can be freely used by the owner.
This architecture maintains transparency and ensures secure management of tokens within the system.
State
Each NFT is represented in the blockchain by a unique address. This address is used to store a balance and to stake this balance to a validator. To generate an NFT address, the following code snippet can be used:
sdk.Bech32ifyAddressBytes("bridge", address.Derive(authtypes.NewModuleAddress(acumulatortypes.ModuleName), []byte(strconv.FormatInt(id, 10))))
The NFT object contains specific information to describe a token:
message NFT {
string address = 1;
string owner = 2;
string uri = 3;
int64 vesting_period = 4;
cosmos.base.v1beta1.Coin reward_per_period = 5 [(gogoproto.nullable) = false];
int64 vesting_periods_count = 6;
cosmos.base.v1beta1.Coin available_to_withdraw = 7 [(gogoproto.nullable) = false];
int64 last_vesting_time = 8;
int64 vesting_counter = 9;
string denom = 10;
}
There are several commands to update the NFT store:
-
To add a new NFT or update an existing NFT, use
SetNFT(ctx sdk.Context, v types.NFT) -
To remove an NFT, use
RemoveNFT(ctx sdk.Context, address string)where
addressis the unique address of the NFT. -
To get an NFT by address, use
GetNFT(ctx sdk.Context, address string)where
addressis the unique address of the NFT. -
To get all NFTs, there are two methods (with and without pagination):
-
GetNFTsWithPagination(ctx sdk.Context, pagination *query.PageRequest) ([]types.NFT, *query.PageResponse, error) -
GetNFTs(ctx sdk.Context) (list []types.NFT)
-
Owner
Query the NFTs held by one owner address. This differs from owners, which lists holder addresses.
CLI
zel-cored query nft owner <owner-address> [flags]
Example:
zel-cored query nft owner zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m
Example response for an owner with no NFTs:
nft: []
pagination:
next_key: null
total: "0"
HTTP
/cosmos/nft/owners/{owner}/nfts
Example:
curl -X GET "https://rpc-api.node1.testnet.zano.org/cosmos/nft/owners/zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m/nfts" -H "accept: application/json"
The response contains an nft array and pagination, not a single NFT object. For an owner with no NFTs:
{
"nft": [],
"pagination": {
"next_key": null,
"total": "0"
}
}
EndBlock
The EndBlocker function is responsible for updating the vesting state of each NFT at the end of each block. This
ensures that NFT owners can only withdraw the available amount as per the vesting schedule.
Each NFT contains vesting information structured as follows:
- Vesting Period: The period (in seconds) between each vesting event.
- Reward Per Period: The amount of reward to be vested per period.
- Vesting Periods Count: The total number of vesting periods.
- Available to Withdraw: The amount available for withdrawal.
- Last Vesting Time: The timestamp of the last vesting event.
- Vesting Counter: The current count of how many periods have vested.
EndBlocker flow
The EndBlocker function updates the vesting state for each NFT, ensuring that the available amount to withdraw is
updated based on the vesting schedule.
- Retrieve NFTs: The function retrieves all NFTs.
- Check Vesting Period: It checks if the current block time is greater than or equal to the last vesting time plus the vesting period.
- Check Vesting Counter: It ensures that the vesting counter has not reached the maximum number of vesting periods.
- Update Available Amount: If both conditions are met, it calculates the new available amount to withdraw based on
the reward per period and updates the
Available to Withdrawfield. - Increment Counter: It increments the vesting counter and updates the last vesting time.
Query Client
A user can query and interact with the nft module using the CLI.
The query commands allow users to query nft state.
zel-cored query nft --help
NFTs
CLI
The nfts command allow users to query all nfts
zel-cored query nft nfts [flags]
Example:
zel-cored query nft nfts
Example Output:
nft:
- address: zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe
available_to_withdraw:
amount: "0"
denom: uzel
denom: uzel
last_vesting_time: "1722244515"
owner: zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m
reward_per_period:
amount: "800000000000000000000"
denom: uzel
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
- address: zel100rx5aqsunw3ta9nclsakkkj3t7sl5dzrza2gsw2n6hf62r2sswqdfesmj
available_to_withdraw:
amount: "0"
denom: uzel
denom: uzel
last_vesting_time: "1722244515"
owner: zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m
reward_per_period:
amount: "800000000000000000000"
denom: uzel
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
HTTP
The nfts endpoint allow users to query all nfts
/cosmos/nft/nfts
Example:
curl -X GET "https://rpc-api.node1.testnet.zano.org/cosmos/nft/nfts" -H "accept: application/json"
Example Output:
{
"nft": [
{
"address": "zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe",
"owner": "zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "uzel",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "uzel",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "uzel"
}
]
}
NFT
CLI
The nft command allow users to query the nft
zel-cored query nft nft [flags]
Example:
zel-cored query nft nft zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe
Example Output:
- address: zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe
available_to_withdraw:
amount: "0"
denom: uzel
denom: uzel
last_vesting_time: "1722244515"
owner: zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m
reward_per_period:
amount: "800000000000000000000"
denom: uzel
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
HTTP
The nft endpoint allow users to query the nft
/cosmos/nft/nft/{address}
Example:
curl -X GET "https://rpc-api.node1.testnet.zano.org/cosmos/nft/nft/zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe" -H "accept: application/json"
Example Output:
{
"nft": {
"address": "zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe",
"owner": "zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "uzel",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "uzel",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "uzel"
}
}
Owners
CLI
The owners command allow users to query the addresses of nft holdres
zel-cored query nft owners [flags]
Example:
zel-cored query nft owners
Example Output:
- owners: zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe
HTTP
The owners endpoint allow users to query the addresses of nft holdres
/cosmos/nft/owners
Example:
curl -X GET "https://rpc-api.node1.testnet.zano.org/cosmos/nft/owners" -H "accept: application/json"
Example Output:
{
"owner": [
"zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m",
"zel10awan8e059lkt7ejyu6qlkwwngs6yxxe4x5lhslpdvqlcmy3nrasvarfjw"
],
"pagination": {
"next_key": null,
"total": "2"
}
}
Owner
CLI
The owner command allow users to query the nfts by holder address
zel-cored query nft owners [flags]
Example:
zel-cored query nft owners
Example Output:
- owners: zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe
HTTP
The owner endpoint allow users to query the nfts by holder address
/cosmos/nft/nft/{address}
Example:
curl -X GET "https://rpc-api.node1.testnet.zano.org/cosmos/nft/nft/zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe" -H "accept: application/json"
Example Output:
{
"nft": {
"address": "zel100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3ms2h3yhe",
"owner": "zel103n4cmjt2je8nqcxg9y9desyhy6m57u5nlve4m",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "uzel",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "uzel",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "uzel"
}
}
Messages
Send
This message is used to change a nft owner
message MsgSend {
string creator = 1;
string address = 2;
string recipient = 3;
cosmos.base.v1beta1.Coin amount = 5 [(gogoproto.nullable) = false];
}
message MsgSendResponse {}
Withdrawal
This message is used to withdraw available(unlocked) nft amount
message MsgWithdrawal {
string creator = 1;
string address = 2;
cosmos.base.v1beta1.Coin amount = 5 [(gogoproto.nullable) = false];
}
message MsgWithdrawalResponse {}
Delegate
This message is used to stake tokens from nft balance to a validator
message MsgDelegate {
string creator = 1;
string address = 2;
string validator = 3;
cosmos.base.v1beta1.Coin amount = 5 [(gogoproto.nullable) = false];
}
message MsgDelegateResponse {}
Undelegate
This message is used to unstake tokens from nft balance to a validator
message MsgUndelegate {
string creator = 1;
string address = 2;
string validator = 3;
cosmos.base.v1beta1.Coin amount = 5 [(gogoproto.nullable) = false];
}
message MsgUndelegateResponse {}