@solana/spl-account-compression
A TypeScript library for interacting with SPL Account Compression and SPL NoOp.
For more information, see the full Solana account compression SDK documentation.
Install
npm install --save @solana/spl-account-compression @solana/web3.js
OR
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.
This program is targeted towards supporting Metaplex Compressed NFTs and may be subject to change.
A rough draft of the whitepaper for SPL ConcurrentMerkleTrees can be found here.
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 create<instructionName>Instruction
.
- 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
.
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
- Create a tree
const cmtKeypair = Keypair.generate();
const allocAccountIx = await createAllocTreeIx(
connection,
cmtKeypair.publicKey,
payer.publicKey,
{ maxDepth, maxBufferSize },
canopyDepth,
);
const initTreeIx = createInitEmptyMerkleTreeIx(
cmtKeypair.publicKey,
payer.publicKey,
{ maxDepth, maxBufferSize }
);
const tx = new Transaction().add(allocAccountIx).add(initTreeIx);
await sendAndConfirmTransaction(connection, tx, [cmtKeypair, payer]);
- Add a leaf to the tree
const newLeaf: Buffer = crypto.randomBytes(32);
const appendIx = createAppendIx(cmtKeypair.publicKey, payer.publicKey, newLeaf);
const tx = new Transaction().add(appendIx);
await sendAndConfirmTransaction(connection, tx, [payer]);
- 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.
const newLeaf: Buffer = crypto.randomBytes(32);
const leafIndex = 314;
const replaceIx = createReplaceIx(
cmtKeypair.publicKey,
payer.publicKey,
newLeaf,
offChainTree.getProof(leafIndex)
);
const tx = new Transaction().add(replaceIx);
await sendAndConfirmTransaction(connection, tx, [payer]);
- Replace a leaf in the tree, using a 3rd party indexer
This example assumes that some 3rd party service is indexing the tree at cmtKeypair.publicKey
for you, and providing MerkleProofs via some REST endpoint.
The getProofFromAnIndexer
function is a placeholder to exemplify this relationship.
const newLeaf: Buffer = crypto.randomBytes(32);
const proof = await getProofFromAnIndexer(myOldLeaf);
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:
Build from Source
-
Install dependencies with pnpm i
.
-
Generate the Solita SDK with pnpm solita
.
-
Then build the SDK with pnpm build
.
-
Run tests with pnpm test
. (Expect jest
to detect an open handle that prevents it from exiting naturally)