🗜ī¸Shyft compressed NFT APIs

Easy APIs for minting, transferring (bulk), burning (bulk) and reading compressed NFTs

How do compressed NFTs work?

Compressing an NFT involves the following steps:

  • Tree authority: This is a program that is authorized to add new leaves to the Merkle tree using the account-compression program. It also handles the encoding of cryptographic hashes.

  • Leaf authority: This attribute is given to a wallet account that owns a leaf. The wallet account has the authority to replace, remove, and decompress the leaf.

There are two types of authorities in compressed NFTs:

  • Mint account: This account is authorized to mint new NFTs.

  • Associated token account: This account holds the NFTs and manages their transfer to other accounts.

  • Wallet account: This account owns a leaf and has the authority to replace, remove, and decompress it.

Compressed NFT authorities are attributes associated with an NFT account that specify its capabilities. Each NFT account has three subordinate accounts:

  1. Metadata verification: The Bubblegum program from Metaplex verifies the metadata associated with an NFT.

  2. Account compression: The account-compression program from Bubblegum adds a new leaf to the Merkle tree.

  3. On-chain update: The Merkle tree is updated on-chain by account compression to reflect the new state of the world.

  4. Blockchain update: Any changes made to the Merkle tree are written to the Solana blockchain.

  5. Off-chain indexing: Off-chain indexers keep track of modifications made to the Merkle tree and manage the necessary information and authorizations to support decentralized applications and programs.

Where are compressed NFTs stored?

Compressed NFTs store their metadata off-chain in a Merkle tree, which is monitored by an indexer and accessed through Solana RPC service providers. Compared to storing the same data on-chain, this method is more cost-effective. Essentially, the data is off-chain, while a much smaller proof of the data in the form of the Merkle tree root is stored on-chain.

Create Merkle Tree

The setup of the tree is a crucial aspect of creating compressed NFTs, as it determines the cost of creation and cannot be modified once established. To create the tree, the @solana/spl-account-compression SDK offers helper functions that enable the following steps:

Determine the desired tree size. Allocate space for the tree on-chain by generating a new Keypair. Create the tree and assign ownership to the Bubblegum program.

POST /sol/v1/nft/compressed/create_tree

Creates a merkle tree.

Body (raw)

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • wallet_address: The tree authority and the payer of the transaction.

  • max_depth_size_pair: A JSON object, e.g, { "max_depth": 14, "max_buffer_size": 64 }

    • max_depth: The maximum depth of a binary tree, such as a Merkle tree, refers to the longest distance from any leaf node to the root node. In the case of Merkle trees, each leaf node represents a piece of data and is paired with another leaf node. To determine the maximum number of nodes that can be stored in a Merkle tree based on its maximum depth, we can use the following formula: nodes_count = 2 ^ maxDepth. This formula calculates the total number of nodes in the tree by raising 2 to the power of the maximum depth.

    • max_buffer_size: The maximum buffer size of a tree represents the maximum number of changes that can be made to the tree while still maintaining the validity of the root hash. In a regular tree, the root hash is a single hash that encompasses all the leaf data. Therefore, modifying any leaf node would make the proof required for any subsequent leaf node modification invalid. However, in a concurrent tree, a log of updates is maintained to keep track of these modifications for generating the necessary proofs. This log of updates is referred to as the changelog buffer, and its size is determined at the creation of the tree and is set as the max_buffer_size value.

    So the valid values are:

  • canopy_depth: The "canopy depth" or "canopy size" refers to the number of proof nodes that are stored or cached on-chain for a specific proof path. This value is important in the context of storing a portion of the proof on-chain, which can impact the cost and composability of a compressed NFT collection.

  • fee_payer: (optional) If mentioned this is the account that will be used for paying the transaction gas fee.

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "network": "devnet",
  "wallet_address": "5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2",
  "max_depth_size_pair": {
    "max_depth": 14,
    "max_buffer_size": 64
  },
  "canopy_depth": 10,
  "fee_payer": "3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/create_tree", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Mint Compressed NFT

This API allows you to mint NFTs from an already uploaded metadata URI. Metadata_uri should open a JSON document conforming to the Metaplex Non-Fungible Token Standard. If the JSON does not confirm to the Metaplex standard, the API returns an error.

The NFT's on-chain metadata is retrieved from the off-chain metadata present at the given URI.

POST /sol/v1/nft/compressed/mint

