# `@solana/spl-account-compression` A TypeScript library for interacting with SPL Account Compression and SPL NoOp. [Docs here](https://solana-labs.github.io/solana-program-library/account-compression/sdk/docs/). ## Install ```shell npm install --save @solana/spl-account-compression @solana/web3.js ``` __OR__ ```shell yarn add @solana/spl-account-compression @solana/web3.js ``` ## Information This on-chain program provides an interface for composing smart contracts to create and use SPL ConcurrentMerkleTrees. The primary application of using SPL ConcurrentMerkleTrees is to synchronize off-chain databases with on-chain updates. SPL ConcurrentMerkleTrees are Merkle Trees that have their roots on-chain with support for fast-forwarding proofs. Fast forwarding allows multiple updates to the tree in a single block and reduces the latency burden on indexers. In order to execute transactions that modify an SPL ConcurrentMerkleTree, an indexer will need to parse through transactions that touch the tree in order to provide up-to-date merkle proofs. For more information regarding merkle proofs, see this great [explainer](https://ethereum.org/en/developers/tutorials/merkle-proofs-for-offline-data-integrity/). This program is targeted towards supporting [Metaplex Compressed NFTs](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum) and may be subject to change. A **rough draft** of the whitepaper for SPL ConcurrentMerkleTrees can be found [here](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view). ## High Level Overview ### Instructions Code to interact with the on-chain instructions is auto-generated by `@metaplex-foundation/solita`. Exported functions to create instructions have pattern `createInstruction`. * For example, account compression's `append_leaf` instruction has a `Solita`-generated factory function called `createAppendLeafInstruction`. `Solita` provides very low-level functions to create instructions. Thus, helper functions are provided for each instruction, denoted with the suffix `ix`. * For example: `createReplaceLeafInstruction` has a helper function `createReplaceLeafIx` ### Modules A merkle tree reference implementation is provided to index the on-chain trees. The `MerkleTree` class and its helpers are provided under `src/merkle-tree`. The `MerkleTree` class is meant to follow a similar interface as `MerkleTree` from [`merkletreejs`](https://www.npmjs.com/package/merkletreejs). | Feature | Our Tree | `merkletreejs` | Notes | | ---------- | -------- | -------------- | ------------------------------------------------------------ | | updateLeaf | ✅ | ❌ | This is the unique feature of `ConcurrentMerkleTree`'s | | multiProof | ❌ | ✅ | Possible to support in future version of Account Compression | | addLeaf | ✅ | ✅ | Our version does this via `updateLeaf()` | If you'd like to see more features added, please create an issue with the title `Account Compression` and your feature request. ### Examples 1. Create a tree ```typescript // Assume: known `payer` Keypair // Generate a keypair for the ConcurrentMerkleTree const cmtKeypair = Keypair.generate(); // Create a system instruction to allocate enough // space for the tree const allocAccountIx = await createAllocTreeIx( connection, cmtKeypair.publicKey, payer.publicKey, { maxDepth, maxBufferSize }, canopyDepth, ); // Create an SPL compression instruction to initialize // the newly created ConcurrentMerkleTree const initTreeIx = createInitEmptyMerkleTreeIx( cmtKeypair.publicKey, payer.publicKey, { maxDepth, maxBufferSize } ); const tx = new Transaction().add(allocAccountIx).add(initTreeIx); await sendAndConfirmTransaction(connection, tx, [cmtKeypair, payer]); ``` 2. Add a leaf to the tree ```typescript // Create a new leaf const newLeaf: Buffer = crypto.randomBytes(32); // Add the new leaf to the existing tree const appendIx = createAppendIx(cmtKeypair.publicKey, payer.publicKey, newLeaf); const tx = new Transaction().add(appendIx); await sendAndConfirmTransaction(connection, tx, [payer]); ``` 3. Replace a leaf in the tree, using the provided `MerkleTree` as an indexer This example assumes that `offChainTree` has been indexing all previous modifying transactions involving this tree. It is okay for the indexer to be behind by a maximum of `maxBufferSize` transactions. ```typescript // Assume: `offChainTree` is a MerkleTree instance // that has been indexing the `cmtKeypair.publicKey` transactions // Get a new leaf const newLeaf: Buffer = crypto.randomBytes(32); // Query off-chain records for information about the leaf // you wish to replace by its index in the tree const leafIndex = 314; // Replace the leaf at `leafIndex` with `newLeaf` const replaceIx = createReplaceIx( cmtKeypair.publicKey, payer.publicKey, newLeaf, offChainTree.getProof(leafIndex) ); const tx = new Transaction().add(replaceIx); await sendAndConfirmTransaction(connection, tx, [payer]); ``` 4. Replace a leaf in the tree, using a 3rd party indexer This example assumes that some 3rd party service is indexing the the tree at `cmtKeypair.publicKey` for you, and providing MerkleProofs via some REST endpoint. The `getProofFromAnIndexer` function is a **placeholder** to exemplify this relationship. ```typescript // Get a new leaf const newLeaf: Buffer = crypto.randomBytes(32); // Query off-chain indexer for a MerkleProof // possibly by executing GET request against a REST api const proof = await getProofFromAnIndexer(myOldLeaf); // Replace `myOldLeaf` with `newLeaf` at the same index in the tree const replaceIx = createReplaceIx( cmtKeypair.publicKey, payer.publicKey, newLeaf, proof ); const tx = new Transaction().add(replaceIx); await sendAndConfirmTransaction(connection, tx, [payer]); ``` ## Reference examples Here are some examples using account compression in the wild: * Solana Program Library [tests](https://github.com/solana-labs/solana-program-library/tree/master/account-compression/sdk/tests) * Metaplex Program Library Compressed NFT [tests](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum/js/tests) ## Build from Source 0. Install dependencies with `yarn`. 1. Generate the Solita SDK with `yarn solita`. 2. Then build the SDK with `yarn build`. 3. Run tests with `yarn test`. (Expect `jest` to detect an open handle that prevents it from exiting naturally)