Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 92b1ea3

Browse files
authored
Account Compression: Typescript SDK v0.1.3 (#3743)
* ac-ts: update typescript to match v0.1.4 on-chain * ac: add prettier, lint, typedoc config * ac: test all init pairs * ac: format everything * ac: breaking changes to instructions helpers * ac: update tests to use getCurrentBufferIndex * ac: add docs to ts sdk * ac: add examples to Readme, Canopy calculation explanation * ac: ts helpers now require ValidDepthSizePair * ac: restructure MerkleTree in tests to follow layout * ac: lint, pretty * ac: export MerkleTree file * ac: revamp instruction helpers with merkle tree class * ac: add docs * ac: pretty, lint * ac: update README with jnwng's feedback * ac: export merkle-tree docs
1 parent a16d21c commit 92b1ea3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3369
-1489
lines changed

account-compression/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

account-compression/sdk/.eslintignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.eslintrc.js
2+
/.solitarc.js
3+
/jest.config.js
4+
/yarn.lock
5+
doc
6+
dist
7+
tests

account-compression/sdk/.eslintrc.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
es6: true,
5+
node: true,
6+
mocha: true,
7+
},
8+
extends: [
9+
"eslint:recommended",
10+
"plugin:import/errors",
11+
"plugin:import/warnings",
12+
"plugin:import/typescript",
13+
],
14+
parser: "@typescript-eslint/parser",
15+
parserOptions: {
16+
sourceType: "module",
17+
ecmaVersion: 8,
18+
},
19+
plugins: ["@typescript-eslint"],
20+
rules: {
21+
"@typescript-eslint/no-unused-vars": ["error"],
22+
"import/first": ["error"],
23+
"import/no-commonjs": ["error"],
24+
"import/order": [
25+
"error",
26+
{
27+
groups: [
28+
["internal", "external", "builtin"],
29+
["index", "sibling", "parent"],
30+
],
31+
"newlines-between": "always",
32+
},
33+
],
34+
"linebreak-style": ["error", "unix"],
35+
"no-console": [0],
36+
"no-trailing-spaces": ["error"],
37+
"no-undef": "off",
38+
"no-unused-vars": "off",
39+
quotes: [
40+
"error",
41+
"single",
42+
{ avoidEscape: true, allowTemplateLiterals: true },
43+
],
44+
"require-await": ["error"],
45+
semi: ["error", "always"],
46+
},
47+
};

account-compression/sdk/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
dist/
2+
doc/
23
test-ledger/
34
yarn-error.log

account-compression/sdk/.solitarc.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
// @ts-check
2-
const path = require('path');
3-
const programDir = path.join(__dirname, '..', 'programs', 'account-compression');
4-
const idlDir = path.join(__dirname, 'idl');
5-
const sdkDir = path.join(__dirname, 'src', 'generated');
6-
const binaryInstallDir = path.join(__dirname, '..', 'target', 'solita');
2+
const path = require("path");
3+
const programDir = path.join(
4+
__dirname,
5+
"..",
6+
"programs",
7+
"account-compression"
8+
);
9+
const idlDir = path.join(__dirname, "idl");
10+
const sdkDir = path.join(__dirname, "src", "generated");
11+
const binaryInstallDir = path.join(__dirname, "..", "target", "solita");
712