Body (raw)

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • creator_wallet: The creator/payer of the NFT.

  • merkle_tree: Merkle tree address.

  • metadata_uri: URI that contains metadata of the NFT (metaplex non-fungible-standard) in JSON file format.

  • max_supply: (optional) Maximum number of clones/edition mints possible for this NFT. Default 0 for one-of-a-kind NFT.

  • collection_address: (optional) On-chain address of the collection represented by an NFT, with max_supply of 0.

  • primary_sale_happend: (optional) Indicating that the first sale has happened. Default is false.

  • is_mutable: (optional) - In future update NFT possible or not depends on the value. Default is true.

  • receiver: (optional) Account address which will receive the newly created NFT.

  • is_delegate_authority: (optional) Delegate authority to the receiver. Default is false.

  • fee_payer: (optional) If mentioned this is the account that will be used for paying the transaction gas fee.

  • priority_fee: (optional) Prioritization fee of transaction in micro Lamports. A micro Lamport is 0.000001 Lamports.

  • service_charge: (optional) Transaction fee to be paid by the fee_payer (if not mentioned then creator_wallet) of the transaction. This fee can be charged in SOL or any SPL-20 token. Below is the structure of the service_charge key.

    • receiver: string - An address that will receive the service charge amount.

    • amount: number - The amount of currency to be charged.

    • token(optional): string - The address of the SPL token, the service charge currency. By default, SOL is charged.

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "creator_wallet": "2h4HFP8NbzvrBF2oN5cvPCZBrvVHytY68tfyzWx9Tyqo",
  "metadata_uri": "https://nftstorage.link/ipfs/bafkreiedqmvce6fs7xbpv5vhfncoscnwcf75rma3cq6el3xruhv5s7fk4u",
  "merkle_tree": "FBBUGyBsRwUqaq6CNhGBSpkDkd1UKJtDv2xRi1Ux1GGC",
  "collection_address": "Ek9o3FHoMjS65ELYJmjWkivMPdroqY9nzSSdDmCpgBc",
  "receiver": "3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf",
  "fee_payer": "rexYt592c6v2MZKtLTXmWQt9zdEDdd3pZzbwSatHBrx",
  "network": "mainnet-beta",
  "priority_fee": 1000,
  "service_charge": {
    "receiver": "5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2",
    "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "amount": 0.1
  }
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/mint", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Transfer Compressed NFT

Transfer an already minted NFT from one wallet to another.

POST /sol/v1/nft/compressed/transfer

Body (raw)

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • sender: Public key of the wallet to transfer from

  • nft_address: Address of the token to transfer

  • receiver: Wallet address of the receiver

  • fee_payer: (optional) If mentioned this is the account that will be used for paying the transaction gas fee.

  • priority_fee: (optional) Prioritization fee of transaction in micro Lamports. A micro Lamport is 0.000001 Lamports.

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "network": "devnet",
  "nft_address": "6YQNj6b5N4WN6MmKvudF9toWquKgt4Uwpv25LhhJ7GW9",
  "sender": "3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf",
  "receiver": "2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc",
  "fee_payer": "5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/transfer", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Burn Compressed NFT

Burn a particular NFT.

You will get an encoded transaction in response which you can sign in your front end or back end using the same wallet as used in the API.

We have already deployed a dev tool to sign and send transactions for quick testing https://shyft-insider.vercel.app/.

DELETE /sol/v1/nft/compressed/burn

Body (raw)

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • nft_address: Address of the token to transfer

  • wallet_address: Owner of the NFT

  • priority_fee: (optional) Prioritization fee of transaction in micro Lamports. A micro Lamport is 0.000001 Lamports.

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "QEbMrBRQEP92ToRo");

var raw = JSON.stringify({
  "network": "devnet",
  "nft_address": "G4EpMRJirDGFcyq7eR9VZ31nPgDhKLpD3HjpZ8GXTzVm",
  "wallet_address": "BFefyp7jNF5Xq2A4JDLLFFGpxLq5oPEFKBAQ46KJHW2R"
});

var requestOptions = {
  method: 'DELETE',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/burn", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Burn Multiple Compressed NFTs

Bulk burn wallet cNFTs.

Body (raw)

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • wallet_address: Source wallet address, holding the NFTs

  • nft_addresses: Addresses of the cNFT to burn (maximum 50 addresses are allowed)

  • priority_fee: (optional) Prioritization fee of transaction in micro Lamports. A micro Lamport is 0.000001 Lamports.

You can burn as many cNFTs. But a maximum of 2-3 burns could be packed in a single transaction, rest can be counted as packed as well. So in response encoded_transactions return an array of transactions.

You will get encoded transactions in response which you can sign in your front end or back end using the same wallet as used in the API.

We have already deployed a dev tool to sign and send transactions for quick testing https://shyft-insider.vercel.app/

DELETE /sol/v1/nft/compressed/burn_many

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "network": "devnet",
  "nft_addresses": [
    "ANBcsLv4gHSPgA28ECErFJgA8kAbQpTS8ee6V1XHDAsa",
    "7Yuv2f8N2cDDD8EPMLpNmo1Svg584f1cxQHAjdfa4v4v",
    "4nhTQTz3Ry8Tz9BUSdCEGDTBL5kbRvC7gQLFL1VnejhA",
    "717u1CYMfrLyrPSx8yvENXkituffj9B4NEhR5rgouq8J"
  ],
  "wallet_address": "3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf"
});

