ItemUsages

An Item can be used as either a Consumable or Wearable. In fact, an Item can have many ItemUsages describing the different ways in which it may be used. For example, it may be that a legendary sword could both be worn and used in a spell - these are two different ItemUsages in the array.

Both ItemUsages can have BasicItemEffects, which look like this:

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct BasicItemEffect {
    amount: u64,
    stat: String,
    item_effect_type: BasicItemEffectType,
    active_duration: Option<u64>,
    staking_amount_numerator: Option<u64>,
    staking_amount_divisor: Option<u64>,
    staking_duration_numerator: Option<u64>,
    staking_duration_divisor: Option<u64>,
    // point where this effect no longer applies
    max_uses: Option<u64>,
}

The primary difference is in the ItemClassType enum of your ItemUsage, which allows for different data storage depending on whether this item is being worn or consumed:

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum ItemClassType {
    Wearable {
        body_part: Vec<String>,
        limit_per_part: Option<u64>,
    },
    Consumable {
        max_uses: Option<u64>,
        // If none, is assumed to be 1 (to save space)
        max_players_per_use: Option<u64>,
        item_usage_type: ItemUsageType,
        cooldown_duration: Option<u64>,
        warmup_duration: Option<u64>,
    },
}

The Wearable enum is focused on scoping and limiting on a per-body-part basis, which is similar to the component scoping used by the Components array. It is up to the enclosing contract to enforce this.

The Consumable enum revolves around setting usage limits, how many players can use it in a given use, warmups and cooldowns.

You may have noticed that both an BasicItemEffects, and the ItemUsage itself, both can have max_uses. This is so that you can have different effects wear down before the item as a whole does.

There are a few ItemUsageTypes you can use:

#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum ItemUsageType {
    Exhaustion,
    Destruction,
    Infinite,
}

Please note that Exhaustion is not an allowable ItemUsageType for SFTs, nor is max_uses > 1.

Exhaustion means an Item will not be destroyed once max_uses hits zero (and max_uses is required for Exhaustion), but it will be rendered unusable for this particular ItemUsage.

Destruction means when max_uses hit zero (and it must be set for Destruction) will cause a token to be burned. For SFTs, with their required max_uses == 1, this means one token is burned per use.

Infinite is self-explanatory - max_uses is ignored or unset, and the item can be used forever. Not applicable to SFTs.

When you define an ItemUsage, it gets a corresponding ItemUsageState in any Item instance to keep track of usage, and cooldowns:

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct ItemUsageState {
    index: u16,
    uses: u64,
    activated_at: Option<u64>,
}

Obviously SFTs cannot have such state as all tokens in the SFT mint share the same Item PDA, which is why there are restrictions on them.

Callbacks and Validation

Just a side note, each ItemUsage can have its own validation and callback (both of type Callback). Validations are CPIs that are called when the item is being activated, whereas callbacks are intended to be CPI’d from the enclosing contract when it is enacting the Item’s changes on its target(s). Here’s what a Callback looks like:

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Callback {
    pub key: Pubkey,
    pub code: u64,
}

An example validation that will work on prod is a dummy endpoint added to the stubbed out Namespace contract. Here’s an example ItemUsage from an ItemClass definition JSON used for create_item_class calls that will make this work:

{
          "index": 0,
          "basicItemEffects": null,
          "callback": null,
           "validation": {
            "key": "nameAxQRRBnd4kLfsVoZBBXfrByZdZTkh8mULLxLyqV",
            "code": 35
          },
          "usagePermissiveness": [{ "updateAuthority": true }],
          "inherited": { "notInherited": true },
          "itemClassType": {
            "consumable": {
              "maxUses": null,
              "maxPlayersPerUse": null,
              "itemUsageType": { "infinite": true },
              "cooldownDuration": 500,
              "warmupDuration": null
            }
          }
        }

Feel free to experiment with this. The additional code is a u64 that is passed in just after the 8 bytes describing the instruction so it can be used for additional filtering. The contract implementing this must have a method called item_validation in its main module and must be an Anchor contract.

Last updated