Skip to main content

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

  1. 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.
  2. Genesis Token Distribution and Minting

    • Configured to distribute NFTs during the genesis block initialization.
    • Supports future token minting by the System master admin.
  3. 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.
  4. 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).
  5. 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.
  6. 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.
  7. 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.
  8. 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 address is the unique address of the NFT.

  • To get an NFT by address, use

      GetNFT(ctx sdk.Context, address string)

    where address is 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.

  1. Retrieve NFTs: The function retrieves all NFTs.
  2. Check Vesting Period: It checks if the current block time is greater than or equal to the last vesting time plus the vesting period.
  3. Check Vesting Counter: It ensures that the vesting counter has not reached the maximum number of vesting periods.
  4. 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 Withdraw field.
  5. 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 {}