var requestOptions = {
  method: 'DELETE',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/burn_many", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Update Compressed NFT

Update metdata of a particular cNFT.

You will get an encoded transaction and signers in response which you can sign in your front end or back end using the same wallet as used in the API.

We have already deployed a dev tool to sign and send transactions for quick testing https://shyft-insider.vercel.app/.

Body (raw)

  • network: Solana blockchain environment (only mainnet-beta supported)

  • authority: Either collection authority (collection NFT's update_authority) or tree owner/delegate, depending on whether the item is in a verified collection

  • nft_address: Addresses of the cNFT to update

  • name (optional): cNFT Name (max 32 char allowed)

  • symbol (optional): cNFT Symbol (max 10 char allowed)

  • metadata_uri (optional): URI that contains metadata of the NFT (metaplex non-fungible-standard) in JSON file format.

  • royalty: (optional) represents how much percentage of secondary sales the original creator gets. Ranges from (0-100), 0 being the original creator gets nothing and 100 being the original creator gets the entire amount from the secondary sales

  • primary_sale_happend (optional): A boolean that indicates whether the asset has been sold before.

  • is_mutable (optional): A boolean that indicates whether the asset can be updated again. When changing this to false, any future updates will fail.

  • fee_payer: (optional) The account that pays the transaction gas fee.

  • priority_fee: (optional) Prioritization fee of transaction in micro Lamports. A micro Lamport is 0.000001 Lamports.

POST /sol/v1/nft/compressed/update

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "network": "devnet",
  "nft_address": "Bi4wSvaUmvMKHGZsbAwfkLt9fpQqwhMVwCr8yh7DpVPz",
  "authority": "5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2",
  "name": "JUP Magic Cats #12",
  "symbol": "JUPMagCats",
  "metadata_uri": "https://bafkreie4njkhtapvynvx7jtqgrmw5o2rfmj7qdul7xcstz2nm3sjlep6ka.ipfs.nftstorage.link",
  "royalty": 18,
  "primary_sale_happend": true,
  "is_mutable": true,
  "fee_payer": "3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf",
  "priority_fee": 100
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/update", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Read All Compressed NFTs ( by Owner Address)

Returns on-chain and off-chain data of all NFTs in the wallet.

GET /sol/v1/nft/compressed/read_all

Query Params

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • wallet_address: Wallet address

  • collection: (optional) To display wallet cNFTs associated with a specific collection, you can use this filter that isolates cNFTs based on their collection address and then present them.

  • refresh:(optional) Include this if the cached cNFTs for this wallet need to be refreshed.

var myHeaders = new Headers();
myHeaders.append("x-api-key", "YOUR_API_KEY");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/read_all?network=mainnet-beta&wallet_address=3PnTBCxBP7Eb7QMVSKTsTodDxfvQsLr4AKEaT8jeM2xJ&collection=Evya1SENYn5NUC66hv6hWmtxAcEAXymEt2TfaAHyvkhk", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Read Compressed NFT

Returns on-chain and off-chain NFT data.

GET /sol/v1/nft/compressed/read

Query Params

  • network: Solana blockchain environment (devnet/mainnet-beta)

  • nft_address: Address of the NFT that you want to read

var myHeaders = new Headers();
myHeaders.append("x-api-key", "QEbMrBRQEP92ToRo");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/read?network=devnet&nft_address=G4EpMRJirDGFcyq7eR9VZ31nPgDhKLpD3HjpZ8GXTzVm", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Read Wallet cNFTs

A paginated version of Read All Compressed NFTs API, returns the list of cNFTs in a wallet. A maximum of 50 NFTs are returned in a single API request.

GET /sol/v2/nft/compressed/read_all

Query Params

  • network: Solana blockchain environment (testnet/devnet/mainnet-beta)

  • wallet_address: Your wallet address

  • collection: (optional) To display wallet cNFTs associated with a specific collection, you can use this filter that isolates cNFTs based on their collection address and then present them.

  • refresh:(optional) Include this if the cached cNFTs for this wallet need to be refreshed.

  • page:(optional) Default value is 1.

  • size:(optional) Default value is 50.

var myHeaders = new Headers();
myHeaders.append("x-api-key", "Ppa46Mm-8Q9uNrLS");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v2/nft/compressed/read_all?network=mainnet-beta&wallet_address=3PnTBCxBP7Eb7QMVSKTsTodDxfvQsLr4AKEaT8jeM2xJ&collection=Evya1SENYn5NUC66hv6hWmtxAcEAXymEt2TfaAHyvkhk&refresh=true&page=1&size=1", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Read Selected cNFTs

Returns on-chain and off-chain data of selected compressed NFTs.

First call might be slightly slow, we cache automatically so that subsequent calls are lightning-fast.

POST /sol/v1/nft/compressed/read_selected

Body Params

  • network: Solana blockchain environment (testnet/devnet/mainnet-beta)

  • nft_addresses: (array of strings) Selected addresses (minimum 1 and maximum 10 cNFTs could be fetched)

  • refresh:(optional) Include this if the cached NFTs need to be refreshed.

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("x-api-key", "YOUR_API_KEY");

var raw = JSON.stringify({
  "network": "mainnet-beta",
  "nft_addresses": [
    "4S3Qv85aDvm3AoWgPiGb8BZByT63uQow9s5HgmrzkU5k",
    "Fh2TN5zEoNeiaiGjy72SZ1uFUJLKsNDny1U56k15ia2r"
  ],
  "refresh": false
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.shyft.to/sol/v1/nft/compressed/read_selected", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));