Exercise: Create a cNFT collection of your own profile picture and social links as metadata and airdrop it to other fellows.
Topics in this exercise:
- bun
- TypeScript
- Merkle Tree
- Compressed NFT
- Metaplex
- Bubblegum
Table of contents:
Clone the repo:
git clone https://github.com/Laugharne/ssf_s8_exo.git
Install bun if needed
curl -fsSL https://bun.sh/install | bash
bun --help
To install dependencies:
bun install
To run:
bun run index.ts
This project was created using bun init
in bun v1.1.20. Bun is a fast all-in-one JavaScript runtime.
If you occure this problem: bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)
, resolve it by running the following command npm rebuild
Compressed NFTs on Solana are a more scalable, cost-efficient way to mint and manage NFTs, using off-chain storage with on-chain proofs via Merkle trees. Here's how it works:
-
Merkle Tree: A data structure that stores compressed proofs on-chain for efficient verification. Created using the
createTree()
call frommetaplex/bubblegum
, it allows for a compact, secure representation of many NFTs. -
NFT Collection: Defines a group of related NFTs. Created with the
createNft()
call frommetaplex
, it sets up metadata and ownership details for the collection. -
Minting: The process of adding new NFTs to a collection. This is done with
mintToCollectionV1()
frommetaplex/bubblegum
, allowing efficient addition of multiple NFTs to a collection using the Merkle tree structure.
Compressed NFTs enable large-scale minting with reduced costs, making them ideal for high-volume use cases like gaming and digital collectibles.
Update the different fields and values in the following files:
- Specify wich kind of environnment you will use
production
ordevelopement
- Set your Helius API key (Dashboard | Helius) if you have one, else stay with the current RPC URL of Solana...
NODE_ENV=developement
SOLANA_MAINNET_RPC_URL=https://rpc.helius.xyz/?api-key=<HELIUS_API_KEY>
SOLANA_DEVNET_RPC_URL=https://devnet.helius-rpc.com/?api-key=<HELIUS_API_KEY>
Put "key.json" file at project root, this will be the payer wallet (Format of the json file generated by solana-keygen grind
command)
There is several fields to set:
-
MERKLE_MAX_DEPTH
: parameter specifies the maximum height of the Merkle tree, which dictates the total number of NFTs (or leaves) it can hold. To accommodate 37 NFTs, we need the smallestmaxDepth
.Let's compute this:
- 2^5 = 32 (too small)
- 2^6 = 64 (sufficient)
So, the smallest depth that can hold 37 NFTs seems to be
maxDepth = 6
.But there's only some specific couple of values for
MERKLE_MAX_DEPTH
andMERKLE_MAX_BUFFER_SIZE
who seems to be authorized for the Merkle Tree !/// Initialization parameters for an SPL ConcurrentMerkleTree. /// /// Only the following permutations are valid: /// /// | max_depth | max_buffer_size | /// | --------- | --------------------- | /// | 14 | (64, 256, 1024, 2048) | /// | 20 | (64, 256, 1024, 2048) | /// | 24 | (64, 256, 512, 1024, 2048) | /// | 26 | (64, 256, 512, 1024, 2048) | /// | 30 | (512, 1024, 2048) |
So i choose the following couple of values (14, 64) for
MERKLE_MAX_DEPTH
andMERKLE_MAX_BUFFER_SIZE
! -
Two off-chain JSON file metadata, URL to set (see section "Metadata" below)
METADATA_COLLECTION_URL
for the collectionMETADATA_ITEM_URL
for each items minted (can be updated between each one)
-
IMAGE_URL
: Image URL of the cNFT -
Name, symbol and description of the collection
-
FEE_PERCENT
: The royalties shared by the creators in basis points (550 means 5.5% royalties) -
EXTERNAL_URL
: URI pointing to an external URL defining the asset (the creator's website for example)
export const MERKLE_MAX_DEPTH = 14;
export const MERKLE_MAX_BUFFER_SIZE = 64;
export const METADATA_COLLECTION_URL = "https://laugharne.github.io/cnft_metadata.json";
export const METADATA_ITEM_URL = "https://laugharne.github.io/cnft_item_metadata.json";
export const IMAGE_URL = "https://laugharne.github.io/logo.png";
export const COLLECTION_NAME = 'Solana Summer Fellowship 2024'
export const COLLECTION_SYMBOL = 'SSF24'
export const COLLECTION_DESCRIPTION = 'Solana Summer Fellowship 2024 cNFT collection from Laugharne'
export const FEE_PERCENT = 0
export const EXTERNAL_URL = 'https://laugharne.github.io'
export const NFT_ITEM_NAME = 'Laugharne Limited Edition'
In normal production environment, the off-chain data are hosted on services like NFT.storage or ipfs !
For this exercise i choose to host them on a GitHub account...
Prepare a list of Solana wallet addresses for the airdrop, in a CSV file.
The CSV file should contain a single column with the header "address", and each row should contain a valid Solana wallet address.
Example :
address
8zN3Wu9K5YX7xMdSXP6Q5bX6FN2h4pLJhxJWpT9uRT7Y
6bJ8eY2F2H1PxNqv4W3zV4XShGqT7G7Hk1Fm5XxgHJ2k
2yL9vR4fJDv7QZCtw8Zw1ND3y5mQeW2Kxqf8nB7nqTXY
5ZcR1WrVn8F8y1mQ5tBfjMkpD2xxVz2JQ5pJMezcvz8e
After set all required parameters and data as seen in previous section.
We can run the processus...
- At first we create the cNFT collection :
bun run 1_createNFTCollection
- Then creating the Merkle Tree :
bun run 2_createMerkleTree
- This program read a CSV file with all the addresses to airdrop :
bun run 3_mintCNFT.ts
Each call generate a "file" in data directory, to keep a trace of the processus These data are used cross programs too...
.
├── .gitignore
├── 1_createNFTCollection.ts
├── 2_createMerkleTree.ts
├── 3_mintCNFT.ts
├── README.md
├── assets
│ ├── 2024-09-10-16-22-42.png
│ ├── 2024-09-12-17-34-38.png
│ └── julie.png
├── bun.lockb
├── cnft_item_metadata.json
├── cnft_metadata.json
├── config.ts
├── data
│ ├── collectionImageUri.txt
│ ├── collectionJsonUri.txt
│ ├── collectionMintDevnet.txt
│ ├── collectionMintMainnet.txt
│ ├── merkleTreeDevnet.txt
│ ├── merkleTreeMainnet.txt
│ ├── nftItemJsonUri.txt
│ ├── nftItemMintDevnet.txt
│ └── nftItemMintMainnet.txt
├── index.ts
├── package-lock.json
├── package.json
├── tsconfig.json
└── utils.ts
{
"name" : "Solana Summer Fellowship 2024",
"symbol" : "SSF24",
"description" : "Solana Summer Fellowship 2024 cNFT collection from Laugharne",
"seller_fee_basis_points": 0,
"image" : "https://laugharne.github.io/logo.png",
"external_url" : "https://laugharne.github.io/",
"attributes" : [],
"collection" : {
"name" : "Laugharne cNFTs Collection #2",
"family": "Laugharne cNFTs"
},
"properties": {
"files": [
{
"uri" : "https://laugharne.github.io/logo.png",
"type": "image/png"
}
],
"category": "image"
}
}
{
"name" : "Solana Summer Fellowship 2024",
"symbol" : "SSF24",
"description" : "Solana Summer Fellowship 2024 cNFT collection from Laugharne",
"seller_fee_basis_points": 0,
"image" : "https://laugharne.github.io/logo.png",
"external_url" : "https://laugharne.github.io/",
"attributes" : [
{
"trait_type": "Program",
"value" : "Solana Summer Fellowship"
},
{
"trait_type": "Cohorte",
"value" : "Summer 2024"
},
{
"trait_type": "Mentor #1",
"value" : "Kunal Bagaria"
},
{
"trait_type": "Mentor #2",
"value" : "Syed Aabis Akhtar"
},
{
"trait_type": "Status",
"value" : "Yeah!"
},
{
"trait_type": "Mint date",
"value" : "2024-09-12"
}
],
"collection": {
"name" : "Laugharne cNFTs Collection #2",
"family": "Laugharne cNFTs"
},
"properties": {
"files": [
{
"uri" : "https://laugharne.github.io/logo.png",
"type": "image/png"
}
],
"category": "image"
},
"creators": {
"address" : "9BbWp6tcX9MEGSUEpNXfspYxYsWCxE9FgRkAc3RpftkT",
"verified": false,
"share" : 100
}
}
- Overview | Token Metadata
- How to Create a NFT On Solana | Token Metadata Guides
- Solana NFT Metadata Deep Dive
"@metaplex-foundation/mpl-bubblegum": "^1.0.1",
"@metaplex-foundation/mpl-token-metadata": "^3.0.0",
"@metaplex-foundation/umi-bundle-defaults": "^0.8.9",
"@metaplex-foundation/umi-uploader-nft-storage": "^0.8.9",
Addresses, Accounts, TX and scrrenshot:
- MerkleTree
- NFT Collection
- cNFT mint #1
- cNFT mint #2
- cNFt inside Phantom wallet
From fellowship:
- Creating Compressed NFTs with JavaScript | Solana
- All You Need to Know About Compression on Solana (The most detailed blog on Solana's compression)
- Exploring NFT Compression on Solana (All about specifically NFT compression (cNFTS))
- Overview | Bubblegum (Metaplex's Bubblegum program's docs)
- compressed.app - estimate costs for compressed NFTs
Metaplex/Bubblegum:
- Overview | Bubblegum
- GitHub - metaplex-foundation/mpl-bubblegum: Create and manage Metaplex compressed NFTs
- @metaplex-foundation/mpl-bubblegum - v4.2.1
Solandy videos:
- What is a Merkle Tree? How does Account Compression work? [Solana Tutorial] - Mar 22nd '23 - YouTube
- How Compressed NFTs work on Solana - Mar 30th '23 - YouTube
- How to Mint and Transfer compressed NFTs [Solana Tutorial] - Apr 14th '23 - YouTube
- Compressed NFTs Deep Dive: What you need to know before you mint! [Solana Tutorial] - June 3rd '23 - YouTube
Misc:
- Account Compression Program
- Core Concepts
- Example usage of the TS SDK
- solana-program-library/account-compression/programs at master · solana-labs/solana-program-library · GitHub
- Merkle Trees Visualization
- How to mint compressed NFTs