813
module.exports = {
9-
idlGenerator: 'anchor',
10-
programName: 'spl_account_compression',
11-
programId: 'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK',
14+
idlGenerator: "anchor",
15+
programName: "spl_account_compression",
16+
programId: "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK",
1217
idlDir,
1318
sdkDir,
1419
binaryInstallDir,

account-compression/sdk/README.md

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,159 @@ __OR__
1414
yarn add @solana/spl-account-compression @solana/web3.js
1515
```
1616

17+
## Information
1718

18-
## Examples
19+
This on-chain program provides an interface for composing smart contracts to create and use SPL ConcurrentMerkleTrees.
20+
The primary application of using SPL ConcurrentMerkleTrees is to synchronize off-chain databases with on-chain updates.
1921

20-
* Solana Program Library [tests](https://github.com/solana-labs/solana-program-library/tree/master/account-compression/sdk/tests)
22+
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.
2123

22-
* Metaplex Program Library Compressed NFT [tests](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum/js/tests)
2324

24-
## Information
25-
26-
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 make edits to off-chain data with on-chain verification.
25+
In order to execute transactions that modify an SPL ConcurrentMerkleTree, an indexer will need to
26+
parse through transactions that touch the tree in order to provide up-to-date merkle proofs.
27+
For more information regarding merkle proofs, see this great [explainer](https://ethereum.org/en/developers/tutorials/merkle-proofs-for-offline-data-integrity/).
2728

2829
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.
2930

30-
Note: Using this program requires an indexer to parse transaction information and write relevant information to an off-chain database.
31+
A **rough draft** of the whitepaper for SPL ConcurrentMerkleTrees can be found [here](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view).
32+
33+
## High Level Overview
34+
35+
### Instructions
36+
Code to interact with the on-chain instructions is auto-generated by `@metaplex-foundation/solita`.
37+
Exported functions to create instructions have pattern `create<instructionName>Instruction`.
38+
* For example, account compression's `append_leaf` instruction has a `Solita`-generated factory function called
39+
`createAppendLeafInstruction`.
40+
41+
`Solita` provides very low-level functions to create instructions. Thus, helper functions are provided for each instruction, denoted with the suffix `ix`.
42+
* For example: `createReplaceLeafInstruction` has a helper function `createReplaceLeafIx`
43+
44+
### Modules
45+
46+
A merkle tree reference implementation is provided to index the on-chain trees. The `MerkleTree` class and its helpers are provided
47+
under `src/merkle-tree`.
48+
49+
The `MerkleTree` class is meant to follow a similar interface as `MerkleTree` from [`merkletreejs`](https://www.npmjs.com/package/merkletreejs).
50+
51+
| Feature | Our Tree | `merkletreejs` | Notes |
52+
| ---------- | -------- | -------------- | ------------------------------------------------------------ |
53+
| updateLeaf ||| This is the unique feature of `ConcurrentMerkleTree`'s |
54+
| multiProof ||| Possible to support in future version of Account Compression |
55+
| addLeaf ||| Our version does this via `updateLeaf()` |
56+
57+
If you'd like to see more features added, please create an issue with the title `Account Compression` and your feature request.
58+
59+
### Examples
60+
61+
1. Create a tree
62+
63+
```typescript
64+
// Assume: known `payer` Keypair
65+
66+
// Generate a keypair for the ConcurrentMerkleTree
67+
const cmtKeypair = Keypair.generate();
68+
69+
// Create a system instruction to allocate enough
70+
// space for the tree
71+
const allocAccountIx = await createAllocTreeIx(
72+
connection,
73+
cmtKeypair.publicKey,
74+
payer.publicKey,
75+
{ maxDepth, maxBufferSize },
76+
canopyDepth,
77+
);
78+
79+
// Create an SPL compression instruction to initialize
80+
// the newly created ConcurrentMerkleTree
81+
const initTreeIx = createInitEmptyMerkleTreeIx(
82+
cmtKeypair.publicKey,
83+
payer.publicKey,
84+
{ maxDepth, maxBufferSize }
85+
);
86+
87+
const tx = new Transaction().add(allocAccountIx).add(initTreeIx);
88+
89+
await sendAndConfirmTransaction(connection, tx, [cmtKeypair, payer]);
90+
```
91+
92+
2. Add a leaf to the tree
93+
94+
```typescript
95+
// Create a new leaf
96+
const newLeaf: Buffer = crypto.randomBytes(32);
97+
98+
// Add the new leaf to the existing tree
99+
const appendIx = createAppendIx(cmtKeypair.publicKey, payer.publicKey, newLeaf);
100+
101+
const tx = new Transaction().add(appendIx);
31102

32-
A **rough draft** of the whitepaper for SPL ConcurrentMerkleTree's can be found [here](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view).
103+
await sendAndConfirmTransaction(connection, tx, [payer]);
104+
```
105+
106+
3. Replace a leaf in the tree, using the provided `MerkleTree` as an indexer
107+
108+
This example assumes that `offChainTree` has been indexing all previous modifying transactions
109+
involving this tree.
110+
It is okay for the indexer to be behind by a maximum of `maxBufferSize` transactions.
111+
112+
113+
```typescript
114+
// Assume: `offChainTree` is a MerkleTree instance
115+
// that has been indexing the `cmtKeypair.publicKey` transactions
116+
117+
// Get a new leaf
118+
const newLeaf: Buffer = crypto.randomBytes(32);
119+
120+
// Query off-chain records for information about the leaf
121+
// you wish to replace by its index in the tree
122+
const leafIndex = 314;
123+
124+
// Replace the leaf at `leafIndex` with `newLeaf`
125+
const replaceIx = createReplaceIx(
126+
cmtKeypair.publicKey,
127+
payer.publicKey,
128+
newLeaf,
129+
offChainTree.getProof(leafIndex)
130+
);
131+
132+
const tx = new Transaction().add(replaceIx);
133+
134+
await sendAndConfirmTransaction(connection, tx, [payer]);
135+
```
136+
137+
4. Replace a leaf in the tree, using a 3rd party indexer
138+
139+
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.
140+
The `getProofFromAnIndexer` function is a **placeholder** to exemplify this relationship.
141+
142+
```typescript
143+
// Get a new leaf
144+
const newLeaf: Buffer = crypto.randomBytes(32);
145+
146+
// Query off-chain indexer for a MerkleProof
147+
// possibly by executing GET request against a REST api
148+
const proof = await getProofFromAnIndexer(myOldLeaf);
149+
150+
// Replace `myOldLeaf` with `newLeaf` at the same index in the tree
151+
const replaceIx = createReplaceIx(
152+
cmtKeypair.publicKey,
153+
payer.publicKey,
154+
newLeaf,
155+
proof
156+
);
157+
158+
const tx = new Transaction().add(replaceIx);
159+
160+
await sendAndConfirmTransaction(connection, tx, [payer]);
161+
```
162+
163+
## Reference examples
164+
165+
Here are some examples using account compression in the wild:
166+
167+
* Solana Program Library [tests](https://github.com/solana-labs/solana-program-library/tree/master/account-compression/sdk/tests)
168+
169+
* Metaplex Program Library Compressed NFT [tests](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum/js/tests)
33170

34171
## Build from Source
35172

account-compression/sdk/idl/spl_account_compression.json

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.1.3",
2+
"version": "0.1.4",
33
"name": "spl_account_compression",
44
"instructions": [
55
{
@@ -34,12 +34,11 @@
3434
]
3535
},
3636
{
37-
"name": "logWrapper",
37+
"name": "noop",
3838
"isMut": false,
3939
"isSigner": false,
4040
"docs": [
41-
"Program used to emit changelogs as instruction data.",
42-
"See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh`"
41+
"Program used to emit changelogs as cpi instruction data."
4342
]
4443
}
4544
],
@@ -86,12 +85,11 @@
8685
]
8786
},
8887
{
89-
"name": "logWrapper",
88+
"name": "noop",
9089
"isMut": false,
9190
"isSigner": false,
9291
"docs": [
93-
"Program used to emit changelogs as instruction data.",
94-
"See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh`"
92+
"Program used to emit changelogs as cpi instruction data."
9593
]
9694
}
9795
],
@@ -222,12 +220,11 @@
222220
]
223221
},
224222
{
225-
"name": "logWrapper",
223+
"name": "noop",
226224
"isMut": false,
227225
"isSigner": false,
228226
"docs": [
229-
"Program used to emit changelogs as instruction data.",
230-
"See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh`"
227+
"Program used to emit changelogs as cpi instruction data."
231228
]
232229
}
233230
],
@@ -267,12 +264,11 @@
267264
]
268265
},
269266
{
270-
"name": "logWrapper",
267+
"name": "noop",
271268
"isMut": false,
272269
"isSigner": false,
273270
"docs": [
274-
"Program used to emit changelogs as instruction data.",
275-
"See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh`"
271+
"Program used to emit changelogs as cpi instruction data."
276272
]
277273
}
278274
],
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
2-
preset: 'ts-jest/presets/default',
3-
testEnvironment: 'node',
4-
testTimeout: 100000,
5-
resolver: "ts-jest-resolver",
6-
};
2+
preset: "ts-jest/presets/default",
3+
testEnvironment: "node",
4+
testTimeout: 100000,
5+
resolver: "ts-jest-resolver",
6+
};

0 commit comments

Comments
 (0)