Resources
Pre-requisites
Please make sure that you've followed our getting started guide and set up your development environment.
You'll also need to have a project set up in Honeycomb Protocol. If you haven't done that yet, you can create one using instructions here.
Overview
Resources and crafting form the backbone of any app or game that has an inventory system. It allows users to create new items by combining existing ones.
Honeycomb Protocol lets developers create crafting resources and recipes for their projects. These recipes can be as simple as combining two resources to create a new one, or as complex as having to craft multiple items in a specific order to create a new item.
There are two types of resources you can create in Honeycomb Protocol:
- Fungible Resources: These are resources that can be divided into smaller units. For example, a fungible resource could be gold coins, which can be divided into smaller units like 1 gold coin, 0.5 gold coins, etc. The supply of these resources is unlimited. Similarly, currencies like SOL, USDT etc.
- Non-Fungible Resources: These are resources that cannot be divided into smaller units. For example, a non-fungible resource could be a gold bar, which cannot be divided into smaller units. The supply of these resources is limited and developers have to define the total supply when creating the resource.
Crafting resources
Creating a resource
- JavaScript
- graphql
const {
createCreateNewResourceTransaction: {
resource: resourceAddress, // This is the resource address once it'll be created
group: resourceGroup, // This is the resource group, can be null if the resource is fungible, in case of non-fungible resources, this instruction will create a resource group and return its PDA address (save the group address in your database along with the actual resource address)
tx: txResponse, // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
},
} = await client.createCreateNewResourceTransaction({
project: projectAddress,
authority: adminPublicKey.toString(),
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional, resource delegate authority public key
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
params: {
metadata: {
name: "Resource",
symbol: "RSC",
uri: "https://example.com",
},
// Use either fungible, to create a fungible resource, or inf, to create a non-fungible resource
fungible: {
decimals: 9,
},
inf: {
characteristics: [],
supply: 1000000000,
}
}
});
query CreateCreateNewResourceTransaction($project: String!, $authority: String!, $params: InitResourceInput!, $delegateAuthority: String, $payer: String) {
createCreateNewResourceTransaction(project: $project, authority: $authority, params: $params, delegateAuthority: $delegateAuthority, payer: $payer) {
tx {
transaction
blockhash
lastValidBlockHeight
}
resource
group
}
}
Provide the data like this:
{
"project": "pubkey", // Project public key as a string
"authority": "pubkey", // Project authority public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"params": {
"metadata": {
"uri": "string", // URI of the resource
"symbol": "string", // Symbol of the resource
"name": "string" // Name of the resource
},
// Use either fungible, to create a fungible resource, or inf, to create a non-fungible resource
"fungible": {
"decimals": int // Number of decimals as an integer
},
"inf": {
"characteristics": ["string"], // Characteristics as a string array
"supply": int // Total supply of the resource
}
}
}
Creating a resource tree
With each new resource, you need a resource tree to store ownership details. This merkle tree store and verify the ownership of resources.
- JavaScript
- GraphQL
const {
createCreateNewResourceTreeTransaction: {
tree: merkleTreeAddress, // This is the merkle tree address once it'll be created
tx, // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
},
} = await client.createCreateNewResourceTreeTransaction({
project: projectAddress.toString(),
authority: adminPublicKey.toString(),
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
resource: resourceAddress.toString(),
treeConfig: {
// Provide either the basic or advanced configuration, we recommend using the basic configuration if you don't know the exact values of maxDepth, maxBufferSize, and canopyDepth (the basic configuration will automatically configure these values for you)
basic: {
numAssets: 100000, // The desired number of resources this tree will be able to store
},
advanced: {
maxDepth: 20,
maxBufferSize: 64,
canopyDepth: 14,
}
}
});
query CreateCreateNewResourceTreeTransaction($project: String!, $resource: String!, $authority: String!, $treeConfig: TreeSetupConfig!, $delegateAuthority: String, $payer: String) {
createCreateNewResourceTreeTransaction(project: $project, resource: $resource, authority: $authority, treeConfig: $treeConfig, delegateAuthority: $delegateAuthority, payer: $payer) {
cost
maxTreeCapacity
proofBytes
space
treeAddress
tx {
blockhash
lastValidBlockHeight
transaction
}
}
}
Provide the data like this:
{
"project": "pubkey", // Project public key as a string
"authority": "pubkey", // Project authority public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"resource": "pubkey", // Resource public key as a string
"treeConfig": {
// Provide either the basic or advanced configuration, we recommend using the basic configuration if you don't know the exact values of maxDepth, maxBufferSize, and canopyDepth (the basic configuration will automatically configure these values for you)
"basic": {
"numAssets": int // The desired number of resources this tree will be able to store, each leaf (asset) in the tree represents one ownership record; for example: everytime you mint this resource, a new leaf will be added to the tree with the ownership and amount information
},
"advanced": {
"maxDepth": int, // Maximum depth of the tree
"maxBufferSize": int, // Maximum buffer size
"canopyDepth": int // Canopy depth
}
}
}
Mint a resource
- JavaScript
- GraphQL
const {
createMintResourceTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createMintResourceTransaction({
authority: adminPublicKey.toString(), // Project authority's public key
owner: userPublicKey.toString(), // The owner's public key, this wallet will receive the resource
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional, if specified, the delegate authority will be used to mint the resource
payer: adminPublicKey.toString(),
resource: resourceAddress.toString(),
params: {
// Use either fungible, if the resource is fungible, or inf, if the resource is non-fungible
fungible: {
amount: "1000000000",
},
inf: {
// Characteristics of the resource, string array
characteristics: []
}
}
});
query CreateMintResourceTransaction($resource: String!, $owner: String!, $authority: String!, $params: MintResourceInput!, $delegateAuthority: String, $payer: String) {
createMintResourceTransaction(resource: $resource, owner: $owner, authority: $authority, params: $params, delegateAuthority: $delegateAuthority, payer: $payer) {
transaction
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"resource": "pubkey", // Resource public key as a string
"owner": "pubkey", // Owner's public key as a string, this wallet will receive the resource
"authority": "pubkey", // Authority's public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"params": {
// Use either fungible, if the resource is fungible, or inf, if the resource is non-fungible
"fungible": {
"amount": "string" // Amount of the resource to mint
},
"inf": {
"characteristics": ["string"] // Characteristics of the resource as a string array
}
}
}
Burn a resource
- JavaScript
- GraphQL
const {
createBurnResourceTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createBurnResourceTransaction({
authority: userPublicKey.toString(), // The resource owner's public key
resource: resourceAddress.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
params: {
// Only fungible resources can be burned
fungible: {
amount: "50000",
}
}
});
query CreateBurnResourceTransaction($resource: String!, $params: BurnResourceInput!, $authority: String!, $payer: String) {
createBurnResourceTransaction(resource: $resource, params: $params, authority: $authority, payer: $payer) {
transaction
blockhash
lastValidBlockHeight
}
}
Provide the accompanying data:
{
"resource": "pubkey", // Resource public key as a string
"params": {
// Only fungible resources can be burned
"fungible": {
"amount": "int" // Amount of the resource to burn
}
},
"authority": "pubkey", // Resource owner's public key as a string
"payer": "pubkey" // Optional, transaction payer's public key as a string
}
Unwrap a resource
By default, when resources are crafted/minted, they are "wrapped". This essentially means that they exist in the user's inventory, but can not be used unless they're unwrapped. For example, in order to craft new items using a recipe, the resources need to be unwrapped first.
Unwrapping a resource also makes it tradable. This is useful if you want to allow players to trade resources with each other or sell them on our upcoming marketplace.
- JavaScript
- GraphQL
const {
createCreateUnwrapResourceTransaction: {
tx: txResponse, // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
member // This is the member address of the unwrapped resource (only in case of non-fungible resources)
},
} = await client.createCreateUnwrapResourceTransaction({
authority: userPublicKey.toString(),
resource: resourceAddress.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
params: {
// Specify one of the two, fungible or inf, depending on the resource type
fungible: {
amount: "1",
},
inf: {
group: "pubkey" // Resource group PDA public key as a string
}
}
});
query CreateCreateUnwrapResourceTransaction($resource: String!, $params: UnWrapResourceInput!, $authority: String!, $payer: String) {
createCreateUnwrapResourceTransaction(resource: $resource, params: $params, authority: $authority, payer: $payer) {
tx {
transaction
blockhash
lastValidBlockHeight
}
member
}
}
Provide the data like this:
{
"resource": "pubkey", // Resource address as a string
"authority": "pubkey", // Resource owner's public key as a string
"payer": "pubkey", // Optional, transaction payer's public key as a string
"params": {
// Specify one of the two, fungible or inf, depending on the resource type
"fungible": {
"amount": "int" // Amount of the resource to unwrap
},
"inf": {
"group": "string" // Resource group PDA public key as a string
}
}
}
Wrap a resource
Wrapping a resource locks the resource in the user's inventory. This means that the resource can not be traded or used in any way until it's unwrapped.
- JavaScript
- GraphQL
const {
createCreateWrapResourceTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createCreateWrapResourceTransaction({
authority: userPublicKey.toString(),
resource: resourceAddress.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
params: {
// Only fungible resources can be rewrapped
fungible: {
amount: "5"
}
}
});
query CreateCreateWrapResourceTransaction($resource: String!, $params: WrapResourceInput!, $authority: String!, $payer: String) {
createCreateWrapResourceTransaction(resource: $resource, params: $params, authority: $authority, payer: $payer) {
transaction
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"resource": "pubkey", // Resource address as a string
"authority": "pubkey", // Unwrapped resource owner's public key as a string
"payer": "pubkey", // Optional, transaction payer's public key as a string
"params": {
"fungible": {
"amount": "int" // Amount of the resource to wrap
}
},
}
Recipes
Recipes are a way to define how resources can be combined to create new items.
In Honeycomb Protocol, developers can define recipes that let their users craft new items. Let's take a look at how you can bake this functionality into your app or game.
Create a recipe
- JavaScript
- GraphQL
const {
createCreateRecipeTransaction: {
recipe: recipeAddress, // This is the recipe address once it'll be created
tx: txResponse, // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
},
} = await client.createInitializeRecipeTransaction({
project: projectAddress.toString(),
xp: "50000",
authority: adminPublicKey.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional, resource delegate authority public key
ingredients: [
// Send an array of ingredients here, these resources must already be created in your project and should be of the same type
{
fungible: {
address: resourceAddress.toString(),
amount: "100",
},
},
],
meal: {
fungible: {
isCompressed: false,
address: resultingResourceAddress.toString(),
amount: "5",
},
},
});
query CreateInitializeRecipeTransaction($project: String!, $xp: BigInt!, $ingredients: [IngredientInput!]!, $meal: MealInput!, $authority: String!, $delegateAuthority: String, $payer: String) {
createInitializeRecipeTransaction(project: $project, xp: $xp, ingredients: $ingredients, meal: $meal, authority: $authority, delegateAuthority: $delegateAuthority, payer: $payer) {
transactions {
transactions
blockhash
lastValidBlockHeight
}
recipe
}
}
Provide the data like this:
{
"project": "pubkey", // Project public key as a string
"authority": "pubkey", // Project authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"xp": "int", // Experience points as string, the user will gain this much XP when they craft the item
"ingredients": [
// Send an array of ingredients here, these resources must already be created in your project
{
// Send either fungible or inf, but make sure all ingredients are of the same type (can't mix fungible and inf ingredients)
"fungible": {
"address": "pubkey", // Resource public key as a string
"amount": "int" // Amount of the resource to mint
},
"inf": {
"address": "pubkey", // Resource public key as a string
"amount": "int" // Amount of the resource to mint
}
}
],
"meal": {
// Send either fungible or inf, this is the item that will be crafted, make sure this resource is already created in your project
"fungible": {
"address": "pubkey", // Resource public key as a string
"amount": "int" // Amount of the resource to mint,
"isCompressed": boolean // Optional, if true, the resource will be compressed
},
"inf": {
"address": "pubkey", // Resource public key as a string
"amount": "int", // Amount of the resource to mint
"isCompressed": boolean // Optional, if true, the resource will be compressed
"characteristics": [
{
"key": "string",
"value": "string"
}
]
}
}
}
Craft/cook an item using a recipe
Crafting or cooking an item using a recipe is a multi-step process. Let's go through those.
1. Begin cooking
The first step in the cooking process is to create a transaction that initializes the cooking process.
This function will return two things, the transaction itself and the cooking account address that's used to store the cooking state.
- JavaScript
- GraphQL
const {
createBeginCookingTransaction: {
transaction, // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
cooking // This is the cooking account address, you'll need this for the next steps
},
} = await client.createBeginCookingTransaction({
recipe: recipeAddress.toString(),
owner: userPublicKey.toString(),
payer: payerPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
});
query CreateBeginCookingTransaction($recipe: String!, $owner: String!, $payer: String) {
createBeginCookingTransaction(recipe: $recipe, owner: $owner, payer: $payer) {
transaction {
transaction
blockhash
lastValidBlockHeight
}
cooking
}
}
Provide the data like this:
{
"recipe": "pubkey", // Recipe public key as a string
"owner": "pubkey", // Owner's public key as a string
"payer": "pubkey" // Optional, tx payer public key as a string
}
2. Use ingredients
The next step is to use the ingredients to cook the item.
Depending on how many ingredients your recipe needs, you might need to create and send multiple use ingredients transactions.
Solana has a transaction size limit of 1232 bytes, so you might need to split the ingredients into multiple use ingredients transactions.
- JavaScript
- GraphQL
const {
createUseIngredientsTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createUseIngredientsTransaction({
recipe: recipeAddress.toString(),
cooking: cookingAddress.toString(), // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
owner: userPublicKey.toString(),
payer: payerPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
ingredients: [
// Send an array of ingredients here, the user must own these resources and they should be unwrapped
{
// Send either fungible or inf per object
fungible: {
address: fungibleResourceAddress.toString(),
},
inf: [
{
mint: infResourceMint.toString(),
resource: infResourceAddress.toString(),
}
]
},
],
});
query CreateUseIngredientsTransaction($recipe: String!, $cooking: String!, $ingredients: [UseIngredientInput!]!, $owner: String!, $payer: String) {
createUseIngredientsTransaction(recipe: $recipe, cooking: $cooking, ingredients: $ingredients, owner: $owner, payer: $payer) {
transactions
blockhash
lastValidBlockHeight
}
}
Provide the accompanying data:
{
"recipe": "pubkey", // Recipe public key as a string
"cooking": "pubkey", // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
"owner": "pubkey", // User's public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"ingredients": [
// Send an array of ingredients here, the user must own these resources and they should be unwrapped
{
// Send either fungible or inf per object
"fungible": {
"address": "pubkey", // Resource public key as a string
},
"inf": [
{
"mint": "pubkey", // Resource mint as a string
"resource": "pubkey" // Resource public key as a string
}
]
}
]
},
3. Claim XP
If your recipe has an XP reward, you can claim it using this function. The user's profile will be updated with the XP reward.
- JavaScript
- GraphQL
const {
createClaimXPTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createClaimXPTransaction({
recipe: recipeAddress.toString(),
cooking: cookingAddress.toString(), // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
owner: userPublicKey.toString(),
payer: payerPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
});
query CreateClaimXPTransaction($recipe: String!, $cooking: String!, $owner: String!, $payer: String) {
createClaimXPTransaction(recipe: $recipe, cooking: $cooking, owner: $owner, payer: $payer) {
transaction
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"recipe": "pubkey", // Recipe public key as a string
"cooking": "pubkey", // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
"owner": "pubkey", // User's public key as a string
"payer": "pubkey" // Optional, tx payer public key as a string
}
4. Finish cooking
- JavaScript
- GraphQL
const {
createFinishCookingTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createFinishCookingTransaction({
cooking: cookingAddress.toString(), // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
recipe: recipeAddress.toString(),
owner: userPublicKey.toString(),
payer: payerPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
});
query CreateFinishCookingTransaction($recipe: String!, $cooking: String!, $owner: String!, $payer: String) {
createFinishCookingTransaction(recipe: $recipe, cooking: $cooking, owner: $owner, payer: $payer) {
transaction
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"recipe": "pubkey", // Recipe public key as a string
"cooking": "pubkey", // Cooking account public key as a string (returned from the createBeginCookingTransaction function)
"owner": "pubkey", // User's public key as a string
"payer": "pubkey" // Optional, tx payer public key as a string
}
Add an ingredient to a recipe
- JavaScript
- GraphQL
const {
createAddIngredientsTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createAddIngredientsTransaction({
recipe: recipeAddress.toString(),
authority: adminPublicKey.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional, resource delegate authority public key
ingredients: [
// Send an array of ingredients here, these resources must already be created in your project, also make sure they're of the same type as the ones already present in your recipe
{
// Send either fungible or inf per object, but make sure all ingredients are of the same type (can't mix fungible and inf ingredients)
fungible: {
address: fungibleResourceAddress.toString(),
amount: "50000",
},
inf: {
address: infResourceAddress.toString(),
amount: "50000",
}
},
],
});
query CreateAddIngredientsTransaction($recipe: String!, $ingredients: [IngredientInput!]!, $authority: String!, $delegateAuthority: String, $payer: String) {
createAddIngredientsTransaction(recipe: $recipe, ingredients: $ingredients, authority: $authority, delegateAuthority: $delegateAuthority, payer: $payer) {
transactions
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"recipe": "pubkey", // Recipe public key as a string
"authority": "pubkey", // Project authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"ingredients": [
// Send an array of ingredients here, these resources must already be created in your project, also make sure they're of the same type as the ones already present in your recipe
{
// Send either fungible or inf per object, but make sure all ingredients are of the same type (can't mix fungible and inf ingredients)
"fungible": {
"address": "pubkey", // Resource public key as a string
"amount": "int" // Amount of the resource needed
},
"inf": {
"address": "pubkey", // Resource public key as a string
"amount": "int" // Amount of the resource needed
}
}
]
},
Remove an ingredient from a recipe
- JavaScript
- GraphQL
const {
createRemoveIngredientsTransaction: txResponse // This is the transaction response, you'll need to sign and send this transaction through client.sendBulkTransactions
} = await client.createRemoveIngredientsTransaction({
recipe: recipeAddress.toString(),
authority: adminPublicKey.toString(),
payer: adminPublicKey.toString(), // Optional, specify when you want a different wallet to pay for the tx
delegateAuthority: delegateAuthorityPublicKey.toString(), // Optional, resource delegate authority public key
ingredients: [ // Send an array of ingredient public keys as a string here, these resources must already be present in your project and this recipe
ingredientAddress.toString(),
],
});
query CreateRemoveIngredientsTransaction($recipe: String!, $ingredients: [String!]!, $authority: String!, $delegateAuthority: String, $payer: String) {
createRemoveIngredientsTransaction(recipe: $recipe, ingredients: $ingredients, authority: $authority, delegateAuthority: $delegateAuthority, payer: $payer) {
transactions
blockhash
lastValidBlockHeight
}
}
Provide the data like this:
{
"recipe": "pubkey", // Recipe public key as a string
"authority": "pubkey", // Project authority public key as a string
"payer": "pubkey", // Optional, tx payer public key as a string
"delegateAuthority": "pubkey", // Optional, resource delegate authority public key as a string
"ingredients": [
// Send an array of ingredient public keys as a string here, these resources must already be present in your project and this recipe
"pubkey"
]
},