Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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 ItemUsage
s in the array.
Both ItemUsage
s can have BasicItemEffect
s, which look like this:
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:
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 BasicItemEffect
s, 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:
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:
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.
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:
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:
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.
Presently the Item contract is configured to allow for storing proof-of-data through the use of Merkle trees for ItemUsages, ItemUsageState, and Components. While all the actions and structs for using these roots and proofs are present, none of them have been tested and the CLI lacks support for these. Please avoid using this functionality and store all data on chain until this has been properly tested and debugged.
An item in Raindrops is an artifact that has only limited state, mostly for tracking how many times it has been used, the last time it was used, cooldowns, etc. Items are primarily Consumable or Wearable, or both, and the data they contain describes the effect they should have on whatever is using them, as implemented by an enclosing contract.
The ItemClass can be separated into three clear sets of definitions:
Permissions for different functionalities (build/update/staking)
How an Item instance may be used (consumable/wearable, how many times, etc)
What component Items are required (if any) to build an Item instance of this ItemClass.
The Item contract can be separated into three clear functionalities:
Item Class Definitions
Item Recipe Creation
Item Activation (preparing and validating item for use by enclosing contract)
We’ll cover the state diagram and the underlying data structures first to get you warmed up to the concepts, then cover the functionalities.
All components must be ItemClasses. Any Item that inherits from those classes directly can be used as this component. Adding support for a top-level ItemClass component that is more than one level up from an Item is on the cards in a future release but was deemed too complex for this iteration.
Each Component requires an amount of that type, and a condition. The allowable conditions are:
When Cooldown or CooldownAndConsume are used, the use_usage_index field is looked at to figure out what ItemUsageState to check in the given item’s usage state array to use when looking to see if the Item is on cooldown or not. In this way you can require a user to first bring an item to cooldown through a use before allowing it to be used as a crafting component.
When Consume or CooldownAndConsume are used, the item is destroyed if used as a component here.
Time_to_build should be the same across all components in a component_scope, and will be used (in seconds) to determine how long to force the user to wait once they enter the build phase for creating an item. A component_scope is an arbitrary grouping of components so that multiple component recipes can be stored in the same array without nesting.
This is a pseudo-diagram of the state defining an ItemClass, with all structs merged together:
As you can see, an ItemClass is made up of its recipe (the Components required to create it), and the ways in which it may be used (the ItemUsage), as well as various permissiveness settings for different kinds of actions. Let’s cover them one at a time.
Okay so you’ve defined your ItemClass
, instantiated an Item
, and now you want to use it. What does that mean? Well, with composability as a central tenet of this protocol, it means that the Item contract should only be responsible for validating that it is possible to use an Item
, and then provide proof that you have decided to use the Item
. It is then the responsibility of the enclosing contract to call the Item contract back to let it know that the Item
has been used, and the activation period is over. Given the “price” of activation is paid up front, not calling back the Item contract simply means you cannot use this Item
again until you do.
To activate an Item
, you issue a begin_item_activation
call with a JSON file like so:
itemMarkerSpace must have a value in the range of 20 to 66, inclusive
This creates an ItemActivationMarker
that acts as a kind of ticket that other programs can use as proof of use:
Note that if you used a validation
key in the ItemUsage
being provided, it will be called during this begin_item_activation
stage. This allows you to hook into item use with custom feedback. It is up to your enclosing program to call the callback
after the enactment. What accounts (writable or otherwise) that you send in a callback is up to you - validation has a preset format that must be followed.
You can provide this PDA address to any enclosing program and they can validate that the Item
is valid for use (i.e. past it’s warmup state if one is specified) and how many of that item is being used (mostly going to be 1 but if it’s an SFT it could potentially be many).
Once your enclosing contract has “enacted” the changes the Item
’s ItemClass
describes, it can make a CPI call to end_item_activation
, or you can use the CLI:
If your Item
contains a warmup period, you may need to use this additional command after beginning item activation to push it into “valid_for_use” after the warmup duration:
All Items start as bare-bones NFTs or SFTs that were previously made before starting the build process. You can either attempt to make them in the same transaction or bring them in from a prior. The Raindrops contracts expect them to pre-exist.
As a reminder, an NFT is a mint with supply = 1, decimals = 0, and both a Metadata and Edition or MasterEdition struct from the Token Metadata program.
An SFT is a mint with supply > 0, decimals = 0, and ONLY a Metadata struct from the Token Metadata program. Ideal uses for SFTs are single-use items like Potions so that you do not need to create a new mint for each potion. SFT-based items cannot have stateful usage types, such as a cooldown period.
To build an Item
from an ItemClass
:
Create an Item Escrow in which to store components from a specific component scope (recipe) for a specific index.
Add or burn or prove absence of required components against the escrow
Start the build phase
If there is no build time, building can be ended immediately. Otherwise, it must be ended once the build time duration has passed.
Now the NFT or SFT provided has a new Item
PDA with seeds [’item’, mint_id, index]
, where index is some u64
> 0 that corresponds to the index used to initialize the Item escrow.
Presently the CLI has the low-level commands for #1-5 but does not have a higher level smart command that can produce all these commands in aggregated transactions. We will be adding a single CLI build command where recipes are read and transactions are built to minimize overhead across all steps.
Let’s walk through how to create an item using the low-level commands. First, you’ll need a JSON file describing what you are trying to do:
You’ll need to give the mints for both the new Item
, and the existing ItemClass
. You’ll need to indicate to the ItemClass
which build permissiveness you want to use as this will determine what accounts the CLI will be passing up to authenticate you.
For every Item
or ItemClass
, it has a specific index offset against the mint to allow the creation of multiple classes and instances from the same mint, and you’ll need to indicate which you want to use for this creation.
The craftEscrowIndex
is used to allow you to have multiple builds progressing for the same ItemClass
against the same mint should you need it.
Components in the recipe list each come tagged with a componentScope
. When you choose a specific scope here, you are required only to provide components that have that tag. Imagine each scope as a different recipe to make the same Item.
If your ItemClass
has itself a parent ItemClass
, the parent
field will need to be set. If not, it can be null.
originator
is an optional field that should be used if those adding components to the escrow will not be the same signer as the one who starts the escrow.
When creating Item
s or ItemClass
es, you get to specify how much space you want to spend. In Solana 1.8 this space is fixed, so think carefully about how much data you plan to store on chain for this Item
before setting this. Depending on ItemUsageState
s, Item
s can be quite inexpensive.
The components
list is a list of actual Item
mints (and their index
offsets) you plan to use when assembling the component
s. This is as opposed to the component
list on the ItemClass
struct itself, which describes what ItemClass
es must be present. This list lets you choose which instance of a given ItemClass
you will use in this build.
Once this is setup, here are the commands, in sequence, that must be run for a single-component recipe:
Some things to note here: the -i
when adding and removing dictate what index
in the component
list you are trying to add (you should always start with 0 and move in order) and the -a
indicates how many of that item you are adding (most of the time, 1.)
If your recipe has a build time, complete_item_escrow_build_phase
will error out until that duration has passed. It is also important, but not required, to remember to run remove_craft_item_from_escrow
even if the Item
was destroyed as part of the crafting, as you will at the very least reclaim lamports for the token account on the escrow side.
The new Item
PDA is setup during the complete_item_escrow_build_phase
step. At this point, the item is now usable.
Want to see what you built? Run this:
You’ll need to supply the mint
and index
offset.
Or from the client side:
Want to cut out before you finish crafting? Simply deactivate and reclaim lamports:
To define a class, you’ll need to start with a JSON file:
Now there’s a lot to unpack here, so let’s go step by step.
You’ll also notice two special options which are optional Booleans (a boolean + an inheritance marker), free_build
and children_must_be_editions
. For a traditional ItemClass
, a lack of Components
means that it cannot be built. Setting free_build
to true
indicates that rather than indicating the ItemClass
cannot be used to build an NFT into an Item
, it can be done for free (no components required). children_must_be_editions
is fairly straightforward - all Item
s created from this ItemClass
must be a limited edition of the ItemClass
’s underlying NFT. This requirement is not enforced for child ItemClass
es.
Whenever you are using the JSON files to define Permissivenesses, you’ll notice notInherited
is exclusively used. This is by design. Overridden and Inherited values are provided by the program when class propagation occurs.
The config part of the JSON file covers how an item may be used, and what components are required to build it. You’ll notice that usages are required to have indexes that match up with their position in the array - this is for later use in item activation. You’ll also see that you can configure the way your item is used to have different permissions depending on the type of use.
The top-level of the JSON file lets the CLI know what permissions to use to define or update the class, how much space should be used for the ItemClass
struct, and whether or not you wish to store the metadata and mint fields on the ItemClass
for reverse-lookups.
Once you have this file, you can create an ItemClass
on top of an existing NFT (no SFTs allowed, sorry) like so:
Updating it is just as easy:
Remember, if you want to propagate updates from a parent ItemClass
to your ItemClass
, you must start at the highest parent that has changed, and issue updates in sequence from parent to child via the update_item_class
command until all updates are propagated. If you wish to do this permissionlessly, use the -iu
flag with update_item_class
.
Want to see what you made? Run show_item_class
!
Or from the client side, fetch an item class like this:
Your settings
contains mostly permissions arrays that use the PermissivenessType
described in to tell the ItemClass
who is allowed to do which things. Examples include who can stake, who can unstake, who can build, and who can update.