NFT module
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
The Owner struct matches the NFT holder (owner) address with the NFT address:
message Owner {
string address = 1;
string nft_address = 2;
}
There are several commands to update the owner store:
-
To add a new NFT owner, use
SetOwnerNFT(ctx sdk.Context, owner, nftAddress string)This function creates a new branch in the store where the key is the owner address, and the leaf in this branch is the Owner object.
-
To remove the NFT owner, use
RemoveOwnerNft(ctx sdk.Context, owner string, nftAddress string) -
To get all NFTs by owner address, use
GetAllNFTsByOwnerWithPagination(ctx sdk.Context, ownerAddress string, pagination *query.PageRequest) ([]types.NFT, *query.PageResponse, error) -
To get all addresses that hold any NFT
GetAllOwnersWithPagination(ctx sdk.Context, pagination *query.PageRequest) ([]string, *query.PageResponse, error)
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.
bridgeless-cored query nft --help
NFTs
CLI
The nfts command allow users to query all nfts
bridgeless-cored query nft nfts [flags]
Example:
bridgeless-cored query nft nfts
Example Output:
nft:
- address: bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37
available_to_withdraw:
amount: "0"
denom: abridge
denom: abridge
last_vesting_time: "1722244515"
owner: bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4
reward_per_period:
amount: "800000000000000000000"
denom: abridge
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
- address: bridge100rx5aqsunw3ta9nclsakkkj3t7sl5dzrza2gsw2n6hf62r2sswqvwcxa4
available_to_withdraw:
amount: "0"
denom: abridge
denom: abridge
last_vesting_time: "1722244515"
owner: bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4
reward_per_period:
amount: "800000000000000000000"
denom: abridge
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
HTTP
The nfts endpoint allow users to query all nfts
/cosmos/nfts
Example:
curl -X GET "https://rpc-api.node1.testnet.bridgeless.com/cosmos/nfts" -H "accept: application/json"
Example Output:
{
"nft": [
{
"address": "bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37",
"owner": "bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "abridge",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "abridge",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "abridge"
}
]
}
NFT
CLI
The nft command allow users to query the nft
bridgeless-cored query nft nft [flags]
Example:
bridgeless-cored query nft nft bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37
Example Output:
- address: bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37
available_to_withdraw:
amount: "0"
denom: abridge
denom: abridge
last_vesting_time: "1722244515"
owner: bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4
reward_per_period:
amount: "800000000000000000000"
denom: abridge
uri: ""
vesting_counter: "2"
vesting_period: "86400"
vesting_periods_count: "100"
HTTP
The nft endpoint allow users to query the nft
/cosmos/nfts/{address}
Example:
curl -X GET "https://rpc-api.node1.testnet.bridgeless.com/cosmos/nfts/bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37" -H "accept: application/json"
Example Output:
{
"nft": {
"address": "bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37",
"owner": "bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "abridge",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "abridge",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "abridge"
}
}
Owners
CLI
The owners command allow users to query the addresses of nft holdres
bridgeless-cored query nft owners [flags]
Example:
bridgeless-cored query nft owners
Example Output:
- owners: bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37
HTTP
The owners endpoint allow users to query the addresses of nft holdres
/cosmos/owners
Example:
curl -X GET "https://rpc-api.node1.testnet.bridgeless.com/cosmos/owners" -H "accept: application/json"
Example Output:
{
"owner": [
"bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4",
"bridge10awan8e059lkt7ejyu6qlkwwngs6yxxe4x5lhslpdvqlcmy3nrasd6zl5f"
],
"pagination": {
"next_key": null,
"total": "2"
}
}
Owner
CLI
The owner command allow users to query the nfts by holder address
bridgeless-cored query nft owners [flags]
Example:
bridgeless-cored query nft owners
Example Output:
- owners: bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37
HTTP
The owner endpoint allow users to query the nfts by holder address
/cosmos/nfts/{address}
Example:
curl -X GET "https://rpc-api.node1.testnet.bridgeless.com/cosmos/nfts/bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37" -H "accept: application/json"
Example Output:
{
"nft": {
"address": "bridge100mehmdwp8kwlj7aak450cu02u9d32cza95yrv8q776sytjfg3mstssj37",
"owner": "bridge103n4cmjt2je8nqcxg9y9desyhy6m57u52kkuc4",
"uri": "",
"vesting_period": "86400",
"reward_per_period": {
"denom": "abridge",
"amount": "800000000000000000000"
},
"vesting_periods_count": "100",
"available_to_withdraw": {
"denom": "abridge",
"amount": "0"
},
"last_vesting_time": "1722262887",
"vesting_counter": "2",
"denom": "abridge"
}
}
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 {}