Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
There are several core foundational concepts that are used in some or across all of the Raindrops contracts. This section explains those concepts in detail. You can read them as you run across one you don't understand or read them all at once before digging into any specific part of the protocol.
To keep gaming inexpensive, only stateful Items
should be NFTs.
Non-stateful Items
, like single-use potions, should be Semi-Fungible Tokens (supply > 0, decimals = 0, this requires only a metadata struct from the Metaplex Token Metadata Program with no edition struct).
In this way, all users share potions from the same mint, without needing to create new mints for each potion. There are certain limitations on SFTs being used, but the trade-offs in cost are extraordinary.
What this means is that you only need a single NFT defining the ItemClass
for a given SFT item, and then a second mint of semi-fungibles representing the supply that you sell or give to users. It is on top of this mint that you define theItem
. As long as you meet the rules of SFT Items
, you've now got a very efficient gameplay item.
A “inherited field” has the following structure:
Properties/fields that can be inherited in an ItemClass
that come from it’s parent classes are marked appropriately if they came from it’s own definition(NotInherited), came from it’s parent definition(Inherited), or the parent class has permissions to make it illegal for any sub-definitions to be made on the given class(Overridden).
An example of Overridden
would be the parent class saying that the child class cannot override the build permissiveness settings. The build permissiveness array, which can host a variety of different permissions types, must be a 1:1 match with the parent’s copy of it.
When an ItemClass
inherits from another ItemClass
, it can be unknown or undefined how any changes to the parent ItemClass
should be propagated to the child ItemClass
.
Raindrops introduces the concept of permission-less updates(--inheritance-update
) to handle this situation.
This tells the endpoint not to accept any changes to a child ItemClass
except for those also present on the parent ItemClass
. This means anybody can enforce inheritance at any time across any size tree of inheritance.
This updates the Item instance with the new ItemUsageStates
that may arise from changes in ItemUsage
s in the parent class.
Similar logic exists for the Player contract, but instead of using --inheritance-update flag, simply use a null updatePermissivenessToUse inside the config JSON file (the -cp flag entry).
Throughout the contracts, with every mint there is often an associated index which is an u64
. The reason for this is that while you may have a single NFT, you may wish to use it across multiple games. These different games may not wish to share a Player
stat sheet or an Item
definition. This is why all NFTs can have multiple Items
and Players
defined on top of them using the index as part of the seed. This index allows a single NFT can be used in different ways by different games.
Another common use-case might be to define index 0 of an NFT as an ItemClass
, and index 1 as the Item
for a true 1/1 Item
so that you don’t need to have two NFTs to define a single legendary sword. Same could be done with a PlayerClass
/Player
.
It should be noted that if one Item
PDA describes a Consumable
that destroys the NFT, and you use the NFT in this way, it will be destroyed, regardless of its other definitions.
Permissions can be propagated from a parent class to a child class in either a fashion that overwrites the child array or is additive.
To specify which permissions get propagated, use the child_update_propagation_permissiveness
array on the Player
or Item
classes.
The available options are:
If you change this setting on an ItemClass
, a permission-less update_item_class
call can be made to the Item
contract to enforce this down the ItemClass
's inheritance tree. The same goes for PlayerClasses
and Players.
All Raindrops artifacts that can be cached or assigned to a namespace have an identical first few fields.
Examples:
Note that even Namespace
has a namespaces section that can be used to join it to other namespaces, making it composable. Any artifact can join a namespace if it has this optional array up front with available slots (defined and set on creation).
Joining a namespace is done through the namespace contract.
It is not required to have a namespace for your raindrops artifacts. However, if you use namespaces, your artifacts become eligible to use staking and you can charge entry fees in $RAIN to join those namespaces.
Multiple inheritance is explicitly not supported to keep things simple, except in , where a single Namespace
can be part of many other parent Namespaces
. This is much more similar to a Mixin however, as namespaces do not inherit from a parent.
It is a pain to have to redefine the same stats for different player or item NFTs across many unique NFT mints, as well as costly. The Raindrops Protocol avoids this by taking the stance that object inheritance is useful, and an object class should be it’s own NFT. Just as an NFT is simply two structs from the Token Metadata program that live on top of a token (specifically the Metadata
and Master Edition
structs), Raindrops adds additional structs on top of that NFT depending if that NFT represents a Class or an Instance of some Class.
One object class NFT can inherit from another class NFT, and all existing object instances should inherit from an existing object class NFT.
Both the Player and Item contract behave this way, with PlayerClass
structs and ItemClass
storing configurational information about what makes up this kind of Player
or Item
(like BodyParts
, BasicStats
like strength, or what BasicItemEffects
an item should have), and the actual Player
and Item
instance structs are slimmed down data that only contain stateful information specific to that given NFT, like whether the strength is 5, or if the item was used 5 minutes ago.
For an example of this, here is an ItemUsage
(Configurational data from an ItemClass
) and an ItemUsageState
(Stateful information from an Item
that is a child of this class):
An ItemUsage
will have a has_many relationship with an ItemClass
and is embedded within the ItemClass
.
ItemUsage
defines how a given Item
may be used, and an Item
can be used in many ways. However, the ItemClass
doesn’t actually get used - it is a blueprint for creating Item
instances. When made, Items
don’t all contain this massive struct in their PDAs, they instead contain a trimmed down ItemUsageState
for the ItemUsage
defined within their parent ItemClass
class. This keeps the Items
and Players
relatively small as PDAs.
There are four different permissions types supported by the five contracts. When speaking here we'll be using the Item
contract for examples, but this works equally well for the other contracts.
TokenHolder
ParentTokenHolder
UpdateAuthority
Anybody
In order to do the action, the token on which the action is being taken must be held by the signer of the transaction.
In order to do the action, the token on which the action is being taken must already have a predefined struct (like an ItemClass
or an Item
) and that struct must have its parent
field set to a valid pubkey. That pubkey will point to the ItemClass
of another NFT. The signer calling this action must be holding the NFT of that parent
ItemClass
.
In order to do the action, the update authority of the Metadata
of the token (as defined by the Token Metadata program) must be the signer on the transaction.
Anybody can do this action. There are no requirements to be met.
These permissiveness types are used to gate every kind of activity there is to do on any object across many of the contracts. In the item contract, for instance, you can set who is allowed to build an instance of an ItemClass
, who can update it, who can stake against it, etc.
Here is an example of an ItemClassSettings
struct that has several fields/properties that use these permissions:
When performing an action against a Raindrops contract, you must specify what type of permissiveness you want to use to do that action and that permissiveness will be checked against the existing array of permissions for the object being manipulated with that contract's endpoint.
The CLI will also use the permissiveness chosen to determine what additional accounts that might be needed to send in the instruction call to prove validity.
Here’s an example configuration file for opening an item escrow, which is the first action one does to create an Item
from an ItemClass
using the Item contract:
Notice that buildPermissivenessToUse
is set to tokenHolder. This means that the ItemClass
allows anybody holding the ItemClass
NFT to use the ItemClass
recipe to turn a non-raindrops NFT into an Item
instance of this ItemClass
. For instance, if you have an NFT that looks like a potion that you just made on a minting website, that does not mean it is an actual Potion that is used during play by Game X. If you hold the NFT representing the ItemClass
for Potion, then you have the right to go through the creation process to turn this freshly minted NFT potion look-alike into an actual Potion that your game client can process during gameplay. At the end of this creation cycle you’ll have an Item PDA seeded to your potion NFT that allows it to be used within the game.