diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9364a66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +.gitignore +dist/ \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 795161b..2b8d41e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ -Resources for developers to learn and build with Light and Photon. +# Light MCP -WIP. +ZK Compression documentation search for AI-powered IDEs. + +## Setup + +### Cursor +```bash +npx light-mcp install +``` + +### Claude Code +```bash +claude mcp add npx light-mcp install +``` diff --git a/compression-docs/.gitbook/assets/1000xHackathon_banner.png b/compression-docs/.gitbook/assets/1000xHackathon_banner.png deleted file mode 100644 index f207fcf..0000000 Binary files a/compression-docs/.gitbook/assets/1000xHackathon_banner.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/1000x_35_banner (1).png b/compression-docs/.gitbook/assets/1000x_35_banner (1).png deleted file mode 100644 index bddc9bb..0000000 Binary files a/compression-docs/.gitbook/assets/1000x_35_banner (1).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/1000x_35_banner.png b/compression-docs/.gitbook/assets/1000x_35_banner.png deleted file mode 100644 index bddc9bb..0000000 Binary files a/compression-docs/.gitbook/assets/1000x_35_banner.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/Build_Anything.png b/compression-docs/.gitbook/assets/Build_Anything.png deleted file mode 100644 index 04e018f..0000000 Binary files a/compression-docs/.gitbook/assets/Build_Anything.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/Group 632668.png b/compression-docs/.gitbook/assets/Group 632668.png deleted file mode 100644 index a9f4a6d..0000000 Binary files a/compression-docs/.gitbook/assets/Group 632668.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/ScaleAnything (1).png b/compression-docs/.gitbook/assets/ScaleAnything (1).png deleted file mode 100644 index da99be4..0000000 Binary files a/compression-docs/.gitbook/assets/ScaleAnything (1).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/ScaleAnything (2).png b/compression-docs/.gitbook/assets/ScaleAnything (2).png deleted file mode 100644 index 6c6d571..0000000 Binary files a/compression-docs/.gitbook/assets/ScaleAnything (2).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/ScaleAnything.png b/compression-docs/.gitbook/assets/ScaleAnything.png deleted file mode 100644 index 6c6d571..0000000 Binary files a/compression-docs/.gitbook/assets/ScaleAnything.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.22.52.png b/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.22.52.png deleted file mode 100644 index e399eb6..0000000 Binary files a/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.22.52.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.37.41.png b/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.37.41.png deleted file mode 100644 index 10f2e43..0000000 Binary files a/compression-docs/.gitbook/assets/Screenshot 2024-05-10 at 06.37.41.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png b/compression-docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png deleted file mode 100644 index 8045247..0000000 Binary files a/compression-docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (1).png b/compression-docs/.gitbook/assets/image (1).png deleted file mode 100644 index f5e7cba..0000000 Binary files a/compression-docs/.gitbook/assets/image (1).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (2).png b/compression-docs/.gitbook/assets/image (2).png deleted file mode 100644 index f5e7cba..0000000 Binary files a/compression-docs/.gitbook/assets/image (2).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (3).png b/compression-docs/.gitbook/assets/image (3).png deleted file mode 100644 index 5ba5abc..0000000 Binary files a/compression-docs/.gitbook/assets/image (3).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (4).png b/compression-docs/.gitbook/assets/image (4).png deleted file mode 100644 index 6cf0af6..0000000 Binary files a/compression-docs/.gitbook/assets/image (4).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (5).png b/compression-docs/.gitbook/assets/image (5).png deleted file mode 100644 index d9f386e..0000000 Binary files a/compression-docs/.gitbook/assets/image (5).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image (6).png b/compression-docs/.gitbook/assets/image (6).png deleted file mode 100644 index 2cef43a..0000000 Binary files a/compression-docs/.gitbook/assets/image (6).png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/image.png b/compression-docs/.gitbook/assets/image.png deleted file mode 100644 index d7c5bc6..0000000 Binary files a/compression-docs/.gitbook/assets/image.png and /dev/null differ diff --git a/compression-docs/.gitbook/assets/solanazero-banner Large 2.jpeg b/compression-docs/.gitbook/assets/solanazero-banner Large 2.jpeg deleted file mode 100644 index 8d539fa..0000000 Binary files a/compression-docs/.gitbook/assets/solanazero-banner Large 2.jpeg and /dev/null differ diff --git a/compression-docs/.gitbook/assets/solanazero-banner Large.jpeg b/compression-docs/.gitbook/assets/solanazero-banner Large.jpeg deleted file mode 100644 index 0df338b..0000000 Binary files a/compression-docs/.gitbook/assets/solanazero-banner Large.jpeg and /dev/null differ diff --git a/compression-docs/.gitbook/assets/solanazero-banner.jpg b/compression-docs/.gitbook/assets/solanazero-banner.jpg deleted file mode 100644 index 9c67ea0..0000000 Binary files a/compression-docs/.gitbook/assets/solanazero-banner.jpg and /dev/null differ diff --git a/compression-docs/.gitbook/assets/test (1) (1).json b/compression-docs/.gitbook/assets/test (1) (1).json deleted file mode 100644 index af2c903..0000000 --- a/compression-docs/.gitbook/assets/test (1) (1).json +++ /dev/null @@ -1,137 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - $ref: '#/components/schemas/PaginatedAccountList' -components: - schemas: - Account: - type: object - required: - - hash - - discriminator - - data - - owner - - lamports - - slotUpdated - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - data: - type: string - discriminator: - type: integer - format: int64 - minimum: 0 - hash: - $ref: '#/components/schemas/Hash' - lamports: - type: integer - format: int64 - minimum: 0 - owner: - $ref: '#/components/schemas/SerializablePubkey' - seq: - type: integer - format: int64 - nullable: true - minimum: 0 - slotUpdated: - type: integer - format: int64 - minimum: 0 - tree: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - additionalProperties: false - Context: - type: object - required: - - slot - properties: - slot: - type: integer - format: int64 - minimum: 0 - additionalProperties: false - Hash: - type: string - format: byte - description: A 32-byte hash represented as a base58 string. - PaginatedAccountList: - type: object - required: - - items - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/Account' - additionalProperties: false - SerializablePubkey: - type: string - format: byte - description: A Solana public key represented as a base58 string. \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (1).json b/compression-docs/.gitbook/assets/test (1).json deleted file mode 100644 index cd911c5..0000000 --- a/compression-docs/.gitbook/assets/test (1).json +++ /dev/null @@ -1,95 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - $ref: '#/components/schemas/PaginatedAccountList' -components: - schemas: - Context: - type: object - required: - - slot - properties: - slot: - type: integer - format: int64 - minimum: 0 - additionalProperties: false - Hash: - type: string - format: byte - description: A 32-byte hash represented as a base58 string. - PaginatedAccountList: - type: object - required: - - items - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/Account' - additionalProperties: false - SerializablePubkey: - type: string - format: byte - description: A Solana public key represented as a base58 string \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (10).json b/compression-docs/.gitbook/assets/test (10).json deleted file mode 100644 index 0478b83..0000000 --- a/compression-docs/.gitbook/assets/test (10).json +++ /dev/null @@ -1,60 +0,0 @@ ---- -openapi: "3.0.3" -info: - title: "photon-indexer" - description: "Solana indexer for general compression" - license: - name: "Apache-2.0" - version: "0.4.0" -paths: - : - summary: "getCompressedAccountsByOwner" - post: - requestBody: - content: - application/json: - schema: - type: "object" - required: - - "jsonrpc" - - "id" - - "method" - - "params" - properties: - id: - type: "string" - description: "An ID to identify the request." - enum: - - "string" - jsonrpc: - type: "string" - description: "The version of the JSON-RPC protocol." - enum: - - "2.0" - method: - type: "string" - description: "The name of the DAS method to invoke." - enum: - - "getCompressedAccountsByOwner" - params: - type: "array" - items: - type: "object" - description: "" - maxItems: "2" - minItems: "2" - responses: -components: - schemas: - CompressedAccountRequest: - type: "object" - properties: - address: - allOf: - - $ref: "#/components/schemas/SerializablePubkey" - nullable: "true" - hash: - allOf: - - $ref: "#/components/schemas/Hash" - nullable: "true" - additionalProperties: "false" \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (2).json b/compression-docs/.gitbook/assets/test (2).json deleted file mode 100644 index 0478b83..0000000 --- a/compression-docs/.gitbook/assets/test (2).json +++ /dev/null @@ -1,60 +0,0 @@ ---- -openapi: "3.0.3" -info: - title: "photon-indexer" - description: "Solana indexer for general compression" - license: - name: "Apache-2.0" - version: "0.4.0" -paths: - : - summary: "getCompressedAccountsByOwner" - post: - requestBody: - content: - application/json: - schema: - type: "object" - required: - - "jsonrpc" - - "id" - - "method" - - "params" - properties: - id: - type: "string" - description: "An ID to identify the request." - enum: - - "string" - jsonrpc: - type: "string" - description: "The version of the JSON-RPC protocol." - enum: - - "2.0" - method: - type: "string" - description: "The name of the DAS method to invoke." - enum: - - "getCompressedAccountsByOwner" - params: - type: "array" - items: - type: "object" - description: "" - maxItems: "2" - minItems: "2" - responses: -components: - schemas: - CompressedAccountRequest: - type: "object" - properties: - address: - allOf: - - $ref: "#/components/schemas/SerializablePubkey" - nullable: "true" - hash: - allOf: - - $ref: "#/components/schemas/Hash" - nullable: "true" - additionalProperties: "false" \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (3).json b/compression-docs/.gitbook/assets/test (3).json deleted file mode 100644 index 1e72af1..0000000 --- a/compression-docs/.gitbook/assets/test (3).json +++ /dev/null @@ -1,59 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: {} -components: - schemas: - CompressedAccountRequest: - type: object - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - hash: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - additionalProperties: false \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (4).json b/compression-docs/.gitbook/assets/test (4).json deleted file mode 100644 index ec182f6..0000000 --- a/compression-docs/.gitbook/assets/test (4).json +++ /dev/null @@ -1,75 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - $ref: '#/components/schemas/T' -components: - schemas: - CompressedAccountRequest: - type: object - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - hash: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - additionalProperties: false \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (5).json b/compression-docs/.gitbook/assets/test (5).json deleted file mode 100644 index ab851fe..0000000 --- a/compression-docs/.gitbook/assets/test (5).json +++ /dev/null @@ -1,60 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - type: string - description: An ID to identify the request. diff --git a/compression-docs/.gitbook/assets/test (6).json b/compression-docs/.gitbook/assets/test (6).json deleted file mode 100644 index cd911c5..0000000 --- a/compression-docs/.gitbook/assets/test (6).json +++ /dev/null @@ -1,95 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccountsByOwner - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccountsByOwner - params: - type: array - items: - type: object - description: '' - maxItems: 2 - minItems: 2 - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - $ref: '#/components/schemas/PaginatedAccountList' -components: - schemas: - Context: - type: object - required: - - slot - properties: - slot: - type: integer - format: int64 - minimum: 0 - additionalProperties: false - Hash: - type: string - format: byte - description: A 32-byte hash represented as a base58 string. - PaginatedAccountList: - type: object - required: - - items - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/Account' - additionalProperties: false - SerializablePubkey: - type: string - format: byte - description: A Solana public key represented as a base58 string \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (7).json b/compression-docs/.gitbook/assets/test (7).json deleted file mode 100644 index 7174070..0000000 --- a/compression-docs/.gitbook/assets/test (7).json +++ /dev/null @@ -1,242 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getMultipleCompressedAccountProofs - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getMultipleCompressedAccountProofs - params: - type: array - items: - $ref: '#/components/schemas/Hash' - required: true - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - type: array - items: - $ref: '#/components/schemas/MerkleProofWithContext' -components: - schemas: - Account: - type: object - required: - - hash - - discriminator - - data - - owner - - lamports - - slotUpdated - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - data: - type: string - discriminator: - type: integer - format: int64 - minimum: 0 - hash: - $ref: '#/components/schemas/Hash' - lamports: - type: integer - format: int64 - minimum: 0 - owner: - $ref: '#/components/schemas/SerializablePubkey' - seq: - type: integer - format: int64 - nullable: true - minimum: 0 - slotUpdated: - type: integer - format: int64 - minimum: 0 - tree: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - additionalProperties: false - Context: - type: object - required: - - slot - properties: - slot: - type: integer - format: int64 - minimum: 0 - additionalProperties: false - Hash: - type: string - format: byte - description: A 32-byte hash represented as a base58 string. - Limit: - type: integer - format: int64 - minimum: 0 - MerkleProofWithContext: - type: object - required: - - proof - - leafIndex - - hash - - merkleTree - properties: - hash: - $ref: '#/components/schemas/Hash' - leafIndex: - type: integer - format: int32 - minimum: 0 - merkleTree: - $ref: '#/components/schemas/SerializablePubkey' - proof: - type: array - items: - $ref: '#/components/schemas/Hash' - additionalProperties: false - PaginatedAccountList: - type: object - required: - - items - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/Account' - additionalProperties: false - SerializablePubkey: - type: string - format: byte - description: A Solana public key represented as a base58 string. - TokenAcccount: - type: object - required: - - hash - - owner - - mint - - amount - - isNative - - frozen - - data - - discriminator - - lamports - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - amount: - type: integer - format: int64 - minimum: 0 - closeAuthority: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - data: - type: string - format: binary - delegate: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - discriminator: - type: integer - format: int64 - minimum: 0 - frozen: - type: boolean - hash: - $ref: '#/components/schemas/Hash' - isNative: - type: boolean - lamports: - type: integer - format: int64 - minimum: 0 - mint: - $ref: '#/components/schemas/SerializablePubkey' - owner: - $ref: '#/components/schemas/SerializablePubkey' - seq: - type: integer - format: int64 - nullable: true - minimum: 0 - tree: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - additionalProperties: false - TokenAccountBalance: - type: object - required: - - amount - properties: - amount: - type: string - additionalProperties: false - TokenAccountList: - type: object - required: - - items - properties: - cursor: - type: string - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/TokenAcccount' diff --git a/compression-docs/.gitbook/assets/test (8).json b/compression-docs/.gitbook/assets/test (8).json deleted file mode 100644 index 12f40af..0000000 --- a/compression-docs/.gitbook/assets/test (8).json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "photon-indexer", - "description": "Solana indexer for general compression", - "license": { - "name": "Apache-2.0" - }, - "version": "0.4.0" - }, - "paths": { - "": { - "summary": "getCompressedAccountsByOwner", - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "jsonrpc", - "id", - "method", - "params" - ], - "properties": { - "id": { - "type": "string", - "description": "An ID to identify the request.", - "enum": [ - "string" - ] - }, - "jsonrpc": { - "type": "string", - "description": "The version of the JSON-RPC protocol.", - "enum": [ - "2.0" - ] - }, - "method": { - "type": "string", - "description": "The name of the DAS method to invoke.", - "enum": [ - "getCompressedAccountsByOwner" - ] - }, - "params": { - "type": "array", - "items": { - "type": "object" - }, - "description": "", - "maxItems": 2, - "minItems": 2 - } - } - } - } - } - }, - "responses": {} - } - } - }, - "components": { - "schemas": { - "CompressedAccountRequest": { - "type": "object", - "properties": { - "address": { - "allOf": [ - { - "$ref": "#/components/schemas/SerializablePubkey" - } - ], - "nullable": true - }, - "hash": { - "allOf": [ - { - "$ref": "#/components/schemas/Hash" - } - ], - "nullable": true - } - }, - "additionalProperties": false - } - } - } -} \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/test (9).json b/compression-docs/.gitbook/assets/test (9).json deleted file mode 100644 index 579b0df..0000000 --- a/compression-docs/.gitbook/assets/test (9).json +++ /dev/null @@ -1,539 +0,0 @@ -openapi: 3.0.0 -info: - title: Helius API - version: 1.0.0 -servers: - - url: https://mainnet.helius-rpc.com -paths: - /?api-key=: - post: - tags: - - RPC - summary: getAsset - operationId: rpc - parameters: - - name: api-key - in: query - description: The API key. - required: true - schema: - type: string - example: 'string' - requestBody: - content: - application/json: - schema: - type: object - properties: - jsonrpc: - type: string - enum: ['2.0'] - description: The version of the JSON-RPC protocol. - id: - type: string - description: An ID to identify the request. - method: - type: string - enum: ['getAsset'] - description: The name of the DAS method to invoke. - params: - type: object - properties: - id: - type: string - description: The ID of the asset to retrieve. - example: 'string' - options: - type: object - description: The display options for the response. - properties: - showUnverifiedCollections: - type: boolean - description: Displays grouping information for unverified collections instead of skipping them. - showCollectionMetadata: - type: boolean - description: Displays metadata for the collection. - showFungible: - type: boolean - description: Displays fungible tokens held by the owner. - default: false - showInscription: - type: boolean - description: Displays inscription details of assets inscribed on-chain. - default: false - required: - - jsonrpc - - id - - method - - params - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - interface: - type: string - description: The interface of the asset. - enum: - [ - 'V1_NFT', - 'V1_PRINT', - 'LEGACY_NFT', - 'V2_NFT', - 'FungibleAsset', - 'FungibleToken', - 'Custom', - 'Identity', - 'Executable', - 'ProgrammableNFT', - ] - example: 'V1_NFT' - id: - type: string - description: The ID of the asset. - example: 'string' - content: - type: object - description: The content of the asset. - properties: - $schema: - type: string - json_uri: - type: string - files: - type: array - items: - type: object - items: - type: object - metadata: - type: object - properties: - attributes: - type: array - items: - type: object - description: - type: string - name: - type: string - symbol: - type: string - links: - type: object - description: Links related to the asset. - properties: - external_url: - type: string - description: External URL of the asset. - authorities: - type: object - properties: - address: - type: string - scopes: - type: array - items: - type: string - description: Scopes of the authority. - compression: - type: object - properties: - eligible: - type: boolean - compressed: - type: boolean - data_hash: - type: string - creator_hash: - type: string - asset_hash: - type: string - tree: - type: string - seq: - type: integer - leaf_id: - type: integer - grouping: - type: object - properties: - group_key: - type: string - group_value: - type: string - royalty: - type: object - properties: - royalty_model: - type: string - target: - type: string - nullable: true - percent: - type: number - basis_points: - type: integer - primary_sale_happened: - type: boolean - locked: - type: boolean - creators: - type: object - properties: - address: - type: string - share: - type: integer - verified: - type: boolean - ownership: - type: object - properties: - frozen: - type: boolean - delegated: - type: boolean - delegate: - type: string - nullable: true - ownership_model: - type: string - owner: - type: string - supply: - type: string - nullable: true - mutable: - type: boolean - burnt: - type: boolean - mint_extensions: - type: object - description: The mint extensions details for Token2022. - properties: - confidential_transfer_mint: - type: object - description: Confidential transfer mint details. - properties: - authority: - type: string - description: Authority responsible for the confidential transfer. - auto_approve_new_accounts: - type: boolean - description: Auto-approval status of new accounts. - auditor_elgamal_pubkey: - type: string - description: ElGamal public key of the auditor. - confidential_transfer_fee_config: - type: object - description: Configuration details for confidential transfer fees. - properties: - authority: - type: string - description: Authority responsible for the fee configuration. - withdraw_withheld_authority_elgamal_pubkey: - type: string - description: ElGamal public key for authority to withdraw withheld amounts. - harvest_to_mint_enabled: - type: boolean - description: Status indicating if harvesting to mint is enabled. - withheld_amount: - type: string - description: Amount withheld during confidential transfers. - transfer_fee_config: - type: object - description: Transfer fee configuration details. - properties: - transfer_fee_config_authority: - type: string - description: Authority responsible for transfer fee configuration. - withdraw_withheld_authority: - type: string - description: Authority to withdraw withheld amounts. - withheld_amount: - type: integer - description: Amount withheld in transfers. - older_transfer_fee: - type: object - description: Details of the older transfer fee structure. - properties: - epoch: - type: string - description: Authority responsible for transfer fee configuration. - maximum_fee: - type: string - description: The old maximum fee for the transfer. - transfer_fee_basis_points: - type: string - description: The old transfer fee basis points. - newer_transfer_fee: - type: object - description: Details of the newer transfer fee structure. - properties: - epoch: - type: string - description: Authority responsible for transfer fee - metadata_pointer: - type: object - description: Metadata pointer details. - properties: - authority: - type: string - description: Authority responsible for transfer fee configuration. - metadata_address: - type: string - description: Authority to withdraw withheld amounts. - mint_close_authority: - type: object - description: Mint close authority details. - properties: - close_authority: - type: string - description: Authority responsible for closing mint. - permanent_delegate: - type: object - description: Permanent delegate details. - properties: - delegate: - type: string - description: Delegate public key. - transfer_hook: - type: object - description: Transfer hook details. - properties: - authority: - type: string - description: Authority responsible for the transfer hook. - program_id: - type: string - description: Program ID related to the transfer hook. - interest_bearing_config: - type: object - description: Interest bearing configuration details. - properties: - rate_authority: - type: string - description: Authority responsible for setting the interest rate. - initialization_timestamp: - type: integer - description: Timestamp of interest rate initialization. - pre_update_average_rate: - type: integer - description: Average rate before the latest update. - last_update_timestamp: - type: integer - description: Timestamp of the last interest rate update. - current_rate: - type: integer - description: Current interest rate. - default_account_state: - type: string - description: The default state of the account. - properties: - state: - type: string - description: The state of the account. - confidential_transfer_account: - type: object - description: Details of the confidential transfer account. - properties: - approved: - type: boolean - description: Indicates whether the account is approved. - elgamal_pubkey: - type: string - description: ElGamal public key associated with the account. - pending_balance_lo: - type: string - description: Lower part of the encrypted pending balance. - pending_balance_hi: - type: string - description: Higher part of the encrypted pending balance. - available_balance: - type: string - description: Encrypted available balance of the account. - decryptable_available_balance: - type: string - description: Decryptable available balance for the account. - allow_confidential_credits: - type: boolean - description: Indicates if the account allows confidential credits. - allow_non_confidential_credits: - type: boolean - description: Indicates if the account allows non-confidential credits. - pending_balance_credit_counter: - type: integer - description: Counter for the number of pending balance credits. - maximum_pending_balance_credit_counter: - type: integer - description: Maximum allowed counter for pending balance credits. - expected_pending_balance_credit_counter: - type: integer - description: Expected counter value for pending balance credits. - actual_pending_balance_credit_counter: - type: integer - description: Actual counter value for pending balance credits. - metadata: - type: object - description: Metadata details. - properties: - update_authority: - type: string - description: Optional non-zero public key of the update authority. - mint: - type: string - description: Public key of the mint. - name: - type: string - description: Name associated with the metadata. - symbol: - type: string - description: Symbol associated with the metadata. - uri: - type: string - description: URI pointing to the metadata resources. - additional_metadata: - type: array - description: A list of additional metadata pairs. - items: - type: object - properties: - key: - type: string - description: Key of the metadata pair. - value: - type: string - description: Value of the metadata pair. - supply: - type: object - description: Supply details of master or edition NFT. - nullable: true - properties: - print_max_supply: - type: integer - description: The maximum printable NFTs. - print_current_supply: - type: integer - description: The current printed NFTs. - edition_nonce: - type: integer - description: The nonce of the edition. - edition_number: - type: integer - description: The number of the edition. - nullable: true - master_edition_mint: - type: string - description: The master edition mint. - nullable: true - token_info: - type: object - description: Details about the specific token. - properties: - symbol: - type: string - description: The symbol of the token. - supply: - type: integer - description: The total supply of the token. - decimals: - type: integer - description: The number of decimals the token uses. - token_program: - type: string - description: The tokens program ID. - price_info: - type: object - description: Information about the token's price. - properties: - price_per_token: - type: number - description: The price per individual token. - currency: - type: string - description: The currency in which the token's price is denoted. - inscription: - type: object - properties: - order: - type: integer - description: The order of the inscription. - size: - type: integer - description: The size of the inscription. - contentType: - type: string - description: The content type of the inscription. - encoding: - type: string - description: The encoding of the inscription. - validationHash: - type: string - description: The validation hash of the inscription. - inscriptionDataAccount: - type: string - description: The inscription data account. - authority: - type: string - description: The authority of the inscription. - spl20: - type: object - 400: - description: Invalid request. - content: - application/json: - schema: - type: object - properties: - error: - type: string - - 401: - description: Unauthorized request. - content: - application/json: - schema: - type: object - properties: - error: - type: string - 403: - description: Request was forbidden. - content: - application/json: - schema: - type: object - properties: - error: - type: string - 404: - description: The specified resource was not found. - content: - application/json: - schema: - type: object - properties: - error: - type: string - 429: - description: Exceeded rate limit.. - content: - application/json: - schema: - type: object - properties: - error: - type: string - 500: - description: The server encountered an unexpected condition that prevented it from fulfilling the request. - content: - application/json: - schema: - type: object - properties: - error: - type: string diff --git a/compression-docs/.gitbook/assets/test.json b/compression-docs/.gitbook/assets/test.json deleted file mode 100644 index 2c77b58..0000000 --- a/compression-docs/.gitbook/assets/test.json +++ /dev/null @@ -1,238 +0,0 @@ -openapi: 3.0.3 -info: - title: photon-indexer - description: Solana indexer for general compression - license: - name: Apache-2.0 - version: 0.4.0 -servers: -- url: http://127.0.0.1 -paths: - /: - summary: getCompressedAccount - post: - requestBody: - content: - application/json: - schema: - type: object - required: - - jsonrpc - - id - - method - - params - properties: - id: - type: string - description: An ID to identify the request. - enum: - - string - jsonrpc: - type: string - description: The version of the JSON-RPC protocol. - enum: - - '2.0' - method: - type: string - description: The name of the DAS method to invoke. - enum: - - getCompressedAccount - params: - $ref: '#/components/schemas/Hash' - required: true - responses: - '200': - description: '' - content: - application/json: - schema: - type: object - required: - - context - - value - properties: - context: - $ref: '#/components/schemas/Context' - value: - $ref: '#/components/schemas/Account' -components: - schemas: - Account: - type: object - required: - - hash - - discriminator - - data - - owner - - lamports - - slotUpdated - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - data: - type: string - discriminator: - type: integer - format: int64 - minimum: 0 - hash: - $ref: '#/components/schemas/Hash' - lamports: - type: integer - format: int64 - minimum: 0 - owner: - $ref: '#/components/schemas/SerializablePubkey' - seq: - type: integer - format: int64 - nullable: true - minimum: 0 - slotUpdated: - type: integer - format: int64 - minimum: 0 - tree: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - additionalProperties: false - Context: - type: object - required: - - slot - properties: - slot: - type: integer - format: int64 - minimum: 0 - additionalProperties: false - Hash: - type: string - format: byte - description: A 32-byte hash represented as a base58 string. - Limit: - type: integer - format: int64 - minimum: 0 - MerkleProofWithContext: - type: object - required: - - proof - - leafIndex - - hash - - merkleTree - properties: - hash: - $ref: '#/components/schemas/Hash' - leafIndex: - type: integer - format: int32 - minimum: 0 - merkleTree: - $ref: '#/components/schemas/SerializablePubkey' - proof: - type: array - items: - $ref: '#/components/schemas/Hash' - additionalProperties: false - PaginatedAccountList: - type: object - required: - - items - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Hash' - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/Account' - additionalProperties: false - SerializablePubkey: - type: string - format: byte - description: A Solana public key represented as a base58 string. - TokenAcccount: - type: object - required: - - hash - - owner - - mint - - amount - - isNative - - frozen - - data - - discriminator - - lamports - properties: - address: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - amount: - type: integer - format: int64 - minimum: 0 - closeAuthority: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - data: - type: string - format: binary - delegate: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - discriminator: - type: integer - format: int64 - minimum: 0 - frozen: - type: boolean - hash: - $ref: '#/components/schemas/Hash' - isNative: - type: boolean - lamports: - type: integer - format: int64 - minimum: 0 - mint: - $ref: '#/components/schemas/SerializablePubkey' - owner: - $ref: '#/components/schemas/SerializablePubkey' - seq: - type: integer - format: int64 - nullable: true - minimum: 0 - tree: - allOf: - - $ref: '#/components/schemas/SerializablePubkey' - nullable: true - additionalProperties: false - TokenAccountBalance: - type: object - required: - - amount - properties: - amount: - type: string - additionalProperties: false - TokenAccountList: - type: object - required: - - items - properties: - cursor: - type: string - nullable: true - items: - type: array - items: - $ref: '#/components/schemas/TokenAcccount' \ No newline at end of file diff --git a/compression-docs/.gitbook/assets/zkc24-ezgif.com-video-to-gif-converter.gif b/compression-docs/.gitbook/assets/zkc24-ezgif.com-video-to-gif-converter.gif deleted file mode 100644 index 0816c5d..0000000 Binary files a/compression-docs/.gitbook/assets/zkc24-ezgif.com-video-to-gif-converter.gif and /dev/null differ diff --git a/compression-docs/.gitbook/assets/zkcompression_1000x_banner.png b/compression-docs/.gitbook/assets/zkcompression_1000x_banner.png deleted file mode 100644 index 30e2dab..0000000 Binary files a/compression-docs/.gitbook/assets/zkcompression_1000x_banner.png and /dev/null differ diff --git a/compression-docs/README.md b/compression-docs/README.md deleted file mode 100644 index 2191b68..0000000 --- a/compression-docs/README.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: - width: default - title: - visible: true - description: - visible: false - tableOfContents: - visible: true - outline: - visible: true - pagination: - visible: true ---- - -# Overview - -
- -## Welcome to ZK Compression - -ZK Compression is a new account primitive that lets developers store tokens and PDAs on Solana at a fraction of the cost, without sacrificing L1 performance or security. - -## State Cost Reduction - -
Creation CostRegular AccountCompressed Account
100-byte PDA Account~ 0.0016 SOL~ 0.00001 SOL (160x cheaper)
100 Token Accounts~ 0.2 SOL~ 0.00004 SOL (5000x cheaper)
- -## Core Features - -
Minimal state costSecurely stores state on cheaper ledger space instead of the more expensive account space, allowing apps to scale to millions of users.
L1 security & performanceExecution and data availability on Solana, preserving the performance and security guarantees of the L1
ComposableApps can mix and match between compressed and regular on-chain state, allowing atomic interaction with multiple programs, accounts, and compressed accounts.
- -## ZK and Compression in a Nutshell - -_**Compression**_**:** Only the [state roots](learn/core-concepts/state-trees.md) (small fingerprints of all [compressed accounts](learn/core-concepts/compressed-account-model.md)) are stored in on-chain accounts. The underlying data is stored on the cheaper Solana ledger. - -_**ZK**_**:** The protocol uses small zero-knowledge proofs ([validity proofs](learn/core-concepts/validity-proofs.md)) to ensure the integrity of the compressed state. This is all done under the hood. You can fetch validity proofs from [RPC providers](get-started/intro-to-development.md#rpc-connection) that support ZK Compression. - -## How to Use This Documentation - -This documentation introduces the ZK Compression primitive and guides you to relevant codebases and examples. - -{% hint style="info" %} -ZK Compression and its RPC implementation is built by [Light](https://twitter.com/LightProtocol) and [Helius](https://twitter.com/heliuslabs). -{% endhint %} diff --git a/compression-docs/SUMMARY.md b/compression-docs/SUMMARY.md index a53e3b9..8ec54e2 100644 --- a/compression-docs/SUMMARY.md +++ b/compression-docs/SUMMARY.md @@ -1,65 +1,91 @@ # Table of contents -## Get Started +* [Introduction](introduction.md) +* [Quickstart](quickstart.md) +* [Help](help.md) -* [Overview](README.md) -* [Intro to Development](get-started/intro-to-development.md) +## Compressed Tokens -## Developers +* [Overview](compressed-tokens/overview.md) +* [Cookbook](compressed-tokens/cookbook/README.md) + * [How to Create and Register a Mint Account for Compression](compressed-tokens/cookbook/how-to-create-and-register-a-mint-account-for-compression.md) + * [How to Create Compressed Token Accounts](compressed-tokens/cookbook/how-to-create-compressed-token-accounts.md) + * [How to Mint Compressed Tokens](compressed-tokens/cookbook/how-to-mint-compressed-tokens.md) + * [How to Transfer Compressed Token](compressed-tokens/cookbook/how-to-transfer-compressed-token.md) + * [How to compress and decompress SPL Tokens](compressed-tokens/cookbook/how-to-compress-and-decompress-spl-tokens.md) + * [How to Compress complete Token Accounts](compressed-tokens/cookbook/how-to-compress-complete-token-accounts.md) + * [How to Merge Compressed Token Accounts](compressed-tokens/cookbook/how-to-merge-compressed-token-accounts.md) + * [How to Create Compressed Token Pools for Mint Accounts](compressed-tokens/cookbook/how-to-create-compressed-token-pools-for-mint-accounts.md) + * [How to Transfer Compressed SOL](compressed-tokens/cookbook/how-to-transfer-compressed-sol.md) + * [How to approve and revoke delegate authority](compressed-tokens/cookbook/how-to-approve-and-revoke-delegate-authority.md) +* [Advanced Guides](compressed-tokens/advanced-guides/README.md) + * [Create an Airdrop](compressed-tokens/advanced-guides/create-an-airdrop.md) + * [Add Wallet Support for Compressed Tokens](compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.md) + * [Use Token 2022 with Compression](compressed-tokens/advanced-guides/use-token-2022-with-compression.md) -* [TypeScript Client](developers/typescript-client.md) -* [JSON RPC Methods](developers/json-rpc-methods/README.md) - * [getCompressedAccount](developers/json-rpc-methods/getcompressedaccount.md) - * [getCompressedBalance](developers/json-rpc-methods/getcompressedbalance.md) - * [getCompressedTokenAccountBalance](developers/json-rpc-methods/getcompressedtokenaccountbalance.md) - * [getCompressedBalanceByOwner](developers/json-rpc-methods/getcompressedbalancebyowner.md) - * [getCompressedMintTokenHolders](developers/json-rpc-methods/getcompressedminttokenholders.md) - * [getCompressedTokenBalancesByOwnerV2](developers/json-rpc-methods/getcompressedtokenbalancesbyownerv2.md) - * [getCompressedAccountsByOwner](developers/json-rpc-methods/getcompressedaccountsbyowner.md) - * [getMultipleCompressedAccounts](developers/json-rpc-methods/getmultiplecompressedaccounts.md) - * [getCompressedTokenAccountsByOwner](developers/json-rpc-methods/getcompressedtokenaccountsbyowner.md) - * [getCompressedTokenAccountsByDelegate](developers/json-rpc-methods/getcompressedtokenaccountsbydelegate.md) - * [getTransactionWithCompressionInfo](developers/json-rpc-methods/gettransactionwithcompressioninfo.md) - * [getCompressedAccountProof](developers/json-rpc-methods/getcompressedaccountproof.md) - * [getMultipleCompressedAccountProofs](developers/json-rpc-methods/getmultiplecompressedaccountproofs.md) - * [getMultipleNewAddressProofs](developers/json-rpc-methods/getmultiplenewaddressproofs.md) - * [getValidityProof](developers/json-rpc-methods/getvalidityproof.md) - * [getCompressionSignaturesForAccount](developers/json-rpc-methods/getcompressionsignaturesforaccount.md) - * [getCompressionSignaturesForAddress](developers/json-rpc-methods/getcompressionsignaturesforaddress.md) - * [getCompressionSignaturesForOwner](developers/json-rpc-methods/getcompressionsignaturesforowner.md) - * [getCompressionSignaturesForTokenOwner](developers/json-rpc-methods/getcompressionsignaturesfortokenowner.md) - * [getLatestCompressionSignatures](developers/json-rpc-methods/getlatestcompressionsignatures.md) - * [getLatestNonVotingSignatures](developers/json-rpc-methods/getlatestnonvotingsignatures.md) - * [getIndexerSlot](developers/json-rpc-methods/getindexerslot.md) - * [getIndexerHealth](developers/json-rpc-methods/getindexerhealth.md) -* [Addresses and URLs](developers/addresses-and-urls.md) -* [Creating Airdrops with Compressed Tokens](developers/creating-airdrops-with-compressed-tokens.md) -* [Using Token-2022](developers/using-token-2022.md) -* [Add Compressed Token Support to Your Wallet](developers/add-compressed-token-support-to-your-wallet.md) -* [Create programs with the program-template](developers/create-programs-with-the-program-template.md) +## Compressed PDAs + +* [Create a Program with Compressed PDAs](compressed-pdas/create-a-program-with-compressed-pdas.md) +* [Create a Program with Compressed PDAs](compressed-pdas/create-a-program-with-compressed-pdas-1.md) +* [Guides](compressed-pdas/guides/README.md) + * [Program Template Eric](compressed-pdas/guides/program-template-eric.md) + * [Regular vs Compressed PDA Implementation](compressed-pdas/guides/regular-vs-compressed-pda-implementation.md) + * [Create and Update Accounts](compressed-pdas/guides/create-and-update-accounts.md) + +## Resources + +* [CLI Installation](resources/cli-installation.md) +* [Addresses & URLs](resources/addresses-and-urls.md) +* [JSON RPC methods](resources/json-rpc-methods/README.md) + * [getCompressedAccount](resources/json-rpc-methods/getcompressedaccount.md) + * [getCompressedAccountsByOwner](resources/json-rpc-methods/getcompressedaccountsbyowner.md) + * [getCompressedBalance](resources/json-rpc-methods/getcompressedbalance.md) + * [getCompressedBalanceByOwner](resources/json-rpc-methods/getcompressedbalancebyowner.md) + * [getCompressedMintTokenHolders](resources/json-rpc-methods/getcompressedminttokenholders.md) + * [getCompressedTokenAccountBalances](resources/json-rpc-methods/getcompressedtokenaccountbalances.md) + * [getCompressedTokenAccountByDelegate](resources/json-rpc-methods/getcompressedtokenaccountbydelegate.md) + * [getCompressedTokenAccountsByOwner](resources/json-rpc-methods/getcompressedtokenaccountsbyowner.md) + * [getCompressedTokenBalancesByOwner](resources/json-rpc-methods/getcompressedtokenbalancesbyowner.md) + * [getCompressionSignaturesForAccount](resources/json-rpc-methods/getcompressionsignaturesforaccount.md) + * [getCompressionSignaturesForAddress](resources/json-rpc-methods/getcompressionsignaturesforaddress.md) + * [getCompressionSignaturesForOwner](resources/json-rpc-methods/getcompressionsignaturesforowner.md) + * [getCompressionSignaturesForTokenOwner](resources/json-rpc-methods/getcompressionsignaturesfortokenowner.md) + * [getIndexerHealth](resources/json-rpc-methods/getindexerhealth.md) + * [getIndexerSlot](resources/json-rpc-methods/getindexerslot.md) + * [getLatestCompressionSignatures](resources/json-rpc-methods/getlatestcompressionsignatures.md) + * [getLatestNonVotingSignatures](resources/json-rpc-methods/getlatestnonvotingsignatures.md) + * [getMultipleCompressedAccounts](resources/json-rpc-methods/getmultiplecompressedaccounts.md) + * [getMultipleNewAddressProofs](resources/json-rpc-methods/getmultiplenewaddressproofs.md) + * [getTransactionWithCompressionInfo](resources/json-rpc-methods/gettransactionwithcompressioninfo.md) + * [getValidityProof](resources/json-rpc-methods/getvalidityproof.md) +* SDKs + * [Client Development](resources/sdks/client-development.md) + * [Program Development](resources/sdks/program-development.md) ## Learn -* [In a Nutshell](learn/in-a-nutshell.md) +* [FAQ Overview](learn/faq-overview.md) * [Core Concepts](learn/core-concepts/README.md) * [Compressed Account Model](learn/core-concepts/compressed-account-model.md) - * [State Trees](learn/core-concepts/state-trees.md) - * [Validity Proofs](learn/core-concepts/validity-proofs.md) * [Lifecycle of a Transaction](learn/core-concepts/lifecycle-of-a-transaction.md) * [Limitations](learn/core-concepts/limitations.md) +* [Node Operators](learn/node-operators.md) + +## References + +* [Whitepaper](whitepaper.md) -## Node Operators +## Changelog -* [Run a Node](node-operators/run-a-node.md) +* [JS - 0.21.0](changelog/js-0.21.0.md) +* [Protocol - 1.0.0](changelog/protocol-1.0.0.md) +* [0.6.0](changelog/0.6.0.md) +* [0.5.0](changelog/0.5.0.md) +* [0.4.0](changelog/0.4.0.md) +* [0.3.0](changelog/0.3.0.md) -## resources +## Links -* [Changelog](resources/changelog/README.md) - * [JS - 0.21.0](resources/changelog/js-0.21.0.md) - * [Protocol - 1.0.0](resources/changelog/protocol-1.0.0.md) - * [0.6.0](resources/changelog/0.6.0.md) - * [0.5.0](resources/changelog/0.5.0.md) - * [0.4.0](resources/changelog/0.4.0.md) - * [0.3.0](resources/changelog/0.3.0.md) -* [Security](resources/security.md) -* [Privacy Policy](resources/privacy-policy.md) +* [GitHub](https://github.com/Lightprotocol/light-protocol) +* [Discord](https://discord.com/invite/CYvjBgzRFP) +* [Changelog](https://github.com/Lightprotocol/light-protocol/releases) diff --git a/docs/developers/release-notes/0.3.0.md b/compression-docs/changelog/0.3.0.md similarity index 95% rename from docs/developers/release-notes/0.3.0.md rename to compression-docs/changelog/0.3.0.md index d06d308..46d57f7 100644 --- a/docs/developers/release-notes/0.3.0.md +++ b/compression-docs/changelog/0.3.0.md @@ -3,7 +3,7 @@ The first pre-release is here! {% hint style="info" %} -_Note: It’s an alpha release, so bugs & breaking changes can occur. If you have questions, bugs, or feedback, check out the_ [_Helius_](https://discord.gg/Uzzf6a7zKr) _and_ [_Light_](https://discord.gg/CYvjBgzRFP) _Developer Discord for help, or message us directly via_ [_Telegram_](https://t.me/swen\_light) +_Note: It’s an alpha release, so bugs & breaking changes can occur. If you have questions, bugs, or feedback, check out the_ [_Helius_](https://discord.gg/Uzzf6a7zKr) _and_ [_Light_](https://discord.gg/CYvjBgzRFP) _Developer Discord for help, or message us directly via_ [_Telegram_](https://t.me/swen_light) {% endhint %} **You can find a list of all crates here:** diff --git a/compression-docs/resources/changelog/0.4.0.md b/compression-docs/changelog/0.4.0.md similarity index 100% rename from compression-docs/resources/changelog/0.4.0.md rename to compression-docs/changelog/0.4.0.md diff --git a/compression-docs/resources/changelog/0.5.0.md b/compression-docs/changelog/0.5.0.md similarity index 97% rename from compression-docs/resources/changelog/0.5.0.md rename to compression-docs/changelog/0.5.0.md index 8c8b8dd..c9b38c8 100644 --- a/compression-docs/resources/changelog/0.5.0.md +++ b/compression-docs/changelog/0.5.0.md @@ -14,7 +14,7 @@ You can find a detailed list of all changes here: ## Migrating from zkTestnet -1. The same `programIds` and account addresses as zkTestnet are used. The full details are listed in the [Devnet Addresses](../../developers/addresses-and-urls.md) section +1. The same `programIds` and account addresses as zkTestnet are used. The full details are listed in the [Devnet Addresses](../../resources/addresses-and-urls.md) section 2. Airdrop yourself some Devnet SOL via the Solana CLI and deploy your program to the public Solana Devnet cluster 3. :tada: 4. If you have any issues migrating or deploying your program on Devnet, just [let us know](https://t.me/swen_light)! diff --git a/compression-docs/resources/changelog/0.6.0.md b/compression-docs/changelog/0.6.0.md similarity index 84% rename from compression-docs/resources/changelog/0.6.0.md rename to compression-docs/changelog/0.6.0.md index b8ab280..eca7127 100644 --- a/compression-docs/resources/changelog/0.6.0.md +++ b/compression-docs/changelog/0.6.0.md @@ -19,7 +19,7 @@ You can find a detailed list of all changes here: ### Upgrading from v0.5.0 -* Redeploy your programs on devnet pointing to the [**new program IDs**](../../developers/addresses-and-urls.md#program-ids-and-accounts-from-27th-aug-2024-onward). You may also need to update your JS client's use of `getCompressedAccounts` according to the breaking changes highlighted above. +* Redeploy your programs on devnet pointing to the [**new program IDs**](../../resources/addresses-and-urls.md#program-ids-and-accounts-from-27th-aug-2024-onward). You may also need to update your JS client's use of `getCompressedAccounts` according to the breaking changes highlighted above. ### Examples diff --git a/compression-docs/resources/changelog/js-0.21.0.md b/compression-docs/changelog/js-0.21.0.md similarity index 100% rename from compression-docs/resources/changelog/js-0.21.0.md rename to compression-docs/changelog/js-0.21.0.md diff --git a/compression-docs/resources/changelog/protocol-1.0.0.md b/compression-docs/changelog/protocol-1.0.0.md similarity index 100% rename from compression-docs/resources/changelog/protocol-1.0.0.md rename to compression-docs/changelog/protocol-1.0.0.md diff --git a/compression-docs/developers/create-programs-with-the-program-template.md b/compression-docs/compressed-pdas/create-a-program-with-compressed-pdas-1.md similarity index 71% rename from compression-docs/developers/create-programs-with-the-program-template.md rename to compression-docs/compressed-pdas/create-a-program-with-compressed-pdas-1.md index f7310b3..b820861 100644 --- a/compression-docs/developers/create-programs-with-the-program-template.md +++ b/compression-docs/compressed-pdas/create-a-program-with-compressed-pdas-1.md @@ -1,18 +1,38 @@ -# Create programs with the program-template -We've created a **program template** that you can use to bootstrap your program. -{% hint style="info" %} -_Note the program-template as well as the light-sdk and proc macros are experimental convenience helpers, and their APIs are subject to change._ -{% endhint %} +# Create a Program with Compressed PDAs + +*** + +### Prerequisites + +* Rust and Cargo installed +* Solana CLI (v1.18.15+) +* Anchor CLI (v0.30.0+) -### 1. Install the CLI +{% stepper %} +{% step %} +### Install the Light CLI -
Package ManagerCommand
npm
npm install -g @lightprotocol/zk-compression-cli
-
Yarn
yarn global add @lightprotocol/zk-compression-cli
-
+Choose your package manager: -### 2. **Initialize your program** +```bash +# npm +npm install -g @lightprotocol/zk-compression-cli + +# yarn +yarn global add @lightprotocol/zk-compression-cli +``` + +Verify installation: + +```bash +light --version +``` +{% endstep %} + +{% step %} +### Initialize Your Program {% hint style="info" %} Requires version **v0.15.1** or higher. @@ -23,35 +43,57 @@ light init testprogram ``` This initializes an anchor program with a basic counter program template using compressed accounts. +{% endstep %} + +{% step %} +### Build and Test Now, run: -``` +```bash anchor build ``` -``` +```bash cargo test-sbf ``` -### Common Errors +**Success markers:** + +* Build completes without errors +* Tests pass (4 tests: create, increment, decrement, close) +* See output: `test result: ok. 4 passed; 0 failed` +{% endstep %} + +{% step %} +### Success! + +You've built a program using compressed PDAs. +{% endstep %} +{% endstepper %} + +### Troubleshooting
- 'assert.h' file not found - during compilation.
+'assert.h' file not found - during compilation + +Build Errors on macOS + +If you see `'assert.h' file not found`: -```shellscript -Fix: -In your terminal, run: -1. export CC=$(xcrun -find clang) -2. export SDKROOT=$(xcrun --show-sdk-path) -3. cargo clean -4. anchor build +```bash +export CC=$(xcrun -find clang) +export SDKROOT=$(xcrun --show-sdk-path) +cargo clean +anchor build +``` +#### Example Log -Example log: The following warnings were emitted during compilation: +``` warning: blake3@1.5.1: In file included from c/blake3_neon.c:1: warning: blake3@1.5.1: c/blake3_impl.h:4:10: fatal error: 'assert.h' file not found warning: blake3@1.5.1: 4 | #include @@ -109,8 +151,22 @@ Caused by: error occurred: Command env -u IPHONEOS_DEPLOYMENT_TARGET "/Users/you/.local/share/solana/install/releases/1.18.22/solana-release/bin/sdk/sbf/dependencies/platform-tools/llvm/bin/clang" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "--target=arm64-apple-darwin" "-mmacosx-version-min=14.4" "-Wall" "-Wextra" "-std=c11" "-o" "/Users/you/testprogram/target/release/build/blake3-735a4c71d985df30/out/db3b6bfb95261072-blake3_neon.o" "-c" "c/blake3_neon.c" with args clang did not execute successfully (status code exit status: 1). ``` + +
-## Support +
+ +Test Failures + +Ensure you have: + +* Latest Solana CLI (v1.18.15+) +* Anchor CLI (v0.30.0+) +* `solana config set --url localhost` + +
+ + -For additional support or questions, please refer to our [documentation](https://www.zkcompression.com) or contact [Swen](https://t.me/swen\_light) or [Mert](https://t.me/mert\_helius) on Telegram or via [email](mailto:friends@lightprotocol.com) +*** diff --git a/compression-docs/compressed-pdas/create-a-program-with-compressed-pdas.md b/compression-docs/compressed-pdas/create-a-program-with-compressed-pdas.md new file mode 100644 index 0000000..7c2f935 --- /dev/null +++ b/compression-docs/compressed-pdas/create-a-program-with-compressed-pdas.md @@ -0,0 +1,218 @@ + + +# Create a Program with Compressed PDAs + +*** + +Compressed PDAs provide full functionality of accounts at PDAs, without per-account rent cost. + +
CreationRegular PDA AccountCompressed PDACost Reduction
100-byte PDA~ 0.0016 SOL~ 0.00001 SOL160x
+ +Like regular PDAs, compressed PDAs are consistently derived using a specific program address and seed. Different from regular PDAs, compressed PDAs + +* invoke the [Light System program](#user-content-fn-1)[^1] to create and update accounts, instead of the System program, and +* don't require space calculation and payer specification in the account struct. + +Compressed PDAs also work with standard Anchor patterns. You use the same seeds, bump constraints, and account validation patterns. + +### Compressed PDAs at a Glance + +

Rent free PDAs

Create accounts at program-derived addresses without upfront rent exempt balance.

Full PDA Functionality

Deterministic seed generation and program ownership.

Composable

CPI support between compressed and regular PDAs.
+ +## Start Building + +Developing with compressed PDAs works similar to regular PDAs and involves minimal setup: + +
+ +required versions + +Make sure you have the required versions installed and available in PATH: + +* **Rust**: 1.86.0 or later +* **Solana CLI**: 2.2.15 +* **Anchor CLI**: 0.31.1 + +- **Node.js**: 23.5.0 or later +- **Zk compression CLI**: 0.27.0 or later + +
+ +{% stepper %} +{% step %} +#### Prerequisite Setup + +{% hint style="info" %} +Make sure you installed Rust, the Solana CLI, and Anchor. Refer to this [setup guide](https://solana.com/developers/guides/getstarted/setup-local-development) for more help. +{% endhint %} + +Install the Light CLI: + +```bash +npm -g i @lightprotocol/zk-compression-cli +``` + +```bash +### verify installation +light --version +``` +{% endstep %} + +{% step %} +#### Initialize Your Program + +```bash +light init testprogram +``` + +{% hint style="info" %} +The `light init` command creates only Anchor-based projects . For Pinocchio programs, manually configure dependencies using `light-sdk-pinocchio`. +{% endhint %} + +This initializes an anchor program with a basic counter program template using compressed accounts with all required dependencies. +{% endstep %} + +{% step %} +#### Build and Test + +Now `cd testprogram` and run: + +```bash +anchor build +# Success: Finished `release` profile [optimized] target(s), after compiling. +# Note: Stack offset warnings are expected and don't prevent compilation +``` + +```bash +cargo test-sbf + +# Success: test result: ok. 1 passed; 0 failed; 0 ignored +``` +{% endstep %} +{% endstepper %} + +
+ +Light Protocol Libraries Used + +**Rust Crates** + +* `light-sdk` - Core SDK for compressed accounts in native and anchor programs +* `light-sdk-pinocchio` Core SDK for compressed accounts in pinocchio programs +* `light-hasher` - Hashing utilities for ZK compression +* `light-client` - RPC client and indexer for interacting with compressed accounts +* `light-program-test` - Testing utilities for compressed programs. + +**TypeScript/JavaScript Packages** + +* `@lightprotocol/stateless.js` - Client library for interacting with compressed accounts +* `@lightprotocol/zk-compression-cli` - Command-line tools for ZK compression development + +
+ +#### Common Errors + +
+ +'assert.h' file not found - during compilation. + +```shellscript +Fix: +In your terminal, run: +1. export CC=$(xcrun -find clang) +2. export SDKROOT=$(xcrun --show-sdk-path) +3. cargo clean +4. anchor build + + +Example log: +The following warnings were emitted during compilation: + +warning: blake3@1.5.1: In file included from c/blake3_neon.c:1: +warning: blake3@1.5.1: c/blake3_impl.h:4:10: fatal error: 'assert.h' file not found +warning: blake3@1.5.1: 4 | #include +warning: blake3@1.5.1: | ^~~~~~~~~~ +warning: blake3@1.5.1: 1 error generated. + +error: failed to run custom build command for `blake3 v1.5.1` + +Caused by: + process didn't exit successfully: `/Users/you/testprogram/target/release/build/blake3-ac41d29c2eabe052/build-script-build` (exit status: 1) + --- stdout + cargo:rerun-if-env-changed=CARGO_FEATURE_PURE + cargo:rerun-if-env-changed=CARGO_FEATURE_NO_NEON + cargo:rerun-if-env-changed=CARGO_FEATURE_NEON + cargo:rerun-if-env-changed=CARGO_FEATURE_NEON + cargo:rerun-if-env-changed=CARGO_FEATURE_NO_NEON + cargo:rerun-if-env-changed=CARGO_FEATURE_PURE + cargo:rustc-cfg=blake3_neon + OUT_DIR = Some(/Users/you/testprogram/target/release/build/blake3-735a4c71d985df30/out) + TARGET = Some(aarch64-apple-darwin) + OPT_LEVEL = Some(3) + HOST = Some(aarch64-apple-darwin) + cargo:rerun-if-env-changed=CC_aarch64-apple-darwin + CC_aarch64-apple-darwin = None + cargo:rerun-if-env-changed=CC_aarch64_apple_darwin + CC_aarch64_apple_darwin = None + cargo:rerun-if-env-changed=HOST_CC + HOST_CC = None + cargo:rerun-if-env-changed=CC + CC = Some(/Users/you/.local/share/solana/install/releases/1.18.22/solana-release/bin/sdk/sbf/dependencies/platform-tools/llvm/bin/clang) + RUSTC_WRAPPER = None + cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT + cargo:rerun-if-env-changed=CRATE_CC_NO_DEFAULTS + CRATE_CC_NO_DEFAULTS = None + DEBUG = Some(false) + cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET + MACOSX_DEPLOYMENT_TARGET = None + cargo:rerun-if-env-changed=CFLAGS_aarch64-apple-darwin + CFLAGS_aarch64-apple-darwin = None + cargo:rerun-if-env-changed=CFLAGS_aarch64_apple_darwin + CFLAGS_aarch64_apple_darwin = None + cargo:rerun-if-env-changed=HOST_CFLAGS + HOST_CFLAGS = None + cargo:rerun-if-env-changed=CFLAGS + CFLAGS = None + cargo:warning=In file included from c/blake3_neon.c:1: + cargo:warning=c/blake3_impl.h:4:10: fatal error: 'assert.h' file not found + cargo:warning= 4 | #include + cargo:warning= | ^~~~~~~~~~ + cargo:warning=1 error generated. + + --- stderr + + + error occurred: Command env -u IPHONEOS_DEPLOYMENT_TARGET "/Users/you/.local/share/solana/install/releases/1.18.22/solana-release/bin/sdk/sbf/dependencies/platform-tools/llvm/bin/clang" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "--target=arm64-apple-darwin" "-mmacosx-version-min=14.4" "-Wall" "-Wextra" "-std=c11" "-o" "/Users/you/testprogram/target/release/build/blake3-735a4c71d985df30/out/db3b6bfb95261072-blake3_neon.o" "-c" "c/blake3_neon.c" with args clang did not execute successfully (status code exit status: 1). +``` + +
+ +### More Examples + +#### Counter Program + +The counter program implements a compressed account lifecycle (create, increment, decrement, reset, close): + +* [**counter/anchor**](https://github.com/Lightprotocol/program-examples/blob/main/counter/anchor) - Anchor program with Rust and TypeScript tests +* [**counter/native**](https://github.com/Lightprotocol/program-examples/blob/main/counter/native) - Native Solana program with light-sdk and Rust tests. +* [**counter/pinocchio**](https://github.com/Lightprotocol/program-examples/blob/main/counter/pinocchio) - Pinocchio program with light-sdk-pinocchio and Rust tests. + +#### Create and Update Program + +* [**create-and-update**](https://github.com/Lightprotocol/program-examples/blob/main/create-and-update) - Create a new compressed account and update an existing compressed account with a single validity proof in one instruction. + +#### Solana vs compressed accounts comparison Program + +* [**account-comparison**](https://github.com/Lightprotocol/program-examples/blob/main/account-comparison) - Compare compressed vs regular Solana accounts. + +*** + +## Next Steps + +Get an overview of the SDKs for program development with ZK Compression. + +{% content-ref url="../resources/sdks/program-development.md" %} +[program-development.md](../resources/sdks/program-development.md) +{% endcontent-ref %} + +[^1]: The program enforces compressed account layout with ownership and sum checks, and is invoked to create and write to compressed accounts and PDAs. diff --git a/compression-docs/compressed-pdas/guides/create-and-update-accounts.md b/compression-docs/compressed-pdas/guides/create-and-update-accounts.md new file mode 100644 index 0000000..c9e2711 --- /dev/null +++ b/compression-docs/compressed-pdas/guides/create-and-update-accounts.md @@ -0,0 +1,333 @@ + + +# Create and Update Accounts + +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; +use borsh::{BorshDeserialize, BorshSerialize}; +use light_sdk::{ + account::LightAccount, + address::v1::derive_address, + cpi::{CpiAccounts, CpiInputs, CpiSigner}, + derive_light_cpi_signer, + instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof}, + LightDiscriminator, LightHasher, +}; + +declare_id!("HNqStLMpNuNJqhBF1FbGTKHEFbBLJmq8RdJJmZKWz6jH"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("HNqStLMpNuNJqhBF1FbGTKHEFbBLJmq8RdJJmZKWz6jH"); + +pub const FIRST_SEED: &[u8] = b"first"; +pub const SECOND_SEED: &[u8] = b"second"; + +#[program] +pub mod create_and_update { + + use super::*; + + /// Creates a new compressed account with initial data + pub fn create_compressed_account<'info>( + ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>, + proof: ValidityProof, + address_tree_info: PackedAddressTreeInfo, + output_state_tree_index: u8, + message: String, + ) -> Result<()> { + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.remaining_accounts, + crate::LIGHT_CPI_SIGNER, + ); + + let (address, address_seed) = derive_address( + &[FIRST_SEED, ctx.accounts.signer.key().as_ref()], + &address_tree_info + .get_tree_pubkey(&light_cpi_accounts) + .map_err(|_| ErrorCode::AccountNotEnoughKeys)?, + &crate::ID, + ); + + let mut data_account = LightAccount::<'_, DataAccount>::new_init( + &crate::ID, + Some(address), + output_state_tree_index, + ); + + data_account.owner = ctx.accounts.signer.key(); + data_account.message = message; + msg!( + "Created compressed account with message: {}", + data_account.message + ); + let cpi_inputs = CpiInputs::new_with_address( + proof, + vec![data_account.to_account_info().map_err(ProgramError::from)?], + vec![address_tree_info.into_new_address_params_packed(address_seed)], + ); + + cpi_inputs + .invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + Ok(()) + } + + /// Creates a new compressed account and updates an existing one in a single instruction + pub fn create_and_update<'info>( + ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>, + proof: ValidityProof, + existing_account: ExistingCompressedAccountIxData, + new_account: NewCompressedAccountIxData, + ) -> Result<()> { + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.remaining_accounts, + crate::LIGHT_CPI_SIGNER, + ); + + // Create new compressed account + let (new_address, new_address_seed) = derive_address( + &[SECOND_SEED, ctx.accounts.signer.key().as_ref()], + &new_account + .address_tree_info + .get_tree_pubkey(&light_cpi_accounts) + .map_err(|_| ErrorCode::AccountNotEnoughKeys)?, + &crate::ID, + ); + + let mut new_data_account = LightAccount::<'_, DataAccount>::new_init( + &crate::ID, + Some(new_address), + existing_account.account_meta.output_state_tree_index, + ); + new_data_account.owner = ctx.accounts.signer.key(); + new_data_account.message = new_account.message.clone(); + + let mut updated_data_account = LightAccount::<'_, DataAccount>::new_mut( + &crate::ID, + &existing_account.account_meta, + DataAccount { + owner: ctx.accounts.signer.key(), + message: existing_account.message.clone(), + }, + ) + .map_err(ProgramError::from)?; + + // Update the message + updated_data_account.message = existing_account.update_message.clone(); + + let cpi_inputs = CpiInputs::new_with_address( + proof, + vec![ + new_data_account + .to_account_info() + .map_err(ProgramError::from)?, + updated_data_account + .to_account_info() + .map_err(ProgramError::from)?, + ], + vec![new_account + .address_tree_info + .into_new_address_params_packed(new_address_seed)], + ); + + cpi_inputs + .invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + msg!( + "Created new account with message: '{}' and updated existing account to: '{}'", + new_account.message, + existing_account.update_message + ); + + Ok(()) + } + + /// Updates two existing compressed accounts in a single instruction + pub fn update_two_accounts<'info>( + ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>, + proof: ValidityProof, + first_account: ExistingCompressedAccountIxData, + second_account: ExistingCompressedAccountIxData, + ) -> Result<()> { + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.remaining_accounts, + crate::LIGHT_CPI_SIGNER, + ); + + // Update first compressed account + let mut updated_first_account = LightAccount::<'_, DataAccount>::new_mut( + &crate::ID, + &first_account.account_meta, + DataAccount { + owner: ctx.accounts.signer.key(), + message: first_account.message.clone(), + }, + ) + .map_err(ProgramError::from)?; + + // Update the message of the first account + updated_first_account.message = first_account.update_message.clone(); + + // Update second compressed account + let mut updated_second_account = LightAccount::<'_, DataAccount>::new_mut( + &crate::ID, + &second_account.account_meta, + DataAccount { + owner: ctx.accounts.signer.key(), + message: second_account.message.clone(), + }, + ) + .map_err(ProgramError::from)?; + + // Update the message of the second account + updated_second_account.message = second_account.update_message.clone(); + + let cpi_inputs = CpiInputs::new( + proof, + vec![ + updated_first_account + .to_account_info() + .map_err(ProgramError::from)?, + updated_second_account + .to_account_info() + .map_err(ProgramError::from)?, + ], + ); + + cpi_inputs + .invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + msg!( + "Updated first account to: '{}' and second account to: '{}'", + first_account.update_message, + second_account.update_message + ); + + Ok(()) + } + + /// Creates two new compressed accounts with different addresses in a single instruction + pub fn create_two_accounts<'info>( + ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>, + proof: ValidityProof, + address_tree_info: PackedAddressTreeInfo, + output_state_tree_index: u8, + byte_data: [u8; 31], + message: String, + ) -> Result<()> { + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.signer.as_ref(), + ctx.remaining_accounts, + crate::LIGHT_CPI_SIGNER, + ); + + // Create first compressed account + let (first_address, first_address_seed) = derive_address( + &[FIRST_SEED, ctx.accounts.signer.key().as_ref()], + &address_tree_info + .get_tree_pubkey(&light_cpi_accounts) + .map_err(|_| ErrorCode::AccountNotEnoughKeys)?, + &crate::ID, + ); + + let mut first_data_account = LightAccount::<'_, ByteDataAccount>::new_init( + &crate::ID, + Some(first_address), + output_state_tree_index, + ); + first_data_account.owner = ctx.accounts.signer.key(); + first_data_account.data = byte_data; + + // Create second compressed account + let (second_address, second_address_seed) = derive_address( + &[SECOND_SEED, ctx.accounts.signer.key().as_ref()], + &address_tree_info + .get_tree_pubkey(&light_cpi_accounts) + .map_err(|_| ErrorCode::AccountNotEnoughKeys)?, + &crate::ID, + ); + + let mut second_data_account = LightAccount::<'_, DataAccount>::new_init( + &crate::ID, + Some(second_address), + output_state_tree_index, + ); + second_data_account.owner = ctx.accounts.signer.key(); + second_data_account.message = message.clone(); + + let cpi_inputs = CpiInputs::new_with_address( + proof, + vec![ + first_data_account + .to_account_info() + .map_err(ProgramError::from)?, + second_data_account + .to_account_info() + .map_err(ProgramError::from)?, + ], + vec![ + address_tree_info.into_new_address_params_packed(first_address_seed), + address_tree_info.into_new_address_params_packed(second_address_seed), + ], + ); + + cpi_inputs + .invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + msg!( + "Created byte account with data: {:?} and string account with message: '{}'", + byte_data, + message + ); + + Ok(()) + } +} + +#[derive(Accounts)] +pub struct GenericAnchorAccounts<'info> { + #[account(mut)] + pub signer: Signer<'info>, +} + +#[derive( + Clone, Debug, Default, BorshSerialize, BorshDeserialize, LightDiscriminator, LightHasher, +)] +pub struct DataAccount { + #[hash] + pub owner: Pubkey, + #[hash] + pub message: String, +} + +#[derive( + Clone, Debug, Default, BorshSerialize, BorshDeserialize, LightDiscriminator, LightHasher, +)] +pub struct ByteDataAccount { + #[hash] + pub owner: Pubkey, + pub data: [u8; 31], +} + +#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] +pub struct ExistingCompressedAccountIxData { + pub account_meta: CompressedAccountMeta, + pub message: String, + pub update_message: String, +} + +#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] +pub struct NewCompressedAccountIxData { + pub address_tree_info: PackedAddressTreeInfo, + pub message: String, +} +``` diff --git a/compression-docs/compressed-pdas/guides/program-template-eric.md b/compression-docs/compressed-pdas/guides/program-template-eric.md new file mode 100644 index 0000000..61cfb33 --- /dev/null +++ b/compression-docs/compressed-pdas/guides/program-template-eric.md @@ -0,0 +1,156 @@ + +# Program Template + +## Program Template + +Get started quickly with our comprehensive template for building Solana programs that utilize compressed PDAs + +### Template Overview + +This template provides a complete foundation for building programs that leverage compressed PDAs, including: + +* **Account Management**: Efficient compressed PDA creation and updates +* **State Management**: Optimized state storage and retrieval +* **Proof Generation**: Built-in zero-knowledge proof handling +* **Cross-Program Invocations**: Integration with other compressed programs + + +#### 1. Clone the Template + +Copy + +``` +git clone https://github.com/Lightprotocol/compressed-pda-template +cd compressed-pda-template +``` + + +#### 2. Project Structure + +Copy + +``` +src/ +├── instructions/ # Program instructions +│ ├── initialize.rs # Initialize compressed PDA +│ ├── update.rs # Update compressed state +│ └── close.rs # Close compressed account +├── state/ # Account state definitions +│ └── compressed_account.rs +├── utils/ # Helper functions +│ └── compression.rs # Compression utilities +└── lib.rs # Main program entry +``` + + + +#### 3. Basic Implementation + +Copy + +``` +use anchor_lang::prelude::*; +use light_compressed_pda::CompressedPda; + +#[program] +pub mod compressed_pda_program { + use super::*; + + pub fn initialize_compressed_account( + ctx: Context, + data: Vec, + ) -> Result<()> { + // Initialize your compressed PDA here + ctx.accounts.compressed_pda.initialize(data)?; + Ok(()) + } + + pub fn update_compressed_account( + ctx: Context, + new_data: Vec, + ) -> Result<()> { + // Update compressed PDA state + ctx.accounts.compressed_pda.update(new_data)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InitializeCompressed<'info> { + #[account(mut)] + pub compressed_pda: Account<'info, CompressedPda>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + + + +### Key Features + + + +#### Compression Integration + +* **Automatic Proof Generation**: Built-in proof creation and verification +* **State Tree Management**: Efficient merkle tree operations +* **Batch Operations**: Process multiple PDAs in single transactions + + + +#### Development Tools + +* **Testing Suite**: Comprehensive test coverage +* **Deployment Scripts**: Automated deployment to devnet/mainnet +* **Monitoring**: Built-in account state monitoring + + + +#### Performance Optimizations + +* **Memory Efficient**: Minimal on-chain storage footprint +* **Gas Optimized**: Reduced transaction costs +* **Parallel Processing**: Concurrent PDA operations + +### Advanced Usage + +#### Custom State Structures + +Copy + +``` +#[account] +pub struct CustomCompressedState { + pub owner: Pubkey, + pub data: Vec, + pub metadata: StateMetadata, +} +``` + +#### Cross-Program Integration + +Copy + +``` +pub fn cross_program_call( + ctx: Context, +) -> Result<()> { + // Call other compressed programs + compressed_token::cpi::transfer( + ctx.accounts.to_cpi_context(), + amount, + )?; + Ok(()) +} +``` + +### Next Steps + +Ready to build? Download the complete template from our [GitHub repository](https://github.com/Lightprotocol/light-protocol) and start developing with compressed PDAs. + +* [**SDK Documentation**](https://luminouslabs.mintlify.app/integrate/rust-client): Detailed Rust SDK reference +* [**Examples**](https://luminouslabs.mintlify.app/examples): More complete implementation examples +* [**Community Support**](https://discord.gg/lightprotocol): Join our Discord for help and discussions + +[Overview](https://luminouslabs.mintlify.app/products/compressed-pdas/overview)[TypeScript](https://luminouslabs.mintlify.app/integrate/typescript-client)[github](https://github.com/Lightprotocol/light-protocol)[x](https://x.com/lightprotocol)[Powered by Mintlify](https://mintlify.com/preview-request?utm_campaign=poweredBy\&utm_medium=referral\&utm_source=luminouslabs) diff --git a/compression-docs/compressed-pdas/guides/regular-vs-compressed-pda-implementation.md b/compression-docs/compressed-pdas/guides/regular-vs-compressed-pda-implementation.md new file mode 100644 index 0000000..db88bfd --- /dev/null +++ b/compression-docs/compressed-pdas/guides/regular-vs-compressed-pda-implementation.md @@ -0,0 +1,267 @@ + + +# Regular vs Compressed PDA Implementation + +By the end of this guide you will have full understanding of differences of regular and compressed PDAs. + + + +1. Setup - 2 min + +* Clone example repository +* Install dependencies +* Start light test-validator + +2. Account Structure - 5 min + +```rust +// Both use same data structure + pub struct AccountData { + pub user: Pubkey, + pub name: String, + pub data: [u8; 128], + } + - Regular: #[account] macro + - Compressed: #[derive(LightDiscriminator, LightHasher)] with #[hash] attributes +``` + +3. Create Operations (10 min) + +* Regular: 5 lines with Anchor's init constraint +* Compressed: 30+ lines with: + * Address derivation + * LightAccount::new\_init() + * Proof handling + * CPI to Light System + +4. Update Operations (10 min) + +* Regular: Direct field assignment +* Compressed: Must provide: + * Current state (existing\_data) + * Validity proof + * Account metadata + * Authorization checks + +5. Client Implementation (5 min) + +* Regular: Simple RPC calls +* Compressed: Fetch account → Generate proof → Include data in transaction + +6. Cost & Performance Analysis (3 min) + +* Transaction size comparison +* Compute unit breakdown +* Break-even calculations + +7. When to Use Which (2 min) + +* Decision matrix based on real implementation complexity +* Migration considerations + +*** + +{% code title="program-examples/account-comparison + + + +" %} +```rust +use anchor_lang::prelude::*; +use light_sdk::{ + account::LightAccount, + address::v1::derive_address, + cpi::{CpiAccounts, CpiInputs, CpiSigner}, + derive_light_cpi_signer, + instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof}, + LightDiscriminator, LightHasher, +}; + +#[error_code] +pub enum CustomError { + #[msg("No authority to perform this action")] + Unauthorized, +} + +declare_id!("FYX4GmKJYzSiycc7XZKf12NGXNE9siSx1cJubYJniHcv"); + +const CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("FYX4GmKJYzSiycc7XZKf12NGXNE9siSx1cJubYJniHcv"); + +#[program] +pub mod account_comparison { + use light_sdk::error::LightSdkError; + + use super::*; + + pub fn create_account(ctx: Context, name: String) -> Result<()> { + let account = &mut ctx.accounts.account; + account.data = [1; 128]; + account.name = name; + account.user = *ctx.accounts.user.key; + + Ok(()) + } + + pub fn update_data(ctx: Context, data: [u8; 128]) -> Result<()> { + let account = &mut ctx.accounts.account; + account.data = data; + Ok(()) + } + + pub fn create_compressed_account<'info>( + ctx: Context<'_, '_, '_, 'info, CreateCompressedAccount<'info>>, + name: String, + proof: ValidityProof, + address_tree_info: PackedAddressTreeInfo, + output_tree_index: u8, + ) -> Result<()> { + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.user.as_ref(), + ctx.remaining_accounts, + CPI_SIGNER, + ); + + let (address, address_seed) = derive_address( + &[b"account", ctx.accounts.user.key().as_ref()], + &address_tree_info + .get_tree_pubkey(&light_cpi_accounts) + .map_err(|err| ProgramError::from(LightSdkError::from(err)))?, + &crate::ID, + ); + + // LightAccount::new_init will create an account with empty output state (no input state). + // Modifying the account will modify the output state that when converted to_account_info() + // is hashed with poseidon hashes, serialized with borsh + // and created with invoke_light_system_program by invoking the light-system-program. + // The hashing scheme is the account structure derived with LightHasher. + let mut compressed_account = LightAccount::<'_, CompressedAccountData>::new_init( + &crate::ID, + Some(address), + output_tree_index, + ); + + compressed_account.user = ctx.accounts.user.key(); + compressed_account.name = name; + compressed_account.data = [1u8; 128]; + + let new_address_params = address_tree_info.into_new_address_params_packed(address_seed); + + let cpi = CpiInputs::new_with_address( + proof, + vec![compressed_account + .to_account_info() + .map_err(ProgramError::from)?], + vec![new_address_params], + ); + cpi.invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + Ok(()) + } + + pub fn update_compressed_account<'info>( + ctx: Context<'_, '_, '_, 'info, UpdateCompressedAccount<'info>>, + new_data: [u8; 128], + existing_data: [u8; 128], + name: String, + proof: ValidityProof, + account_meta: CompressedAccountMeta, + ) -> Result<()> { + let mut compressed_account = LightAccount::<'_, CompressedAccountData>::new_mut( + &crate::ID, + &account_meta, + CompressedAccountData { + user: ctx.accounts.user.key(), + data: existing_data, + name, + }, + ) + .map_err(ProgramError::from)?; + + if compressed_account.user != ctx.accounts.user.key() { + return err!(CustomError::Unauthorized); + } + + compressed_account.data = new_data; + + let light_cpi_accounts = CpiAccounts::new( + ctx.accounts.user.as_ref(), + ctx.remaining_accounts, + CPI_SIGNER, + ); + + let cpi_inputs = CpiInputs::new( + proof, + vec![compressed_account + .to_account_info() + .map_err(ProgramError::from)?], + ); + + cpi_inputs + .invoke_light_system_program(light_cpi_accounts) + .map_err(ProgramError::from)?; + + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateAccount<'info> { + #[account(mut)] + pub user: Signer<'info>, + #[account(init, payer = user, space = 8 + 32 + 128 + 64, seeds = [b"account", user.key().as_ref()], bump)] + pub account: Account<'info, AccountData>, + pub system_program: Program<'info, System>, +} + +/// [0..8, 8..40,40..168,168..232] +#[account] +#[derive(Debug)] +pub struct AccountData { + pub user: Pubkey, + pub name: String, + pub data: [u8; 128], +} + +#[derive(Accounts)] +pub struct UpdateData<'info> { + #[account(mut, has_one = user)] + pub account: Account<'info, AccountData>, + #[account(mut)] + pub user: Signer<'info>, +} + +#[derive(Accounts)] +pub struct CreateCompressedAccount<'info> { + #[account(mut)] + pub user: Signer<'info>, +} + +#[derive(Accounts)] +pub struct UpdateCompressedAccount<'info> { + #[account(mut)] + pub user: Signer<'info>, +} + +#[derive(Clone, Debug, AnchorDeserialize, AnchorSerialize, LightDiscriminator, LightHasher)] +pub struct CompressedAccountData { + #[hash] + pub user: Pubkey, + #[hash] + pub name: String, + #[hash] + pub data: [u8; 128], +} + +impl Default for CompressedAccountData { + fn default() -> Self { + Self { + user: Pubkey::default(), + name: String::default(), + data: [0u8; 128], + } + } +} +``` +{% endcode %} diff --git a/compression-docs/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.md b/compression-docs/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.md new file mode 100644 index 0000000..6315230 --- /dev/null +++ b/compression-docs/compressed-tokens/advanced-guides/add-wallet-support-for-compressed-tokens.md @@ -0,0 +1,562 @@ + + +# Add Wallet Support for Compressed Tokens + +*** + +{% hint style="info" %} +This guide assumes you are familiar with the basics of Solana.\ +If you aren't, we recommend reading the following: + +* [Solana documentation](https://solana.com/docs/intro/dev) +* [The Solana Programming Model: An Introduction to Developing on Solana](https://www.helius.dev/blog/the-solana-programming-model-an-introduction-to-developing-on-solana) +{% endhint %} + +## What you will do + +With this guide you will add Compressed Token Support to Your Wallet Application. + +{% hint style="success" %} +Leading Solana Wallets like Phantom and Backpack already support compressed tokens. +{% endhint %} + +## Integration Steps + +{% stepper %} +{% step %} +### **Prerequisites** + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace \ with your API key before running. + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Display Compressed Token Balances + +This example fetches and displays all compressed token balances for a wallet address. + +```javascript +import { Rpc, createRpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +// 1. Setup RPC connection to local test validator +// 2. Call getCompressedTokenBalancesByOwnerV2() to fetch compressed token balances per mint +// 3. Display results with balance amounts and mint addresses + +const connection: Rpc = createRpc(); // defaults to localhost:8899 +const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG'); // public + +(async () => { + // Fetch compressed token balances for wallet address + // Returns balance for owner per mint - can optionally apply filter: {mint, limit, cursor} + const balances = await connection.getCompressedTokenBalancesByOwnerV2(publicKey); + console.log(balances); +})(); +``` +{% endstep %} + +{% step %} +### Get Transaction History + +This example retrieves compression transaction signatures and detailed transaction information for wallet transaction history display. + +```javascript +import { Rpc, createRpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +// 1. Setup RPC connection and fetch compression transaction signatures using getCompressionSignaturesForOwner() +// 2. Retrieve detailed transaction data with getTransactionWithCompressionInfo() including pre/post balances +// 3. Display transaction history with signature list and balance changes + +const connection: Rpc = createRpc(); // defaults to localhost:8899 +const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG'); + +(async () => { + // Fetch compression transaction signatures for wallet address + // Returns confirmed signatures for compression transactions involving the specified account owner + const signatures = await connection.getCompressionSignaturesForOwner(publicKey); + console.log(signatures); + + // Check if any signatures exist before trying to access them + if (signatures.items.length > 0) { + // Retrieve detailed transaction information with compression data + // Returns pre- and post-compressed token balances grouped by owner + const parsedTransaction = await connection.getTransactionWithCompressionInfo(signatures.items[0].signature); + console.log(parsedTransaction); + } else { + console.log("No compression transactions found for this address"); + } +})(); +``` +{% endstep %} + +{% step %} +### Send Compressed Tokens + +First, set up a test mint to and mint 10 compressed tokens to your filesystem wallet. + +
+ +Set up Test Mint + +```typescript +import { Keypair } from "@solana/web3.js"; +import { Rpc, confirmTx, createRpc } from '@lightprotocol/stateless.js'; +import { createMint, mintTo } from '@lightprotocol/compressed-token'; +import * as fs from 'fs'; +import * as os from 'os'; + +// 1. Setup RPC connection and load filesystem wallet for mint operations +// 2. Call createMint() to create SPL mint with token pool for compression +// 3. Call mintTo() to mint compressed tokens to filesystem wallet + +const connection: Rpc = createRpc(); // defaults to localhost:8899 + +// Load wallet from filesystem +const walletPath = `${os.homedir()}/.config/solana/id.json`; +const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); +const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); +const mintKeypair = Keypair.generate(); + +(async() => { + // Fund payer with SOL + await connection.requestAirdrop(payer.publicKey, 1e9); + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Create SPL mint with token pool for compression + const { mint, transactionSignature } = await createMint( + connection, + payer, + payer.publicKey, // mint authority + 9, + mintKeypair, + ); + console.log(`Mint address: ${mint.toString()}`); + console.log(`Transaction: ${transactionSignature}`); + + // Mint compressed tokens to payer + const mintToTxId = await mintTo( + connection, + payer, + mint, // SPL mint with token pool for compression + payer.publicKey, // recipient address + payer, + 10e9, + ); + + console.log(`\nMinted ${10e9 / 1e9} compressed token`); + console.log(`Transaction: ${mintToTxId}`); +})(); +``` + +
+ +{% hint style="success" %} +Make sure you add your Mint address to `send-tokens.ts`. +{% endhint %} + +
// Compressed Token Transfer - Local
+// 1. Load wallet and fetch compressed token accounts with getCompressedTokenAccountsByOwner()
+// 2. Select accounts for transfer using selectMinCompressedTokenAccountsForTransfer() 
+//    and get validity proof with getValidityProof()
+// 3. Create transfer instruction with CompressedTokenProgram.transfer() 
+//    and submit transaction with sendAndConfirmTx()
+// 4. Verify balances via getCompressedTokenAccountsByOwner()
+
+import {
+  Rpc,
+  createRpc,
+  bn,
+  dedupeSigner,
+  sendAndConfirmTx,
+  buildAndSignTx,
+} from "@lightprotocol/stateless.js";
+import {
+  CompressedTokenProgram,
+  selectMinCompressedTokenAccountsForTransfer,
+} from "@lightprotocol/compressed-token";
+import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";
+import * as fs from 'fs';
+import * as os from 'os';
+
+// Step 1: Setup RPC connection and define transfer parameters
+const connection: Rpc = createRpc(); // defaults to localhost:8899
+const mint = new PublicKey("MINT ADDRESS"); // Replace with mint address
+
+// Load wallet from filesystem
+const walletPath = `${os.homedir()}/.config/solana/id.json`;
+const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
+const payer = Keypair.fromSecretKey(Buffer.from(secretKey));
+const owner = payer;
+
+const recipient = Keypair.generate();
+const amount = bn(1e8);
+
+(async () => {
+  // Step 2: Fetch compressed account hashes from state trees
+  const compressedTokenAccounts =
+    await connection.getCompressedTokenAccountsByOwner(owner.publicKey, {
+      mint, // SPL mint with token pool for compression
+    });
+
+  if (compressedTokenAccounts.items.length === 0) {
+    console.log("No compressed token accounts found for this mint");
+    return;
+  }
+
+  // Show initial sender balance
+  const initialBalance = compressedTokenAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
+  console.log(`Sender balance: ${initialBalance / 1e8} compressed tokens`);
+
+  // Step 3: Select minimum compressed accounts for transfer amount
+  const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
+    compressedTokenAccounts.items,
+    amount
+  );
+
+  // Get validity proof for Merkle tree verification
+  const proof = await connection.getValidityProof(
+    inputAccounts.map((account) => account.compressedAccount.hash)
+  );
+
+  // Step 4: Create transfer instruction that consumes input accounts and creates new output accounts
+  const ix = await CompressedTokenProgram.transfer({
+    payer: payer.publicKey,
+    inputCompressedTokenAccounts: inputAccounts, // accounts to consume
+    toAddress: recipient.publicKey,
+    amount,
+    recentInputStateRootIndices: proof.rootIndices,
+    recentValidityProof: proof.compressedProof,
+  });
+
+  // Step 5: Build, sign, and submit transaction
+  const { blockhash } = await connection.getLatestBlockhash();
+  const additionalSigners = dedupeSigner(payer, [owner]);
+  const signedTx = buildAndSignTx(
+    [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
+    payer,
+    blockhash,
+    additionalSigners
+  );
+  const transferTxId = await sendAndConfirmTx(connection, signedTx);
+
+  console.log(`\nTransferred ${amount.toNumber() / 1e8} compressed tokens`);
+  console.log(`Transaction: ${transferTxId}`);
+  console.log(`Recipient: ${recipient.publicKey.toString()}`);
+
+  // Step 6: Verify via getCompressedTokenAccountsByOwner
+  const senderCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(payer.publicKey, { mint });
+  const senderBalance = senderCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
+  
+  const recipientCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(recipient.publicKey, { mint });
+  const recipientBalance = recipientCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
+
+  console.log(`\nSummary compressed token balances:`);
+  console.log(`Sender balance: ${senderBalance / 1e8} compressed tokens`);
+  console.log(`Recipient balance: ${recipientBalance / 1e8} compressed token`);
+
+  return transferTxId;
+})();
+
+{% endstep %} + +{% step %} +### Success! + +You've integrated compressed token support into your wallet! + +Your wallet now can + +* Display compressed token balances +* Show transaction history +* Send compressed tokens +{% endstep %} +{% endstepper %} + +## Advanced Integrations + +Use these integrations to let users convert between regular and compressed format as needed. + +
+ +Decompress to Regular SPL + +This example converts compressed tokens to regular SPL format using `CompressedTokenProgram.decompress().` + +```javascript +import { + bn, + buildAndSignTx, + sendAndConfirmTx, + dedupeSigner, + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; +import { ComputeBudgetProgram } from "@solana/web3.js"; +import { + CompressedTokenProgram, + getTokenPoolInfos, + selectMinCompressedTokenAccountsForTransfer, + selectTokenPoolInfosForDecompression, +} from "@lightprotocol/compressed-token"; + +// 1. Setup RPC connection and fetch compressed token accounts with getCompressedTokenAccountsByOwner() +// 2. Select accounts and token pool infos using selectMinCompressedTokenAccountsForTransfer() and selectTokenPoolInfosForDecompression() +// 3. Create decompress instruction with CompressedTokenProgram.decompress() and submit transaction + +// Step 1: Setup RPC connection and define decompression parameters +const connection: Rpc = createRpc("https://mainnet.helius-rpc.com?api-key=";); +const payer = PAYER_KEYPAIR; +const owner = PAYER_KEYPAIR; +const mint = MINT_ADDRESS; +const amount = 1e5; // 100K tokens to decompress + +(async () => { + // 1. Fetch compressed token accounts + const compressedTokenAccounts = + await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { + mint, + }); + + // 2. Select + const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( + compressedTokenAccounts.items, + bn(amount) + ); + + // 3. Fetch validity proof + const proof = await connection.getValidityProof( + inputAccounts.map((account) => account.compressedAccount.hash) + ); + + // 4. Fetch & Select tokenPoolInfos + const tokenPoolInfos = await getTokenPoolInfos(connection, mint); + const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression( + tokenPoolInfos, + amount + ); + + // 5. Build instruction + const ix = await CompressedTokenProgram.decompress({ + payer: payer.publicKey, + inputCompressedTokenAccounts: inputAccounts, + toAddress: owner.publicKey, + amount, + tokenPoolInfos: selectedTokenPoolInfos, + recentInputStateRootIndices: proof.rootIndices, + recentValidityProof: proof.compressedProof, + }); + + + // 6. Sign, send, and confirm. + // Example with keypair: + const { blockhash } = await connection.getLatestBlockhash(); + const additionalSigners = dedupeSigner(payer, [owner]); + const signedTx = buildAndSignTx( + [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], + payer, + blockhash, + additionalSigners + ); + + return await sendAndConfirmTx(connection, signedTx); +})(); +``` + +
+ +
+ +Compress Regular SPL Tokens + +This example converts regular SPL tokens to compressed format using `CompressedTokenProgram.compress().` + +```typescript +// 1. Setup RPC connection and get user ATA with getOrCreateAssociatedTokenAccount() +// 2. Fetch state tree and token pool infos using getStateTreeInfos() and getTokenPoolInfos() +// 3. Create compress instruction with CompressedTokenProgram.compress() and submit transaction + + +import { + buildAndSignTx, + sendAndConfirmTx, + Rpc, + createRpc, + selectStateTreeInfo, +} from "@lightprotocol/stateless.js"; +import { ComputeBudgetProgram } from "@solana/web3.js"; +import { + CompressedTokenProgram, + getTokenPoolInfos, + selectTokenPoolInfo, +} from "@lightprotocol/compressed-token"; +import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; + +// Step 1: Setup RPC connection and define compression parameters +const connection: Rpc = createRpc( + "https://mainnet.helius-rpc.com?api-key=" +); + const payer = ; + const mint = ; +const amount = 1e5; // 100K tokens to compress + +(async () => { + // Step 2: Get or create associated token account for SPL tokens + const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + payer, // fee payer + mint, // token mint address + payer.publicKey // token account owner + ); + + // Step 3: Fetch and select state tree info for compression + const treeInfos = await connection.getStateTreeInfos(); + const treeInfo = selectStateTreeInfo(treeInfos); + + // Step 4: Fetch and select token pool info for compression + const tokenPoolInfos = await getTokenPoolInfos(connection, mint); + const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); + + // Step 5: Create compress instruction - transfer SPL tokens to pool and create compressed accounts + const compressInstruction = await CompressedTokenProgram.compress({ + payer: payer.publicKey, // fee payer + owner: payer.publicKey, // owner of source SPL tokens + source: sourceTokenAccount.address, // source ATA address + toAddress: payer.publicKey, // recipient of compressed tokens (self) + amount, // amount to compress + mint, // token mint address + outputStateTreeInfo: treeInfo, // state tree for compressed accounts + tokenPoolInfo, // token pool for compression + }); + + // Step 6: Build, sign, and submit compression transaction + const { blockhash } = await connection.getLatestBlockhash(); + const tx = buildAndSignTx( + [ + ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), + compressInstruction, + ], + payer, // transaction signer + blockhash, + [payer] // additional signers + ); + await sendAndConfirmTx(connection, tx); +})(); +``` + +
+ +## Common Errors + +
+ +No compressed tokens found + +If `getCompressedTokenBalancesByOwnerV2` returns empty: + +* Ensure the wallet has compressed tokens (not regular SPL tokens) +* Verify you're on the correct network (devnet/mainnet) + +
+ +## Best Practices + +* **Clear UI Indicators —** Provide clear visual distinctions between compressed and uncompressed SPL tokens +* **Transaction History** — Provide detailed transaction histories for compressed tokens +* **Decompression and Compression** — Provide a clear path for users to convert between compressed and uncompressed tokens when needed + +*** + +## Next Steps + +Explore more guides in our cookbook section. + +{% content-ref url="../cookbook/" %} +[cookbook](../cookbook/) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/advanced-guides/create-an-airdrop.md b/compression-docs/compressed-tokens/advanced-guides/create-an-airdrop.md new file mode 100644 index 0000000..ea5b27d --- /dev/null +++ b/compression-docs/compressed-tokens/advanced-guides/create-an-airdrop.md @@ -0,0 +1,1051 @@ + + +# Create an Airdrop + +*** + +## Cost Comparison + +{% hint style="info" %} +You can use the [Airship Calculator ](https://airship.helius.dev/calculator)to anticipate the cost of your airdrop. +{% endhint %} + +
Airdrop SizeRegular AirdropZK Compression Airdrop
10,00020.4 SOL (*$4,080)0.0065 SOL (*$1.3)
100,000203.96 SOL (*$40,080)0.065 SOL (*$13)
1,000,0002039.28 SOL (*$400,080)0.65 SOL (*$130)
+ +_\*\* assuming $200 per SOL_ + +## Guides + +There are two ways to use ZK Compression to distribute your SPL tokens. + +
Cover imageCover image (dark)

No-Code Airdrop Tool

Use Airship by Helius Labs to airdrop to up to 200,000 recipients via Webapp.Light Protocol v2 - Batched Merkle trees-42.pnghttps://www.helius.dev/docs/airship/getting-startedLight Protocol v2 - Batched Merkle trees-65.png

Custom Programmatic Airdrop

Create a programmatic airdrop with this guide for more control.Light Protocol v2 - Batched Merkle trees-41.png#programmatic-airdropLight Protocol v2 - Batched Merkle trees-68.png
+ +## Programmatic Airdrop + +### What you will do + +By the end of this guide you will have a fully functioning programmatic airdrop. + +The high-level overview is this: + +1. Mint and send the to-be-airdropped SPL tokens to a wallet you control. +2. Create batches of instructions based on a list of recipients and amounts. +3. Build transactions from these instruction batches, then sign, send, and confirm them. +4. Tokens will appear in the recipients wallets automatically, or you can implement a claim function. + +### Get started + +{% stepper %} +{% step %} +#### Preqrequisites to Initialize Airdrop Project + +Make sure you have dependencies and developer environment set up! + +{% hint style="info" %} +System Requirements + +* **Node.js >= 20.18.0** (required by latest Solana packages) +* npm or yarn package manager +{% endhint %} + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace \ with your API key before running. + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +#### Mint SPL tokens to your wallet + +Run this `mint-spl-tokens.ts` to mint SPL tokens to your wallet. + +{% code title="mint-spl-tokens.ts" %} +```typescript +// Mint SPL Tokens for Airdrop - LocalNet +// 1. Load wallet and connect to local validator +// 2. Create SPL mint with token pool for compression via createMint() +// 3. Create ATA and mint SPL tokens to sender for airdrop preparation +// 4. Output mint address for use in simple-airdrop.ts + +import { Keypair } from "@solana/web3.js"; +import { createRpc } from "@lightprotocol/stateless.js"; +import { + createMint, + getOrCreateAssociatedTokenAccount, + mintTo, +} from "@solana/spl-token"; +import { createTokenPool } from "@lightprotocol/compressed-token"; +import * as fs from 'fs'; +import * as os from 'os'; + +// Step 1: Setup local connection and load wallet +const connection = createRpc(); // defaults to localhost:8899 + +// Load wallet from filesystem +const walletPath = `${os.homedir()}/.config/solana/id.json`; +const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); +const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); + +(async () => { + // Step 2: Create SPL mint with token pool for compression + const mint = await createMint(connection, payer, payer.publicKey, null, 9); + const poolTxId = await createTokenPool(connection, payer, mint); + console.log(`Mint address: ${mint.toBase58()}`); + console.log(`TokenPool created: ${poolTxId}`); + + // Step 3: Create associated token account for sender + // The sender will send tokens from this account to the recipients as compressed tokens. + const ata = await getOrCreateAssociatedTokenAccount( + connection, + payer, + mint, // SPL mint with token pool for compression + payer.publicKey + ); + console.log(`ATA address: ${ata.address.toBase58()}`); + + // Step 4: Mint SPL tokens to ATA. + // The sender will send tokens from this account to the recipients as compressed tokens. + const mintToTxId = await mintTo( + connection, + payer, + mint, // SPL mint with token pool for compression + ata.address, // distributor ATA + payer.publicKey, + 100_000_000_000 // amount: 100 tokens with 9 decimals + ); + console.log(`\nSPL tokens minted and ready for distribution!`); + console.log(`Transaction: ${mintToTxId}`); + + console.log(`\nCopy mint address to your airdrop script: ${mint.toBase58()}`); +})(); +``` +{% endcode %} +{% endstep %} + +{% step %} +### Execute the Airdrop + +Next, distribute the SPL tokens to all recipients. + +{% hint style="warning" %} +Ensure you have the latest `@lightprotocol/stateless.js` and `@lightprotocol/compressed-token` versions `≥ 0.21.0`! +{% endhint %} + +
// Simple Airdrop - LocalNet
+// 1. Load wallet and select compression infrastructure with getStateTreeInfos() and getTokenPoolInfos()
+// 2. Build CompressedTokenProgram.compress() instruction for multiple recipients in one transaction
+// 3. Execute transaction with compute budget and confirm compression operation with sendAndConfirmTx()
+// 4. Verify distribution via getCompressedTokenAccountsByOwner
+
+import { Keypair, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
+import {
+  CompressedTokenProgram,
+  getTokenPoolInfos,
+  selectTokenPoolInfo,
+} from "@lightprotocol/compressed-token";
+import {
+  bn,
+  buildAndSignTx,
+  calculateComputeUnitPrice,
+  createRpc,
+  dedupeSigner,
+  Rpc,
+  selectStateTreeInfo,
+  sendAndConfirmTx,
+} from "@lightprotocol/stateless.js";
+import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
+import * as fs from 'fs';
+import * as os from 'os';
+
+// Step 1: Setup local connection and load wallet
+const connection: Rpc = createRpc(); // defaults to localhost:8899
+const mint = new PublicKey("MINTADDRESS"); // Replace with mint address from mint-spl-tokens.ts
+
+// Local uses file wallet. Use constants from .env file in production
+const walletPath = `${os.homedir()}/.config/solana/id.json`;
+const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
+const payer = Keypair.fromSecretKey(Buffer.from(secretKey));
+const owner = payer;
+
+(async () => {
+  // Step 2: Select state tree and token pool
+  const activeStateTrees = await connection.getStateTreeInfos();
+  const treeInfo = selectStateTreeInfo(activeStateTrees);
+
+  const infos = await getTokenPoolInfos(connection, mint);
+  const info = selectTokenPoolInfo(infos);
+
+  // Step 3: Get or create source token account for distribution
+  // The sender will send tokens from this account to the recipients as compressed tokens.
+  const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
+    connection,
+    payer,
+    mint, // SPL mint with token pool for compression
+    payer.publicKey
+  );
+
+  // Step 4: Define airdrop recipients and amounts
+  const airDropAddresses = [
+    Keypair.generate().publicKey,
+    Keypair.generate().publicKey,
+    Keypair.generate().publicKey,
+  ];
+  
+  const amounts = [
+    bn(20_000_000_000), // 20 tokens 
+    bn(30_000_000_000), // 30 tokens
+    bn(40_000_000_000), // 40 tokens
+  ];
+
+  const totalAmount = amounts.reduce((sum, amt) => sum + amt.toNumber(), 0);
+  console.log(`Distributing ${totalAmount / 1e9} compressed tokens to ${airDropAddresses.length} recipients`);
+
+  const initialSplBalance = await connection.getTokenAccountBalance(sourceTokenAccount.address);
+  console.log(`Sender initial balance: ${initialSplBalance.value.uiAmount} tokens`);
+
+  // Step 5: Build transaction with compute budget and compression instruction
+  const instructions = [];
+  // Set compute unit limits based on recipient count (estimated 120k CU per recipient)
+  instructions.push(
+    ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 * airDropAddresses.length }),
+    ComputeBudgetProgram.setComputeUnitPrice({
+      microLamports: calculateComputeUnitPrice(20_000, 120_000 * airDropAddresses.length), // dynamic priority fee
+    })
+  );
+
+  // Create compression instruction for multiple recipients in one transaction
+  const compressInstruction = await CompressedTokenProgram.compress({
+    payer: payer.publicKey,
+    owner: owner.publicKey,
+    source: sourceTokenAccount.address, // source ATA holding SPL tokens
+    toAddress: airDropAddresses, // recipient addresses for compressed tokens
+    amount: amounts, // different amounts for each recipient
+    mint, // SPL mint with token pool for compression
+    tokenPoolInfo: info,
+    outputStateTreeInfo: treeInfo, // destination state tree
+  });
+  instructions.push(compressInstruction);
+
+  // Step 6: Sign and send transaction
+  const additionalSigners = dedupeSigner(payer, [owner]);
+  const { blockhash } = await connection.getLatestBlockhash();
+  const tx = buildAndSignTx(instructions, payer, blockhash, additionalSigners);
+  
+  // For production: Add address lookup table to reduce transaction size and fees
+  // const lookupTableAddress = new PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"); // mainnet // or "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" for devnet
+  // const lookupTableAccount = (await connection.getAddressLookupTable(lookupTableAddress)).value!;
+  // const tx = buildAndSignTx(instructions, payer, blockhash, additionalSigners, [lookupTableAccount]);
+  const txId = await sendAndConfirmTx(connection, tx);
+
+  console.log(`\nAirdrop completed!`);
+  console.log(`Transaction: ${txId}`);
+
+  // Step 7: Verify distribution via getCompressedTokenAccountsByOwner
+  for (let i = 0; i < airDropAddresses.length; i++) {
+    const recipientAccounts = await connection.getCompressedTokenAccountsByOwner(airDropAddresses[i], { mint });
+    const balance = recipientAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0);
+    console.log(`Recipient ${i + 1} (${airDropAddresses[i].toString()}): ${balance / 1e9} compressed tokens`);
+  }
+
+  const finalSplBalance = await connection.getTokenAccountBalance(sourceTokenAccount.address);
+  console.log(`\nSender balance after airdrop: ${finalSplBalance.value.uiAmount} SPL tokens`);
+
+  return txId;
+})();
+
+{% endstep %} + +{% step %} +### Success! + +You've executed an airdrop with compressed tokens. +{% endstep %} +{% endstepper %} + +*** + +## Next Steps + +You're ready to deploy an airdrop on devnet or mainnet. + +{% hint style="success" %} +You can use an existing mint or create a new one with `createMint`. +{% endhint %} + +1. Configure your environment variables + +{% code title="example.env" %} +```bash +RPC_ENDPOINT=https://devnet.helius-rpc.com?api-key=YOUR_API_KEY +PAYER_KEYPAIR=YOUR_BASE58_ENCODED_PRIVATE_KEY +MINT_ADDRESS=YOUR_MINT_ADDRESS +``` +{% endcode %} + +2. Mint SPL tokens to your wallet, as shown in the guide above. See the [source code here](https://github.com/Lightprotocol/example-token-distribution/blob/main/src/simple-airdrop/mint.ts). +3. Choose below between the + 1. Simple Airdrop Script for <10,000 recipients, and + 2. Script for large-scale Airdrops with batched operations +4. Add the variables to your airdrop script & execute the airdrop! + +### A. Simple Airdrop + +{% hint style="success" %} +For small airdrops (<10,000 recipients). [View ](https://github.com/Lightprotocol/example-token-distribution/blob/main/src/simple-airdrop/simple-airdrop.ts)[the source code here](https://github.com/Lightprotocol/example-token-distribution/blob/main/src/simple-airdrop/simple-airdrop.ts). +{% endhint %} + +
+ +Simple Airdrop + +```typescript +// 1. Load environment and select compression infrastructure with getStateTreeInfos() and getTokenPoolInfos() +// 2. Build CompressedTokenProgram.compress() instruction for multiple recipients in one transaction +// 3. Execute transaction with compute budget, address lookup table, and confirm with sendAndConfirmTx() +// 4. Verify distribution via getCompressedTokenAccountsByOwner + +import { + PublicKey, + TransactionInstruction, + ComputeBudgetProgram, +} from '@solana/web3.js'; +import { + CompressedTokenProgram, + getTokenPoolInfos, + selectTokenPoolInfo, +} from '@lightprotocol/compressed-token'; +import { + bn, + buildAndSignTx, + calculateComputeUnitPrice, + createRpc, + dedupeSigner, + Rpc, + selectStateTreeInfo, + sendAndConfirmTx, +} from '@lightprotocol/stateless.js'; +import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token'; +import { MINT_ADDRESS, PAYER_KEYPAIR, RPC_ENDPOINT } from '../constants'; + +(async () => { + const connection: Rpc = createRpc(RPC_ENDPOINT); + const mintAddress = MINT_ADDRESS; + const payer = PAYER_KEYPAIR; + const owner = payer; + const recipients = [ + PublicKey.default, + // ... + ]; + + // 1. Select a state tree + const treeInfos = await connection.getStateTreeInfos(); // Fixed: removed deprecated getCachedActiveStateTreeInfos + const treeInfo = selectStateTreeInfo(treeInfos); + + // 2. Select a token pool + const tokenPoolInfos = await getTokenPoolInfos(connection, mintAddress); + const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); + + // Create an SPL token account for the sender. + // The sender will send tokens from this account to the recipients as compressed tokens. + const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + payer, + mintAddress, + payer.publicKey, + ); + + // 1 recipient = 120_000 CU + // 5 recipients = 170_000 CU + const units = 120_000; + const amount = bn(333); + // To land faster, replace this with a dynamic fee based on network + // conditions. + const microLamports = calculateComputeUnitPrice(20_000, units); + + const instructions: TransactionInstruction[] = [ + ComputeBudgetProgram.setComputeUnitLimit({ units }), + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports, + }), + ]; + + const compressInstruction = await CompressedTokenProgram.compress({ + payer: payer.publicKey, + owner: owner.publicKey, + source: sourceTokenAccount.address, + toAddress: recipients, + amount: recipients.map(() => amount), + mint: mintAddress, + outputStateTreeInfo: treeInfo, + tokenPoolInfo, + }); + instructions.push(compressInstruction); + + // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables + const lookupTableAddress = new PublicKey( + '9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ', // mainnet + // "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet + ); + + // Get the lookup table account state + const lookupTableAccount = ( + await connection.getAddressLookupTable(lookupTableAddress) + ).value!; + + const additionalSigners = dedupeSigner(payer, [owner]); + + const { blockhash } = await connection.getLatestBlockhash(); + + const tx = buildAndSignTx( + instructions, + payer, + blockhash, + additionalSigners, + [lookupTableAccount], + ); + + const txId = await sendAndConfirmTx(connection, tx); + console.log(`txId: ${txId}`); +})(); +``` + +
+ +### B. Large-scale Airdrop with Batched Operations + +{% hint style="success" %} +For large-scale airdrops (10,000+ recipients) we recommend to batch operations efficiently. \ +[View the source code here.](https://github.com/Lightprotocol/example-token-distribution/tree/main/src/optimized-airdrop) +{% endhint %} + +1. **create-instructions.ts** - Process recipients in chunks, create batched CompressedTokenProgram.compress() instructions with optimized compute limits + +
+ +create-instructions.ts + +```typescript +// 1. Process recipients in chunks with selectStateTreeInfo() and selectTokenPoolInfo() for each batch +// 2. Create CompressedTokenProgram.compress() instructions with ComputeBudgetProgram limits for multiple recipients +// 3. Return batched instructions for optimized large-scale airdrop execution + +import { + CompressedTokenProgram, + TokenPoolInfo, +} from "@lightprotocol/compressed-token"; +import { + bn, + selectStateTreeInfo, + StateTreeInfo, +} from "@lightprotocol/stateless.js"; +import { + ComputeBudgetProgram, + TransactionInstruction, + PublicKey, +} from "@solana/web3.js"; + +interface CreateAirdropInstructionsParams { + amount: number | bigint; + recipients: PublicKey[]; + payer: PublicKey; + sourceTokenAccount: PublicKey; + mint: PublicKey; + stateTreeInfos: StateTreeInfo[]; + tokenPoolInfos: TokenPoolInfo[]; + maxRecipientsPerInstruction?: number; + maxInstructionsPerTransaction?: number; + computeUnitLimit?: number; + computeUnitPrice?: number | undefined; +} + +export type InstructionBatch = TransactionInstruction[]; + +export async function createAirdropInstructions({ + amount, + recipients, + payer, + sourceTokenAccount, + mint, + stateTreeInfos, + tokenPoolInfos, + maxRecipientsPerInstruction = 5, + maxInstructionsPerTransaction = 3, + computeUnitLimit = 500_000, + computeUnitPrice = undefined, +}: CreateAirdropInstructionsParams): Promise { + const instructionBatches: InstructionBatch[] = []; + const amountBn = bn(amount.toString()); + + // Process recipients in chunks + for ( + let i = 0; + i < recipients.length; + i += maxRecipientsPerInstruction * maxInstructionsPerTransaction + ) { + const instructions: TransactionInstruction[] = []; + + instructions.push( + ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }) + ); + if (computeUnitPrice) { + instructions.push( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: computeUnitPrice, + }) + ); + } + + const treeInfo = selectStateTreeInfo(stateTreeInfos); + const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); + + for (let j = 0; j < maxInstructionsPerTransaction; j++) { + const startIdx = i + j * maxRecipientsPerInstruction; + const recipientBatch = recipients.slice( + startIdx, + startIdx + maxRecipientsPerInstruction + ); + + if (recipientBatch.length === 0) break; + + const compressIx = await CompressedTokenProgram.compress({ + payer, + owner: payer, + source: sourceTokenAccount, + toAddress: recipientBatch, + amount: recipientBatch.map(() => amountBn), + mint, + tokenPoolInfo, + outputStateTreeInfo: treeInfo, + }); + + instructions.push(compressIx); + } + + if ( + (computeUnitPrice && instructions.length > 2) || + (!computeUnitPrice && instructions.length > 1) + ) { + instructionBatches.push(instructions); + } + } + + return instructionBatches; +} +``` + +
+ +2. **update-blockhash.ts** - Maintain fresh blockhashes with background refresh loop using getLatestBlockhash() every 30 seconds + +
+ +update-blockhash.ts + +```typescript +import { Rpc } from "@lightprotocol/stateless.js"; + +// 1. Fetch initial blockhash with getLatestBlockhash() and store in exported variable +// 2. Set up background refresh loop with setTimeout() to update blockhash every 30 seconds +// 3. Provide AbortSignal support to stop background updates when airdrop completes + +export let currentBlockhash: string; + +export async function updateBlockhash( + connection: Rpc, + signal: AbortSignal +): Promise { + try { + const { blockhash } = await connection.getLatestBlockhash(); + currentBlockhash = blockhash; + console.log(`Initial blockhash: ${currentBlockhash}`); + } catch (error) { + console.error("Failed to fetch initial blockhash:", error); + return; + } + + // Update blockhash in the background + (function updateInBackground() { + if (signal.aborted) return; + const timeoutId = setTimeout(async () => { + if (signal.aborted) return; + try { + const { blockhash } = await connection.getLatestBlockhash(); + currentBlockhash = blockhash; + console.log(`Updated blockhash: ${currentBlockhash}`); + } catch (error) { + console.error("Failed to update blockhash:", error); + } + updateInBackground(); + }, 30_000); + + signal.addEventListener("abort", () => clearTimeout(timeoutId)); + })(); +} +``` + +
+ +3. **sign-and-send.ts** - Execute batched transactions with VersionedTransaction, retry logic, and sendAndConfirmTx() confirmation + +
+ +sign-and-send.ts + +```typescript +// 1. Initialize blockhash updates with updateBlockhash() and get address lookup table with getAddressLookupTable() +// 2. Process instruction batches with VersionedTransaction and retry logic for failed transactions +// 3. Yield batch results with sendAndConfirmTx() confirmation and comprehensive error handling + +import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js"; +import { + Keypair, + PublicKey, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; +import { InstructionBatch } from "./create-instructions"; +import { currentBlockhash, updateBlockhash } from "./update-blockhash"; +import bs58 from "bs58"; + +export enum BatchResultType { + Success = "success", + Error = "error", +} + +export type BatchResult = + | { type: BatchResultType.Success; index: number; signature: string } + | { type: BatchResultType.Error; index: number; error: string }; + +export async function* signAndSendAirdropBatches( + batches: InstructionBatch[], + payer: Keypair, + connection: Rpc, + maxRetries = 3 +): AsyncGenerator { + const abortController = new AbortController(); + const { signal } = abortController; + + await updateBlockhash(connection, signal); + + const statusMap = new Array(batches.length).fill(0); // Initialize all as pending (0) + + // Use zk-compression look up table for your network + // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables + const lookupTableAddress = new PublicKey( + "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" + ); + + // Get the lookup table account + const lookupTableAccount = ( + await connection.getAddressLookupTable(lookupTableAddress) + ).value!; + + while (statusMap.includes(0)) { + // Continue until all are confirmed or errored + const pendingBatches = statusMap.filter((status) => status === 0).length; + console.log(`Sending ${pendingBatches} transactions`); + + const sends = statusMap.map(async (status, index) => { + if (status !== 0) return; // Skip non-pending batches + + let retries = 0; + while (retries < maxRetries && statusMap[index] === 0) { + if (!currentBlockhash) { + console.warn("Waiting for blockhash to be set..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); + continue; + } + + try { + const tx = new VersionedTransaction( + new TransactionMessage({ + payerKey: payer.publicKey, + recentBlockhash: currentBlockhash, + instructions: batches[index], + }).compileToV0Message([lookupTableAccount]) + ); + tx.sign([payer]); + + const sig = bs58.encode(tx.signatures[0]); + console.log(`Batch ${index} signature: ${sig}`); + + const confirmedSig = await sendAndConfirmTx(connection, tx, { + skipPreflight: true, + commitment: "confirmed", + }); + + if (confirmedSig) { + statusMap[index] = 1; // Mark as confirmed + return { + type: BatchResultType.Success, + index, + signature: confirmedSig, + }; + } + } catch (e) { + retries++; + console.warn(`Retrying batch ${index}, attempt ${retries + 1}`); + if (retries >= maxRetries) { + statusMap[index] = `err: ${(e as Error).message}`; // Mark as error + return { + type: BatchResultType.Error, + index, + error: (e as Error).message, + }; + } + } + } + }); + + const results = await Promise.all(sends); + for (const result of results) { + if (result) yield result as BatchResult; + } + } + + // Stop the blockhash update loop + abortController.abort(); +} +``` + +
+ +4. **airdrop.ts** - Finally, put it all together in your main file: + +
+ +airdrop.ts (entrypoint file) + +{% code title="airdrop.ts" %} +```typescript +// 1. Create compressed mint with createMint(), mint supply with mintTo(), get infrastructure with getStateTreeInfos() and getTokenPoolInfos() +// 2. Generate batched compression instructions with createAirdropInstructions() - create CompressedTokenProgram.compress() calls +// 3. Execute batched airdrop with signAndSendAirdropBatches() - sign transactions and confirm with sendAndConfirmTx() for large-scale distribution + +import { Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; +import { + calculateComputeUnitPrice, + createRpc, + Rpc, +} from "@lightprotocol/stateless.js"; +import { createMint, getTokenPoolInfos } from "@lightprotocol/compressed-token"; +import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token"; +import { createAirdropInstructions } from "./create-instructions"; +import { BatchResultType, signAndSendAirdropBatches } from "./sign-and-send"; +import dotenv from "dotenv"; +import bs58 from "bs58"; +dotenv.config(); + +// Step 1: Setup environment and RPC connection +const RPC_ENDPOINT = `https://mainnet.helius-rpc.com?api-key=${process.env.HELIUS_API_KEY}`; +const connection: Rpc = createRpc(RPC_ENDPOINT); +const PAYER = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!)); + +// Step 2: Define airdrop recipient list (20 example addresses) +const recipients = [ + "GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7", + "GySGrTgPtPfMtYoYTmwUdUDFwVJbFMfip7QZdhgXp8dy", + "Bk1r2vcgX2uTzwV3AUyfRbSfGKktoQrQufBSrHzere74", + "8BvkadZ6ycFNmQF7S1MHRvEVNb1wvDBFdjkAUnxjK9Ug", + "EmxcvFKXsWLzUho8AhV9LCKeKRFHg5gAs4sKNJwhe5PF", + "6mqdHkSpcvNexmECjp5XLt9V9KnSQre9TvbMLGr6sEPM", + "3k4MViTWXBjFvoUZiJcNGPvzrqnTa41gcrbWCMMnV6ys", + "2k6BfYRUZQHquPtpkyJpUx3DzM7W3K6H95igtJk8ztpd", + "89jPyNNLCcqWn1RZThSS4jSqU5VCJkR5mAaSaVzuuqH4", + "3MzSRLf9jSt6d1MFFMMtPfUcDY6XziRxTB8C5mfvgxXG", + "9A1H6f3N8mpAPSdfqvYRD4cM1NwDZoMe3yF5DwibL2R2", + "PtUAhLvUsVcoesDacw198SsnMoFNVskR5pT3QvsBSQw", + "6C6W6WpgFK8TzTTMNCPMz2t9RaMs4XnkfB6jotrWWzYJ", + "8sLy9Jy8WSh6boq9xgDeBaTznn1wb1uFpyXphG3oNjL5", + "GTsQu2XCgkUczigdBFTWKrdDgNKLs885jKguyhkqdPgV", + "85UK4bjC71Jwpyn8mPSaW3oYyEAiHPbESByq9s5wLcke", + "9aEJT4CYHEUWwwSQwueZc9EUjhWSLD6AAbpVmmKDeP7H", + "CY8QjRio1zd9bYWMKiVRrDbwVenf3JzsGf5km5zLgY9n", + "CeHbdxgYifYhpB6sXGonKzmaejqEfq2ym5utTmB6XMVv", + "4z1qss12DjUzGUkK1fFesqrUwrEVJJvzPMNkwqYnbAR5", +].map((address) => new PublicKey(address)); + +(async () => { + // Step 3: Create compressed mint and register for compression + // 3a: Call createMint() to initialize mint with compression pool + const { mint, transactionSignature } = await createMint( + connection, + PAYER, // fee payer + PAYER.publicKey, // mint authority + 9 // decimals + ); + console.log( + `create-mint success! txId: ${transactionSignature}, mint: ${mint.toBase58()}` + ); + + // Step 4: Create associated token account for distributor + // 4a: Ensure PAYER has ATA for holding tokens before compression + const ata = await getOrCreateAssociatedTokenAccount( + connection, + PAYER, // fee payer + mint, // token mint + PAYER.publicKey // token owner + ); + console.log(`ATA: ${ata.address.toBase58()}`); + + // Step 5: Mint initial token supply to distributor + // 5a: Create 10 billion tokens in the ATA for airdrop distribution + const mintToTxId = await mintTo( + connection, + PAYER, // fee payer and mint authority + mint, // token mint + ata.address, // destination ATA + PAYER.publicKey, // mint authority + 10e9 * LAMPORTS_PER_SOL // amount: 10 billion tokens with decimals + ); + console.log(`mint-to success! txId: ${mintToTxId}`); + + // Step 6: Get compression infrastructure for batch operations + // 6a: Fetch available state trees for compressed account storage + const stateTreeInfos = await connection.getStateTreeInfos(); + + // 6b: Get token pool infos for compression operations + const tokenPoolInfos = await getTokenPoolInfos(connection, mint); + + // Step 7: Create instruction batches for large-scale airdrop + // 7a: Generate batched compression instructions with compute optimization + const instructionBatches = await createAirdropInstructions({ + amount: 1e6, // 1 million tokens per recipient + recipients, // array of recipient addresses + payer: PAYER.publicKey, // transaction fee payer + sourceTokenAccount: ata.address, // source ATA holding SPL tokens + mint, // token mint + stateTreeInfos, // state trees for compressed accounts + tokenPoolInfos, // token pools for compression + computeUnitPrice: calculateComputeUnitPrice(10_000, 500_000), // dynamic priority fee + }); + + // Step 8: Execute batched airdrop with error handling + // 8a: Process instruction batches with retry logic and confirmation + for await (const result of signAndSendAirdropBatches( + instructionBatches, + PAYER, + connection + )) { + if (result.type === BatchResultType.Success) { + console.log(`Batch ${result.index} confirmed: ${result.signature}`); + } else if (result.type === BatchResultType.Error) { + console.log(`Batch ${result.index} failed: ${result.error}`); + // Use result.index to access the specific batch in instructionBatches + const failedBatch = instructionBatches[result.index]; + console.log(`Failed batch instructions:`, failedBatch); + // Additional logic to handle failed instructions + } + } + + console.log("Airdrop process complete."); +})(); +``` +{% endcode %} + +
+ +*** + +## Advanced Features + +#### Decompress / Claim + +{% hint style="info" %} +Solana Wallets like Phantom and Backpack already support compressed tokens.\ +Still, you can let users decompress to SPL via your Frontend to customize claims. Here's how:point\_down: +{% endhint %} + +
+ +Decompress SPL Tokens + +```typescript +import { + bn, + buildAndSignTx, + sendAndConfirmTx, + dedupeSigner, + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; +import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js"; +import { + CompressedTokenProgram, + getTokenPoolInfos, + selectMinCompressedTokenAccountsForTransfer, + selectTokenPoolInfosForDecompression, +} from "@lightprotocol/compressed-token"; +import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; +import bs58 from "bs58"; +import dotenv from "dotenv"; +dotenv.config(); + +// Set these values in your .env file +const RPC_ENDPOINT = process.env.RPC_ENDPOINT; +const mint = new PublicKey(process.env.MINT_ADDRESS!); +const payer = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!)); + +const owner = payer; +const amount = 1e5; +const connection: Rpc = createRpc(RPC_ENDPOINT); + +(async () => { + // 1. Create an associated token account for the user if it doesn't exist + const ata = await getOrCreateAssociatedTokenAccount( + connection, + payer, + mint, + payer.publicKey + ); + + // 2. Fetch compressed token accounts + const compressedTokenAccounts = + await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { + mint, + }); + + // 3. Select + const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( + compressedTokenAccounts.items, + bn(amount) + ); + + // 4. Fetch validity proof + const proof = await connection.getValidityProof( + inputAccounts.map((account) => account.compressedAccount.hash) + ); + + // 5. Fetch token pool infos + const tokenPoolInfos = await getTokenPoolInfos(connection, mint); + + // 6. Select + const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression( + tokenPoolInfos, + amount + ); + + // 7. Build instruction + const ix = await CompressedTokenProgram.decompress({ + payer: payer.publicKey, + inputCompressedTokenAccounts: inputAccounts, + toAddress: ata.address, + amount, + tokenPoolInfos: selectedTokenPoolInfos, + recentInputStateRootIndices: proof.rootIndices, + recentValidityProof: proof.compressedProof, + }); + + // 8. Sign, send, and confirm + const { blockhash } = await connection.getLatestBlockhash(); + const additionalSigners = dedupeSigner(payer, [owner]); + const signedTx = buildAndSignTx( + [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], + payer, + blockhash, + additionalSigners + ); + return await sendAndConfirmTx(connection, signedTx); +})(); + +``` + +
+ +Tip: Set priority fees dynamically for decompression. Learn more [here](https://docs.helius.dev/guides/sending-transactions-on-solana#summary). + +### Native Swap via Jup-API + +If you have a custom FE, you can let users swap compressed tokens using the Jup-API. A reference implementation is available [here](https://github.com/Lightprotocol/example-jupiter-swap-node). + +*** + +## Next Steps + +Explore more guides in our cookbook section. + +{% content-ref url="../cookbook/" %} +[cookbook](../cookbook/) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/advanced-guides/use-token-2022-with-compression.md b/compression-docs/compressed-tokens/advanced-guides/use-token-2022-with-compression.md new file mode 100644 index 0000000..074eab7 --- /dev/null +++ b/compression-docs/compressed-tokens/advanced-guides/use-token-2022-with-compression.md @@ -0,0 +1,359 @@ + + +# Use Token 2022 with Compression + +*** + +## What you will do + +With this guide you will mint, compress, and transfer tokens with Token-2022 Metadata. + +## Overview Token 2022 Extensions + +Token 2022 Extensions are optional features that can be added to Token 2022 mints on Solana to enable additional functionality. + +ZK Compression supports compressing the following mint-extensions: + +* MetadataPointer +* TokenMetadata +* InterestBearingConfig +* GroupPointer +* GroupMemberPointer +* TokenGroup +* TokenGroupMember + +All other extensions are not yet supported. + +{% hint style="info" %} +If you require support for other mint-extensions, [let us know](https://t.me/swen_light)! +{% endhint %} + +## Get started + +{% stepper %} +{% step %} +### Prerequisites + +{% hint style="info" %} +You need the following SDK versions: + +* `@lightprotocol/stateless.js` ≥ 0.21.0 +* `@lightprotocol/compressed-token` ≥ 0.21.0 +* `@solana/web3.js` ≥ 1.95.3 +{% endhint %} + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace \ with your API key before running. + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Mint, compress, and transfer tokens with Token-2022 Metadata + +Run `compress-t22.ts`. + +{% code title="compress-t22.ts" %} +```typescript +// Token-2022 with ZK Compression - Local +// 1. Load wallet and connect to local validator +// 2. Create Token-2022 mint with metadata extension and register for compression via createTokenPool() +// 3. Mint SPL tokens to ATA, compress via compress(), and transfer compressed tokens via transfer() +// 4. Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner + +import { confirmTx, createRpc } from "@lightprotocol/stateless.js"; +import { + compress, + createTokenPool, + transfer, +} from "@lightprotocol/compressed-token"; +import { + getOrCreateAssociatedTokenAccount, + mintTo as mintToSpl, + TOKEN_2022_PROGRAM_ID, + createInitializeMetadataPointerInstruction, + createInitializeMintInstruction, + ExtensionType, + getMintLen, + LENGTH_SIZE, + TYPE_SIZE, +} from "@solana/spl-token"; +import { + Keypair, + sendAndConfirmTransaction, + SystemProgram, + Transaction, +} from "@solana/web3.js"; +import { + createInitializeInstruction, + pack, + TokenMetadata, +} from "@solana/spl-token-metadata"; + +// Step 1: Setup local connection and load wallet +import * as fs from 'fs'; +import * as os from 'os'; + +const walletPath = `${os.homedir()}/.config/solana/id.json`; +const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf8')); +const payer = Keypair.fromSecretKey(Buffer.from(secretKey)); +const connection = createRpc(); // defaults to localhost:8899 + +(async () => { + + // Generate mint keypair and define Token-2022 metadata + const mint = Keypair.generate(); + const decimals = 9; + + const metadata: TokenMetadata = { + mint: mint.publicKey, + name: "Local Test Token", + symbol: "LTT", + uri: "https://example.com/token-metadata.json", + additionalMetadata: [["environment", "localnet"], ["test", "true"]], + }; + + // Calculate space requirements for Token-2022 mint with MetadataPointer extension + const mintLen = getMintLen([ExtensionType.MetadataPointer]); + const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; + + // Check wallet balance + const balance = await connection.getBalance(payer.publicKey); + + // Step 2: Create Token-2022 mint with metadata extension + const mintLamports = await connection.getMinimumBalanceForRentExemption( + mintLen + metadataLen + ); + + const mintTransaction = new Transaction().add( + // Create account for Token-2022 mint + SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: mint.publicKey, + space: mintLen, + lamports: mintLamports, + programId: TOKEN_2022_PROGRAM_ID, + }), + // Initialize MetadataPointer extension + createInitializeMetadataPointerInstruction( + mint.publicKey, + payer.publicKey, + mint.publicKey, + TOKEN_2022_PROGRAM_ID + ), + // Initialize Token-2022 mint account + createInitializeMintInstruction( + mint.publicKey, + decimals, + payer.publicKey, + null, + TOKEN_2022_PROGRAM_ID + ), + // Initialize token metadata + createInitializeInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + mint: mint.publicKey, + metadata: mint.publicKey, + name: metadata.name, + symbol: metadata.symbol, + uri: metadata.uri, + mintAuthority: payer.publicKey, + updateAuthority: payer.publicKey, + }) + ); + + // Send Token-2022 mint creation transaction + const mintCreationTxId = await sendAndConfirmTransaction( + connection, + mintTransaction, + [payer, mint] + ); + console.log(`Token-2022 mint created with address: ${mint.publicKey.toString()}`); + + // Step 3: Call createTokenPool() to initialize omnibus account + // and register Token-2022 mint with Compressed Token Program + // Create PDA that holds SPL tokens for compressed tokens + const registerTxId = await createTokenPool( + connection, + payer, + mint.publicKey, // Token-2022 mint to register with compressed token program + undefined, + TOKEN_2022_PROGRAM_ID + ); + + // Step 4: Create associated token account and mint tokens + const ata = await getOrCreateAssociatedTokenAccount( + connection, + payer, + mint.publicKey, // Token-2022 mint with token pool for compression + payer.publicKey, + undefined, + undefined, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + // Mint Token-2022 tokens to ATA + const mintAmount = 400_000_000; // 0.4 tokens + const mintToTxId = await mintToSpl( + connection, + payer, + mint.publicKey, // Token-2022 mint with token pool for compression + ata.address, // destination token account + payer.publicKey, // mint authority + mintAmount, // amount to mint + undefined, // multiSigners (not used for single authority) + undefined, + TOKEN_2022_PROGRAM_ID + ); + console.log(`Minted ${mintAmount / 1e9} Token-2022 tokens with metadata extension`); + console.log(`Transaction: ${mintToTxId}`); + + // Step 5: Call compress() to convert Token-2022 tokens to compressed format + // Transfer Token-2022 tokens to omnibus pool and mint compressed tokens + const compressAmount = 300_000_000; // 0.3 tokens + const compressTxId = await compress( + connection, + payer, + mint.publicKey, // Token-2022 mint with token pool for compression + compressAmount, // amount to compress + payer, // owner of SPL tokens + ata.address, // Source ATA for compression + payer.publicKey // recipient for compressed tokens + ); + console.log(`\nCompressed ${compressAmount / 1e9} Token-2022 tokens`); + console.log(`Transaction: ${compressTxId}`); + + // Step 6: Transfer compressed Token-2022 tokens + const transferRecipient = Keypair.generate(); // Keypair recipient + const transferAmount = 100_000_000; // 0.1 tokens + const transferTxId = await transfer( + connection, + payer, + mint.publicKey, // Token-2022 mint with token pool for compression + transferAmount, + payer, + transferRecipient.publicKey + ); + console.log(`\nTransferred ${transferAmount / 1e9} Compressed Token-2022 tokens`); + console.log(`Transaction: ${transferTxId}`); + console.log(`Recipient: ${transferRecipient.publicKey.toString()}`); + + // Step 7: Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner + const senderCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(payer.publicKey, { mint: mint.publicKey }); + const senderBalance = senderCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); + + const recipientCompressedAccounts = await connection.getCompressedTokenAccountsByOwner(transferRecipient.publicKey, { mint: mint.publicKey }); + const recipientBalance = recipientCompressedAccounts.items.reduce((sum, account) => sum + Number(account.parsed.amount), 0); + + const splBalance = await connection.getTokenAccountBalance(ata.address); + + console.log(`\nSummary Token-2022 balances:`); + console.log(`Sender balance: ${senderBalance / 1e9} compressed tokens / ${splBalance.value.uiAmount} SPL tokens`); + console.log(`Recipient balance: ${recipientBalance / 1e9} compressed tokens`); + +})().catch(console.error); +``` +{% endcode %} +{% endstep %} + +{% step %} +### Success! + +You've just compressed Token-2022 tokens with metadata extensions and transferred them using ZK Compression. +{% endstep %} +{% endstepper %} + +*** + +## Next Steps + +Explore more guides in our cookbook section. + +{% content-ref url="../cookbook/" %} +[cookbook](../cookbook/) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-approve-and-revoke-delegate-authority.md b/compression-docs/compressed-tokens/cookbook/how-to-approve-and-revoke-delegate-authority.md new file mode 100644 index 0000000..8cbff3a --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-approve-and-revoke-delegate-authority.md @@ -0,0 +1,463 @@ + +# How to approve and revoke delegate authority + +The `approve()` and `revoke()` functions grant and remove delegate spending authority for compressed tokens. Only the token owner can perform these operations. + +Before we approve or revoke delegates, we need: + +* Compressed token accounts to delegate or revoke delegation from +* SPL mint registered with the compressed token program via `createMint()` or `createTokenPool()` + +The functions perform opposite operations: + +1. **`approve()`** consumes input account and creates delegated account with spending limit for delegate and change account for owner +2. **`revoke()`** consumes delegated accounts and creates output account under owner control without delegation + +{% code title="function-manage-delegates.ts" %} +```typescript +import { approve, revoke } from '@lightprotocol/compressed-token'; +import { Keypair, PublicKey } from '@solana/web3.js'; + +const mint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS"); +const delegate = Keypair.generate(); +const amount = 500_000_000; +const owner = payer; + +// Approve delegate +const approveSignature = await approve( + rpc, + payer, + mint, // SPL mint with token pool for compression + amount, + owner, + delegate.publicKey, // delegate account +); + +// Get delegated accounts for revocation +const delegatedAccounts = await rpc.getCompressedTokenAccountsByDelegate( + delegate.publicKey, + { mint } +); + +// Revoke delegate authority +const revokeSignature = await revoke( + rpc, + payer, + delegatedAccounts.items, // delegated compressed token accounts + owner, +); +``` +{% endcode %} + +### Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} +Get your API Key [here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Approving Delegates + +Run this script to approve delegate authority for compressed tokens! + +
// 1. Setup funded payer and connect to local validator
+// 2. Create mint and token pool with initial tokens
+// 3. Call approve() with mint, amount, owner, delegate
+// 4. Verify delegation via getCompressedTokenAccountsByDelegate
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { 
+    createMint, 
+    mintTo, 
+    approve 
+} from '@lightprotocol/compressed-token';
+import BN from 'bn.js';
+import * as fs from 'fs';
+
+async function approveDelegates() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL mint with token pool and mint initial tokens
+    const { mint } = await createMint(
+        rpc,
+        payer,
+        payer.publicKey, // mint authority
+        9 // decimals
+    );
+
+    console.log("SPL mint with token pool created:", mint.toBase58());
+
+    const tokenOwner = Keypair.generate();
+    const initialAmount = 1_000_000_000; // 1 token with 9 decimals
+
+    await mintTo(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        tokenOwner.publicKey, // recipient
+        payer, // mint authority
+        initialAmount
+    );
+
+    console.log("Initial tokens minted:", initialAmount / 1_000_000_000, "tokens");
+    console.log("Token owner:", tokenOwner.publicKey.toBase58());
+    
+    // Generate delegate address and define amount to approve for delegation    
+    const delegate = Keypair.generate();    
+    const delegateAmount = 500_000_000; // 0.5 tokens
+
+    // Step 3: Call approve() with mint, amount, owner, delegate
+    const approveTx = await approve(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        delegateAmount,
+        tokenOwner, // owner keypair
+        delegate.publicKey // delegate address
+    );
+
+    console.log("Delegate approved");
+    console.log("Delegate:", delegate.publicKey.toBase58());
+    console.log("Approved amount:", delegateAmount / 1_000_000_000, "tokens");
+    console.log("Transaction:", approveTx);
+
+    // Step 4: Verify delegation via getCompressedTokenAccountsByDelegate
+    const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate(
+        delegate.publicKey,
+        { mint }
+    );
+
+    // Check delegated balance
+    if (delegateAccounts.items.length > 0) {
+        const delegatedBalance = delegateAccounts.items.reduce(
+            (sum, account) => sum.add(account.parsed.amount),
+            new BN(0)
+        );
+        console.log("Verified delegation:", delegatedBalance.toNumber() / 1_000_000_000, "tokens");
+    }
+
+    // Save state for revoke step
+    const approveState = {
+        mint: mint.toBase58(),
+        tokenOwner: Array.from(tokenOwner.secretKey),
+        delegate: Array.from(delegate.secretKey),
+        delegatePublicKey: delegate.publicKey.toBase58(),
+        payer: Array.from(payer.secretKey)
+    };
+    fs.writeFileSync('./approve-state.json', JSON.stringify(approveState, null, 2));
+
+    return { 
+        mint,
+        tokenOwner,
+        delegate: delegate.publicKey,
+        approveTransaction: approveTx,
+        delegatedAmount: delegateAmount
+    };
+}
+
+approveDelegates().catch(console.error);
+
+{% endstep %} + +{% step %} +### Revoking Delegates + +Remove delegate authority from previously approved accounts. + +
// Continue from approve setup - revoke delegate authority
+// 1. Get delegated accounts to revoke via getCompressedTokenAccountsByDelegate
+// 2. Call revoke() with delegated accounts and token owner - remove delegate authority and return control to owner
+// 3. Verify delegation removed via getCompressedTokenAccountsByDelegate
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { revoke } from '@lightprotocol/compressed-token';
+import BN from 'bn.js';
+import * as fs from 'fs';
+
+async function revokeDelegates() {
+    if (!fs.existsSync('./approve-state.json')) {
+        console.log("No delegated accounts found to revoke. Please run 'npx tsx approve.ts' first.");
+        return;
+    }
+
+    // Load approve state
+    const state = JSON.parse(fs.readFileSync('./approve-state.json', 'utf8'));
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.fromSecretKey(new Uint8Array(state.payer));
+    const mint = new PublicKey(state.mint);
+    const tokenOwner = Keypair.fromSecretKey(new Uint8Array(state.tokenOwner));
+    const delegate = new PublicKey(state.delegatePublicKey);
+
+    // Step 1: Get delegated accounts to revoke via getCompressedTokenAccountsByDelegate
+    const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate(
+        delegate,
+        { mint }
+    );
+    const delegatedBalanceBefore = delegateAccounts.items.reduce(
+        (sum, account) => sum.add(account.parsed.amount),
+        new BN(0)
+    );
+    console.log("Delegated balance:", delegatedBalanceBefore.toNumber() / 1_000_000_000, "tokens");
+
+    console.log("Revoke Delegate");
+    // Step 2: Call revoke() with delegated accounts and token owner
+    // Remove delegate authority and return control to owner
+    const revokeTx = await revoke(
+        rpc,
+        payer,
+        delegateAccounts.items, // delegated accounts to revoke
+        tokenOwner, // owner of compressed tokens
+    );
+
+    console.log("Delegate revoked!");
+    console.log("Transaction:", revokeTx);
+
+    // Step 3: Verify delegation removed via getCompressedTokenAccountsByDelegate
+    const delegateAccountsAfter = await rpc.getCompressedTokenAccountsByDelegate(
+        delegate,
+        { mint }
+    );
+
+    const ownerAccounts = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+    const ownerBalance = ownerAccounts.items.reduce(
+        (sum, account) => sum.add(account.parsed.amount),
+        new BN(0)
+    );
+
+    console.log("\nFinal balances:");
+    console.log("Delegated accounts:", delegateAccountsAfter.items.length);
+    console.log("Owner balance after revocation:", ownerBalance.toNumber() / 1_000_000_000, "tokens");
+
+    return {
+        revokeTransaction: revokeTx,
+        finalOwnerBalance: ownerBalance
+    };
+}
+
+revokeDelegates().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've successfully approved and revoked delegation for compressed tokens. The output shows: + +* **Approval completion**: Delegate authority granted for specified token amount +* **Delegation verification**: Confirmed delegate accounts exist with correct amounts +* **Revocation completion**: Delegate authority removed and tokens returned to owner +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"Account is not delegated" + +Attempting to revoke non-delegated accounts. + +```typescript +/// Verify accounts are delegated before revocation. +const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate( + delegate.publicKey, + { mint } +); + +if (delegateAccounts.items.length === 0) { + console.log("No delegated accounts to revoke"); + return; +} +``` + +
+ +## Advanced Configuration + +
+ +Approve Multiple Delegates + +```typescript +const delegates = [ + Keypair.generate().publicKey, + Keypair.generate().publicKey, +]; + +const amounts = [ + 200_000_000, // 0.2 tokens to first delegate + 300_000_000, // 0.3 tokens to second delegate +]; + +// Approve each delegate +for (let i = 0; i < delegates.length; i++) { + const approveTx = await approve( + rpc, + payer, + mint, + amounts[i], + tokenOwner, + delegates[i], + ); + + console.log(`Delegate ${i + 1} approved:`, approveTx); +} +``` + +
+ +
+ +Revoke Multiple Delegates + +```typescript +const delegates = [ + new PublicKey("DELEGATE_1_ADDRESS"), + new PublicKey("DELEGATE_2_ADDRESS"), +]; + +// Revoke each delegate +for (const delegate of delegates) { + // Get delegated accounts for this delegate + const delegateAccounts = await rpc.getCompressedTokenAccountsByDelegate( + delegate, + { mint } + ); + + if (delegateAccounts.items.length > 0) { + const revokeTx = await revoke( + rpc, + payer, + delegateAccounts.items, + tokenOwner, + ); + + console.log(`Delegate ${delegate.toBase58()} revoked:`, revokeTx); + } +} +``` + +
+ +## Next Steps + +That's it! Explore more guides in our cookbook section, or check out the advanced guides. + +{% columns %} +{% column %} +{% content-ref url="./" %} +[.](./) +{% endcontent-ref %} +{% endcolumn %} + +{% column %} +{% content-ref url="../advanced-guides/" %} +[advanced-guides](../advanced-guides/) +{% endcontent-ref %} +{% endcolumn %} +{% endcolumns %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-compress-and-decompress-spl-tokens.md b/compression-docs/compressed-tokens/cookbook/how-to-compress-and-decompress-spl-tokens.md new file mode 100644 index 0000000..8b60bfe --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-compress-and-decompress-spl-tokens.md @@ -0,0 +1,495 @@ + + +# How to compress and decompress SPL Tokens + +*** + +The `compress()` and `decompress()` functions convert SPL tokens between compressed and regular format. + +The functions perform opposite operations: + +1. `compress()` locks SPL tokens in a token pool account and creates compressed accounts +2. `decompress()` withdraws SPL tokens from the token pool to an Associated Token Account and invalidates compressed accounts + +Before we convert formats, we need: + +* SPL mint registered with the compressed token program via [`createMint()`](how-to-create-and-register-a-mint-account-for-compression.md) or [`createTokenPool()`](how-to-create-compressed-token-pools-for-mint-accounts.md) +* for `compress()` SPL tokens in an Associated Token Account +* For `decompress()` compressed token accounts with sufficient balance + +{% hint style="success" %} +**Function Difference and Best Practice:** + +* `compress(amount, sourceTokenAccount, toAddress)` compresses specific amounts from\ + source to a specified recipient. Use for transfers and precise amounts. +* `compressSplTokenAccount(tokenAccount, remainingAmount)` compresses the entire SPL token account balance minus optional remaining amount only to the same owner. Use to migrate complete token accounts with optional partial retention. [Here is how.](how-to-compress-complete-token-accounts.md) +{% endhint %} + +{% code title="function-decompress-compress.ts" %} +```typescript + import { decompress, compress } from '@lightprotocol/compressed-token'; + import { PublicKey } from '@solana/web3.js'; + import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token'; + + const mint = new PublicKey("YOUR_EXISTING_MINT_ADDRESS"); + const recipient = new PublicKey("RECIPIENT_WALLET_ADDRESS"); + const amount = 1_000_000_000; // 1 token (9 decimals) + + // Create ATA for decompressed tokens + const tokenAccount = await getOrCreateAssociatedTokenAccount( + rpc, payer, mint, payer.publicKey + ); + + // Decompress compressed tokens to SPL tokens + const transactionSignature = await decompress( + rpc, + payer, + mint, // SPL mint with token pool for compression + amount, + payer, // owner of compressed tokens + tokenAccount.address, // destination token account (toAddress parameter) + ); + + // Compress SPL tokens to compressed tokens + const compressionSignature = await compress( + rpc, + payer, + mint, // SPL mint with token pool for compression + amount, + payer, // owner of SPL tokens + tokenAccount.address, // source SPL token account (sourceTokenAccount parameter) + recipient, // recipient owner address (toAddress parameter) + ); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} + [Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Decompress Tokens + +Convert compressed tokens to regular SPL tokens. + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL mint with token pool and mint initial compressed tokens
+// 3. Call decompress() to convert compressed tokens to SPL tokens
+// 4. Verify decompressed balance via getTokenAccountBalance
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { 
+    createMint, 
+    mintTo, 
+    decompress
+} from '@lightprotocol/compressed-token';
+import { 
+    getOrCreateAssociatedTokenAccount,
+    TOKEN_PROGRAM_ID 
+} from '@solana/spl-token';
+import * as fs from 'fs';
+import * as os from 'os';
+
+async function decompressTokens() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL mint with token pool and mint initial compressed tokens
+    const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
+    console.log("Mint with token pool created:", mint.toBase58());
+
+    const tokenOwner = Keypair.generate();
+    const compressedAmount = 1_000_000_000; // 1 token with 9 decimals
+    await mintTo(rpc, payer, mint, tokenOwner.publicKey, payer, compressedAmount);
+    console.log("Compressed tokens minted:", compressedAmount / 1_000_000_000, "tokens");
+
+    // Create or get Associated Token Account for decompression
+    const tokenAccount = await getOrCreateAssociatedTokenAccount(
+        rpc, payer, mint, tokenOwner.publicKey, false, TOKEN_PROGRAM_ID
+    );
+
+    const decompressAmount = 500_000_000; // 0.5 tokens
+    console.log("Decompress Tokens");
+
+    // Step 3: Call decompress() to convert to SPL tokens
+    // Withdraw SPL tokens from omnibus pool and burn compressed tokens
+    const decompressTx = await decompress(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        decompressAmount, // amount to decompress
+        tokenOwner, // owner of compressed tokens
+        tokenAccount.address, // destination token account (toAddress parameter)
+    );
+
+    console.log("Tokens decompressed:", decompressAmount / 1_000_000_000, "tokens");
+    console.log("Transaction:", decompressTx);
+
+    // Verify decompressed balance in SPL token account
+    const tokenBalance = await rpc.getTokenAccountBalance(tokenAccount.address);
+    console.log("SPL token balance:", tokenBalance.value.uiAmount);
+
+    // Save state for compress step
+    const state = {
+        mint: mint.toBase58(),
+        tokenOwner: Array.from(tokenOwner.secretKey),
+        tokenAccount: tokenAccount.address.toBase58(),
+        payer: Array.from(payer.secretKey)
+    };
+    fs.writeFileSync('./shared-state.json', JSON.stringify(state, null, 2));
+
+    return { 
+        mint,
+        tokenOwner,
+        tokenAccount: tokenAccount.address,
+        decompressTransaction: decompressTx
+    };
+}
+
+decompressTokens().catch(console.error);
+
+{% endstep %} + +{% step %} +### Compress Tokens + +Continue from the previous step to compress regular SPL tokens back to compressed format. + +
// Continue from Step 1 - compress SPL tokens
+// 1. Call compress() to convert SPL tokens to compressed format
+// 2. Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { compress } from '@lightprotocol/compressed-token';
+import { 
+    mintTo as splMintTo,
+    TOKEN_PROGRAM_ID 
+} from '@solana/spl-token';
+import BN from 'bn.js';
+import * as fs from 'fs';
+
+async function compressTokens() {
+    if (!fs.existsSync('./shared-state.json')) {
+        console.log("No SPL tokens found. Please run 'npx tsx decompress.ts first.");
+        return;
+    }
+
+    const state = JSON.parse(fs.readFileSync('./shared-state.json', 'utf8'));
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.fromSecretKey(new Uint8Array(state.payer));
+    const mint = new PublicKey(state.mint);
+    const tokenOwner = Keypair.fromSecretKey(new Uint8Array(state.tokenOwner));
+    const tokenAccount = new PublicKey(state.tokenAccount);
+
+    console.log("Compress Tokens");
+
+    // Add SPL tokens to account for compression
+    await splMintTo(rpc, payer, mint, tokenAccount, payer, 300_000_000, [], undefined, TOKEN_PROGRAM_ID);
+    
+    const compressAmount = 400_000_000; // 0.4 tokens
+
+    // Step 1: Call compress() to convert to compressed format
+    // Lock SPL tokens to pool account and mint compressed tokens
+    const compressTx = await compress(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        compressAmount, // amount to compress
+        tokenOwner, // owner of SPL tokens
+        tokenAccount, // source token account
+        tokenOwner.publicKey, // recipient for compressed tokens
+    );
+
+    console.log("Compressed amount:", compressAmount / 1_000_000_000, "tokens");
+    console.log("Transaction:", compressTx);
+
+    // Step 2: Verify balances via getTokenAccountBalance and getCompressedTokenAccountsByOwner
+    const finalTokenBalance = await rpc.getTokenAccountBalance(tokenAccount);
+    const finalCompressedAccounts = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+    
+    // Calculate total compressed balance
+    const finalCompressedBalance = finalCompressedAccounts.items.reduce(
+        (sum, account) => sum.add(account.parsed.amount),
+        new BN(0)
+    );
+
+    console.log("\nFinal balances:");
+    console.log("Regular SPL tokens:", finalTokenBalance.value.uiAmount);
+    console.log("Compressed tokens:", finalCompressedBalance.toNumber() / 1_000_000_000);
+
+    return { 
+        compressTransaction: compressTx,
+        finalCompressedBalance,
+        finalSplBalance: finalTokenBalance.value.amount
+    };
+}
+
+compressTokens().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've decompressed and compressed tokens. The output shows: + +* **Decompression**: Compressed tokens converted to regular SPL tokens in your Associated Token Account +* **Compression**: Regular SPL tokens converted to compressed tokens +* **Balance verification**: Both operations confirmed with token amounts +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"Insufficient balance" difference between decompress and compress + +Check your balances before operations: + +```typescript +// For decompression - check compressed balance +const compressedAccounts = await rpc.getCompressedTokenAccountsByOwner( + owner.publicKey, + { mint } +); +const compressedBalance = compressedAccounts.items.reduce( + (sum, account) => sum.add(account.parsed.amount), + new BN(0) +); + +// For compression - check SPL token balance +const tokenBalance = await rpc.getTokenAccountBalance(tokenAccount); +const splBalance = new BN(tokenBalance.value.amount); + +console.log("Can decompress up to:", compressedBalance.toString()); +console.log("Can compress up to:", splBalance.toString()); +``` + +
+ +
+ +"Invalid owner" + +Ensure the signer owns the tokens being decompressed/compressed: + +```typescript +// The owner parameter must be the actual owner +const decompressTx = await decompress( + rpc, + payer, // can be different (pays fees) + mint, + amount, + actualOwner, // must own compressed tokens + destinationAta, +); + +const compressTx = await compress( + rpc, + payer, // can be different (pays fees) + mint, + amount, + actualOwner, // must own SPL tokens + sourceAta, + recipient, +); +``` + +
+ +## Advanced Configurations + +
+ +Compress to Different Owner + +Compress tokens directly to someone else: + +```typescript +const recipientWallet = new PublicKey("RECIPIENT_WALLET_ADDRESS"); + +// Compress your SPL tokens to recipient +const compressTx = await compress( + rpc, + payer, + mint, + amount, + tokenOwner, // current owner signs + tokenAccount, // your token account + recipientWallet, // recipient gets compressed tokens +); +``` + +
+ +
+ +Batch Operations + +Compress multiple token accounts: + +```typescript +// Compress to multiple recipients at once +const recipients = [recipient1.publicKey, recipient2.publicKey, recipient3.publicKey]; +const amounts = [1_000_000_000, 2_000_000_000, 500_000_000]; // Different amounts + +const batchCompressTx = await compress( + rpc, + payer, + mint, + amounts, // Array of amounts + owner, + tokenAccount, + recipients, // Array of recipients +); + +console.log("Batch compression completed:", batchCompressTx); +``` + +
+ +
+ +Decompress with Delegate Authority + +Decompress tokens using delegate authority: + +```typescript +import { decompressDelegated } from '@lightprotocol/compressed-token'; +import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token'; + +// Get ATA for decompressed tokens +const ataAddress = await getAssociatedTokenAddress( + mint, + recipient, + false, + TOKEN_PROGRAM_ID +); + +// Delegate decompresses tokens +await decompressDelegated( + rpc, + payer, + mint, + amount, + delegate, // Signer - owner of compressed tokens + ataAddress, // Uncompressed token account (ATA) +); +``` + +
+ +## Next Steps + +Learn how to compress complete token accounts in one transaction to reclaim rent. + +{% content-ref url="how-to-compress-complete-token-accounts.md" %} +[how-to-compress-complete-token-accounts.md](how-to-compress-complete-token-accounts.md) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-compress-complete-token-accounts.md b/compression-docs/compressed-tokens/cookbook/how-to-compress-complete-token-accounts.md new file mode 100644 index 0000000..dacbc1b --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-compress-complete-token-accounts.md @@ -0,0 +1,427 @@ + + +# How to Compress complete Token Accounts + +The `compressSplTokenAccount` function compresses the balance of an SPL token account, with an optional `remainingAmount` parameter to leave tokens in the original account. + +The function + +1. Transfer SPL tokens from ATA to token pool account (omnibus account) +2. Create equivalent compressed accounts for the account owner + +Before compressing SPL token accounts, you need: + +* SPL mint registered with the compressed token program via [`createMint()`](how-to-create-and-register-a-mint-account-for-compression.md) or [`createTokenPool()`](how-to-create-compressed-token-pools-for-mint-accounts.md) +* SPL token account (ATA) with tokens to compress + +After compression, empty token accounts can now be closed to reclaim rent with [`closeAccount()`](https://solana.com/developers/cookbook/tokens/close-token-accounts). + +{% hint style="success" %} +**Function Difference and Best Practice:** + +* `compressSplTokenAccount(tokenAccount, remainingAmount)` compresses the entire SPL token\ + account balance minus optional remaining amount only to the same owner. Use to migrate complete token\ + accounts with optional partial retention. +* `compress(amount, sourceTokenAccount, toAddress)` compresses specific amounts from\ + source to a specified recipient. Use for transfers and precise amounts. [Here is how.](how-to-compress-and-decompress-spl-tokens.md) +{% endhint %} + +{% code title="function-compress-spl-accounts.ts" %} +```typescript +import { compressSplTokenAccount } from '@lightprotocol/compressed-token'; +import { Keypair, PublicKey } from '@solana/web3.js'; +import { bn } from '@lightprotocol/stateless.js'; + +const mint = new PublicKey("YOUR_MINT_ADDRESS"); +const owner = payer; // Account owner (signer) +const tokenAccount = new PublicKey("YOUR_TOKEN_ACCOUNT_ADDRESS"); +const remainingAmount = bn(100_000_000); // Optional: amount to keep uncompressed + +// Transfers SPL tokens to omnibus pool and mints compressed tokens, +const transactionSignature = await compressSplTokenAccount( + rpc, + payer, + mint, // SPL mint with token pool for compression + owner, + tokenAccount, // SPL token account to compress + remainingAmount, // optional amount to keep in SPL format +); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your actual API key. + +{% hint style="info" %} +[Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Compressing SPL Token Accounts + +Run this script to compress an entire SPL token account! + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL token account with tokens
+// 3. Call compressSplTokenAccount() to convert SPL tokens to compressed format
+// 4. Verify results via getTokenAccountBalance and getCompressedTokenAccountsByOwner
+// 5. Optional: reclaim rent for empty token accounts
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc, bn } from '@lightprotocol/stateless.js';
+import { 
+    createMint, 
+    compressSplTokenAccount 
+} from '@lightprotocol/compressed-token';
+import { 
+    createAssociatedTokenAccount,
+    mintTo,
+    getAssociatedTokenAddress,
+    TOKEN_PROGRAM_ID 
+} from '@solana/spl-token';
+
+async function compressSplTokenAccounts() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL token account with tokens
+    let { mint, tokenAccount, tokenOwner } = await setup();
+
+    // Check balances before compression - SPL account and compressed token accounts
+    const splBalanceBefore = await rpc.getTokenAccountBalance(tokenAccount);
+    const compressedAccountsBefore = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+
+    console.log("\nBefore Compression:");
+    console.log("SPL token balance:", Number(splBalanceBefore.value.amount) / 1_000_000_000, "tokens");
+    console.log("Compressed accounts:", compressedAccountsBefore.items.length);
+
+    // Step 3: Call compressSplTokenAccount() to convert SPL tokens to compressed format
+    // Transfer SPL tokens to omnibus pool and mint compressed tokens
+    const compressTx = await compressSplTokenAccount(
+        rpc,
+        payer,
+        mint, // mint with existing token pool
+        tokenOwner,
+        tokenAccount, // SPL token account to be compressed
+    );
+
+    console.log("SPL token account compressed!");
+    console.log("Transaction:", compressTx);
+
+    // Step 4: Verify SPL and compressed token account balance
+    const splBalanceAfter = await rpc.getTokenAccountBalance(tokenAccount);
+    const compressedAccountsAfter = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+
+    console.log("\nAfter Compression:");
+    console.log("SPL token balance:", Number(splBalanceAfter.value.amount) / 1_000_000_000, "tokens");
+    console.log("Compressed accounts:", compressedAccountsAfter.items.length);
+
+    // Calculate total compressed balance from all compressed accounts
+    const totalCompressed = compressedAccountsAfter.items.reduce(
+        (sum, account) => sum.add(account.parsed.amount),
+        bn(0)
+    );
+    console.log("Total compressed balance:", totalCompressed.toNumber() / 1_000_000_000, "tokens");
+
+    // Check if SPL account can be closed for rent reclaim
+    if (Number(splBalanceAfter.value.amount) === 0) {
+        console.log("SPL token account is now empty and can be closed to reclaim rent!");
+    }
+
+    console.log("Transaction signature:", compressTx);
+
+    return { 
+        compressTransaction: compressTx,
+        tokenAccount,
+        splBalanceAfter: Number(splBalanceAfter.value.amount),
+        compressedBalance: totalCompressed.toNumber()
+    };
+
+    async function setup() {
+        
+        const { mint } = await createMint(
+            rpc,
+            payer,
+            payer.publicKey,
+            9,
+        );
+        console.log("Compressed mint created:", mint.toBase58());
+
+        const tokenOwner = Keypair.generate();
+        
+        const tokenAccount = await createAssociatedTokenAccount(
+            rpc,
+            payer,
+            mint,
+            tokenOwner.publicKey,
+        );
+        console.log("SPL token account created:", tokenAccount.toBase58());
+
+        const mintAmount = 2_000_000_000;
+        await mintTo(
+            rpc,
+            payer,
+            mint,
+            tokenAccount,
+            payer,
+            mintAmount,
+        );
+        console.log("SPL tokens minted:", mintAmount / 1_000_000_000, "tokens");
+
+        return {
+            mint,
+            tokenAccount,
+            tokenOwner,
+        };
+    }
+}
+
+compressSplTokenAccounts().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've successfully compressed an SPL token account. The output shows: + +* **Account compression**: Entire SPL token account balance converted to compressed format +* **Balance verification**: All tokens migrated from SPL account to compressed accounts +* **Rent Reclaimable**: The empty SPL account can now be closed to reclaim rent with [`closeAccount()`](https://solana.com/developers/cookbook/tokens/close-token-accounts). +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"Insufficient balance in token account" + +The token account doesn't have enough tokens for the operation. + +```typescript +// Check token account balance before compression +const balance = await rpc.getTokenAccountBalance(tokenAccount); + +if (Number(balance.value.amount) === 0) { + console.log("Token account is empty"); + return; +} + +console.log("Available balance:", Number(balance.value.amount)); + +// Proceed with compression +const compressTx = await compressSplTokenAccount( + rpc, + payer, + mint, + owner, + tokenAccount, +); +``` + +
+ +
+ +"Remaining amount exceeds balance" + +The `remainingAmount` parameter exceeds the current account balance. + +```typescript +const balance = await rpc.getTokenAccountBalance(tokenAccount); +const availableAmount = Number(balance.value.amount); +const remainingAmount = bn(500_000_000); // 0.5 tokens + +if (remainingAmount.gt(bn(availableAmount))) { + console.log(`Cannot leave ${remainingAmount.toString()} tokens`); + console.log(`Available balance: ${availableAmount}`); + throw new Error("Remaining amount exceeds balance"); +} + +// Use valid remaining amount +const compressTx = await compressSplTokenAccount( + rpc, + payer, + mint, + owner, + tokenAccount, + remainingAmount, // must be <= balance +); +``` + +
+ +## Advanced Configuration + +
+ +Partial Account Compression + +Compress most tokens while leaving some in SPL format: + +```typescript +import { bn } from '@lightprotocol/stateless.js'; + +// Leave 100 tokens (0.1 with 9 decimals) in SPL account +const remainingAmount = bn(100_000_000); + +const compressTx = await compressSplTokenAccount( + rpc, + payer, + mint, + owner, + tokenAccount, + remainingAmount, // amount to keep in SPL format +); + +// Account will retain remainingAmount tokens +``` + +
+ +
+ +Compress Multiple Accounts + +Compress several token accounts for the same mint: + +```typescript +const tokenAccounts = [ + { account: new PublicKey("ACCOUNT_1"), owner: owner1 }, + { account: new PublicKey("ACCOUNT_2"), owner: owner2 }, + { account: new PublicKey("ACCOUNT_3"), owner: owner3 }, +]; + +// Compress each account +for (const { account, owner } of tokenAccounts) { + console.log(`Compressing account: ${account.toBase58()}`); + + try { + const compressTx = await compressSplTokenAccount( + rpc, + payer, + mint, + owner, + account, + ); + console.log(`Compressed: ${compressTx}`); + } catch (error) { + console.log(`Failed: ${error.message}`); + } +} +``` + +
+ +## Next Steps + +Learn how to merge multiple compressed token accounts into one to simplify state management. + +{% content-ref url="how-to-merge-compressed-token-accounts.md" %} +[how-to-merge-compressed-token-accounts.md](how-to-merge-compressed-token-accounts.md) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-create-and-register-a-mint-account-for-compression.md b/compression-docs/compressed-tokens/cookbook/how-to-create-and-register-a-mint-account-for-compression.md new file mode 100644 index 0000000..114015f --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-create-and-register-a-mint-account-for-compression.md @@ -0,0 +1,244 @@ + + +# How to Create and Register a Mint Account for Compression + +*** + +{% hint style="info" %} +The mint account itself requires rent (like regular SPL mints), but individual compressed token accounts are rent-free. +{% endhint %} + +Compressed tokens use an SPL mint that is registered with the compressed token program. Connect an existing SPL mint with [`createTokenPool()`](how-to-create-compressed-token-pools-for-mint-accounts.md) or use `createMint()` to create a new one from scratch. + +The `createMint()` function performs three operations: + +1. Create a standard SPL mint account `createMintAccountInstruction` +2. Initialize the mint `initializeMintInstruction` to set authority, decimals, etc. +3. Create token pool PDA with `createTokenPoolInstruction`, the [omnibus account](#user-content-fn-1)[^1] for compression and decompression of tokens + +{% hint style="success" %} +**Best Practice:** Each mint supports a maximum of 4 token pools total. During compression/decompression operations, token pools get write-locked. Use `addTokenPools()` to create additional pools to increase per-block write-lock capacity. +{% endhint %} + +{% code title="function-create-mint.ts" %} +```typescript + import { createMint } from '@lightprotocol/compressed-token'; + + const decimals = 9; + const mintAuthority = payer; + const freezeAuthority = null; // optional + + // Create SPL mint with token pool for compression + const { mint, transactionSignature } = await createMint( + rpc, + payer, + mintAuthority.publicKey, + decimals, + ); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && +npm install \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +**Alternatives:** + +```bash +yarn add --dd typescript tsx @types/node && +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} +Get your API Key [here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Create SPL Mint with Token Pool for Compression + +Run this script to create a mint account with token pool for compression. + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL mint with token pool for compression via createMint()
+
+import { Keypair } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { createMint } from '@lightprotocol/compressed-token';
+
+async function createCompressedMint() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+        const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Call createMint() to create mint account and token pool for compression
+    const { mint, transactionSignature } = await createMint(
+        rpc,
+        payer,
+        payer.publicKey, // mint authority
+        9
+    );
+
+    console.log("SPL mint with token pool for compression created");
+    console.log("Mint address:", mint.toBase58());
+    console.log("Transaction:", transactionSignature);
+
+
+    return { mint, transactionSignature };
+}
+
+createCompressedMint().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've just created and registered a mint account with token pool for ZK Compression. The output shows: + +* Mint address +* Transaction signature +{% endstep %} +{% endstepper %} + +## Advanced Configurations + +
+ +Customize Mint Authority + +Customize who can mint new compressed tokens. + +```typescript +const mintAuthority = Keypair.generate(); + +const { mint, transactionSignature } = await createMint( + rpc, + payer, + mintAuthority.publicKey, + 9, +); +``` + +
+ +
+ +Add Freeze Authority + +Customize who can freeze/thaw compressed token accounts. + +```typescript +const freezeAuthority = Keypair.generate(); + +const { mint, transactionSignature } = await createMint( + rpc, + payer, + payer.publicKey, // mint authority + 9, // decimals + Keypair.generate(), // mint keypair + undefined, // confirm options + undefined, // token program ID + freezeAuthority.publicKey, // freeze authority +); +``` + +
+ +## Next Steps + +Learn How to Create Compressed Token Accounts in our next section. + +{% content-ref url="how-to-create-compressed-token-accounts.md" %} +[how-to-create-compressed-token-accounts.md](how-to-create-compressed-token-accounts.md) +{% endcontent-ref %} + +[^1]: SPL token account that holds SPL tokens corresponding to compressed tokens in circulation. Tokens are locked when compressed and withdrawn when decompressed. Owned by the compressed token program's CPI authority PDA. diff --git a/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-accounts.md b/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-accounts.md new file mode 100644 index 0000000..ea83c79 --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-accounts.md @@ -0,0 +1,27 @@ + + +# How to Create Compressed Token Accounts + +*** + +Compressed token accounts store ownership information for compressed tokens like regular token accounts with three core differences. Compressed Tokens + +* do not require an [Associated Token Accounts (ATAs)](#user-content-fn-1)[^1], +* do not require a rent-exempt balance, and +* are not explicitly created. + +Compressed token accounts are automatically created in the following scenarios: + +1. `mintTo()` creates compressed token accounts for recipients + +{% content-ref url="how-to-mint-compressed-tokens.md" %} +[how-to-mint-compressed-tokens.md](how-to-mint-compressed-tokens.md) +{% endcontent-ref %} + +2. `transfer()` creates compressed token accounts for recipients + +{% content-ref url="how-to-transfer-compressed-token.md" %} +[how-to-transfer-compressed-token.md](how-to-transfer-compressed-token.md) +{% endcontent-ref %} + +[^1]: An associated token account is a token account with an address that's a Program Derived Address (PDA) created by the Associated Token Program. You can think of an associated token account as the default token account for a user to hold units of a specific token (mint). diff --git a/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-pools-for-mint-accounts.md b/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-pools-for-mint-accounts.md new file mode 100644 index 0000000..2cf200b --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-create-compressed-token-pools-for-mint-accounts.md @@ -0,0 +1,291 @@ + + +# How to Create Compressed Token Pools for Mint Accounts + +The `createTokenPool()` function registers an existing SPL mint with the compressed token program and creates a token pool PDA. `createTokenPool()` requires only `fee_payer` and has no mint authority constraint. + +{% hint style="info" %} +The token pool account itself requires rent, but individual compressed token accounts are rent-free. +{% endhint %} + +The function + +1. registers an existing SPL mint with the compressed token program and +2. create token pool PDA with `createTokenPoolInstruction`, the omnibus account that holds SPL tokens corresponding to compressed tokens in circulation + +Before we create a token pool, we need an existing SPL mint account. + +{% hint style="success" %} +**Best Practice:** Each mint supports a maximum of 4 token pools total. During compression/decompression operations, token pools get write-locked. Use `addTokenPools()` to create additional pools to increase per-block write-lock capacity. +{% endhint %} + +{% code title="function-create-token-pool.ts" %} +```typescript +import { createTokenPool } from '@lightprotocol/compressed-token'; +import { PublicKey } from '@solana/web3.js'; + +const mint = new PublicKey("YOUR_EXISTING_SPL_MINT_ADDRESS"); + +// Registers existing SPL mint with compressed token program, creates token pool account +const transactionSignature = await createTokenPool( + rpc, + payer, + mint, +); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} +[Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Creating Token Pools + +Run this script to create token pools for an SPL mint! + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL mint 
+// 3. Call createTokenPool() to register mint with compressed token program
+// 4. Add additional pools to increase write-lock capacity (optional)
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { createTokenPool, addTokenPools } from '@lightprotocol/compressed-token';
+import { createMint } from '@solana/spl-token';
+
+async function createTokenPools() {
+
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL mint
+    const mint = await createMint(
+        rpc,
+        payer,
+        payer.publicKey, // mint authority
+        payer.publicKey, // freeze authority
+        9
+    );
+
+    console.log("SPL mint created");
+    console.log("Mint address:", mint.toBase58());
+
+    // Step 3: Call createTokenPool() to register SPL mint with compressed token program
+    // Creates token pool PDA (omnibus account) that holds SPL tokens for compressed tokens
+    const poolTx = await createTokenPool(
+        rpc,
+        payer,
+        mint // existing SPL mint to register
+    );
+
+    console.log("\nToken pool created!");
+    console.log("SPL mint registered with compressed token program:", mint.toBase58());
+    console.log("Pool transaction:", poolTx);
+    
+    // Step 4: Add up to 3 additional pools - increase write-lock capacity for higher throughput
+    const additionalPoolsCount = 2;
+    const additionalPoolsTx = await addTokenPools(
+        rpc,
+        payer,
+        mint, // SPL mint with existing token pool
+        additionalPoolsCount, // number of additional pools (max 3 more)
+    );
+
+    console.log(`\nAdded ${additionalPoolsCount} additional token pools!`);
+    console.log("Additional pools transaction:", additionalPoolsTx);
+
+
+    return { 
+        mint,
+        poolTransaction: poolTx,
+        additionalPoolsTransaction: additionalPoolsTx
+    };
+}
+
+createTokenPools().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've created multiple token pools for an SPL mint. The output shows: + +* **Token pool creation**: Omnibus account registered for compression/decompression +* **Additional pools**: Multiple pools created for increased write-lock limit per block +* **Transaction confirmations**: All pool creation operations confirmed on-chain +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"TokenPool not found" + +You're trying to access a token pool that doesn't exist. + +```typescript +// Create the missing token pool +const poolTx = await createTokenPool(rpc, payer, mint); +console.log("Token pool created:", poolTx); +``` + +
+ +## Advanced Configuration + +
+ +Batch Pool Creation + +Create pools for multiple mints: + +```typescript +const mints = [ + new PublicKey("MINT_1_ADDRESS"), + new PublicKey("MINT_2_ADDRESS"), + new PublicKey("MINT_3_ADDRESS"), +]; + +for (const mint of mints) { + try { + const poolTx = await createTokenPool(rpc, payer, mint); + console.log(`Pool created for ${mint.toBase58()}:`, poolTx); + } catch (error) { + console.log(`Failed for ${mint.toBase58()}:`, error.message); + } +} +``` + +
+ +
+ +Create Pool with Token-2022 + +Create token pools for Token-2022 mints: + +```typescript +import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; + +const poolTx = await createTokenPool( + rpc, + payer, + mint, // Token-2022 mint + undefined, + TOKEN_2022_PROGRAM_ID, +); +``` + +
+ +## Next Steps + +Learn how to approve and revoke delegate authority for compressed token accounts. + +{% content-ref url="how-to-approve-and-revoke-delegate-authority.md" %} +[how-to-approve-and-revoke-delegate-authority.md](how-to-approve-and-revoke-delegate-authority.md) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-merge-compressed-token-accounts.md b/compression-docs/compressed-tokens/cookbook/how-to-merge-compressed-token-accounts.md new file mode 100644 index 0000000..74388e1 --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-merge-compressed-token-accounts.md @@ -0,0 +1,349 @@ + + +# How to Merge Compressed Token Accounts + +The `mergeTokenAccounts()` function consolidates multiple compressed accounts of the same mint into a single compressed account. + +The function + +1. Consume multiple input compressed token accounts (up to 8 accounts) +2. Create single output compressed account with combined balance for the owner + +Before we merge compressed accounts, we need: + +* Multiple compressed token accounts of the same mint owned by the same wallet +* SPL mint registered with the compressed token program via [`createMint()`](how-to-create-and-register-a-mint-account-for-compression.md) or [`createTokenPool()`](how-to-create-compressed-token-pools-for-mint-accounts.md) + +{% hint style="success" %} +State trees where compressed account's are stored, are append only. `mergeTokenAccounts()` reduces account fragmentation to simplify balance calculations from `getCompressedTokenAccountsByOwner` +{% endhint %} + +{% code title="function-merge-accounts.ts" %} +```typescript +import { mergeTokenAccounts } from '@lightprotocol/compressed-token'; +import { Keypair, PublicKey } from '@solana/web3.js'; + +const mint = new PublicKey("YOUR_MINT_ADDRESS"); +const owner = payer; + +// Combines multiple compressed token accounts into single compressed account +const transactionSignature = await mergeTokenAccounts( + rpc, + payer, + mint, // SPL mint with token pool for compression + owner, +); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to develop create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} + [Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+ + +{% endstep %} + +{% step %} +### Merging Compressed Accounts + +Run this script to merge multiple compressed token accounts into one! + +
// 1: Setup funded payer and connect to local validator
+// 2. Create mint and multiple compressed accounts  
+// 3. Call mergeTokenAccounts() to consolidate multiple compressed accounts to one output
+// 4. Use getCompressedTokenAccountsByOwner() to query account states before and after merge
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { 
+    createMint, 
+    mintTo, 
+    mergeTokenAccounts 
+} from '@lightprotocol/compressed-token';
+
+async function mergeCompressedAccounts() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL mint with token pool for compression
+    const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
+    console.log("SPL Mint with token pool created:", mint.toBase58());
+
+    const tokenOwner = Keypair.generate();
+    const amounts = [300_000_000, 200_000_000, 500_000_000]; // 0.3, 0.2, 0.5 tokens
+    
+    console.log("Creating multiple compressed accounts...");
+    
+    for (let i = 0; i < amounts.length; i++) {
+        await mintTo(
+            rpc,
+            payer,
+            mint, // SPL mint with token pool for compression
+            tokenOwner.publicKey,// recipient address (toPubkey parameter)
+            payer, // mint authority
+            amounts[i],
+        );
+    }
+
+    // Step 2a: Get all compressed accounts before merging
+    const accountsBefore = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+
+    console.log("Number of accounts before merge:", accountsBefore.items.length);
+    
+    // Step 2b: Calculate total balance across all compressed accounts
+    const totalBalance = accountsBefore.items.reduce(
+        (sum, account) => sum.add(account.parsed.amount),
+        new (require('bn.js'))(0)
+    );
+    console.log("Total balance:", totalBalance.toNumber() / 1_000_000_000, "tokens");
+
+    accountsBefore.items.forEach((account, index) => {
+        console.log(`Account ${index + 1}:`, account.parsed.amount.toNumber() / 1_000_000_000, "tokens");
+    });
+
+    // Step 3: Call mergeTokenAccounts() to consolidate into single account
+    // Nullify old compressed accounts and create one with combined balance
+    const mergeTx = await mergeTokenAccounts(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        tokenOwner,
+    );
+
+    console.log("\nMerge Compressed Accounts...");
+    console.log("Transaction:", mergeTx);
+
+    // Step 4: Verify merge results - check single compressed account contains total balance
+    const accountsAfter = await rpc.getCompressedTokenAccountsByOwner(
+        tokenOwner.publicKey,
+        { mint }
+    );
+
+    console.log("Number of accounts after merge:", accountsAfter.items.length);
+    
+    if (accountsAfter.items.length > 0) {
+        const mergedBalance = accountsAfter.items[0].parsed.amount;
+        console.log("Merged account balance:", mergedBalance.toNumber() / 1_000_000_000, "tokens");
+    }
+
+
+    return { 
+        mint,
+        tokenOwner,
+        mergeTransaction: mergeTx,
+        accountsBefore: accountsBefore.items.length,
+        accountsAfter: accountsAfter.items.length
+    };
+
+}
+
+mergeCompressedAccounts().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've merged multiple compressed token accounts. The output shows: + +* **Account consolidation**: Multiple accounts merged into a single account +* **Unified Balance**: Total account balance maintained across the merge +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"No compressed token accounts found" + +The owner has no compressed token accounts for the specified mint: + +```typescript +// Check if accounts exist before merging +const accounts = await rpc.getCompressedTokenAccountsByOwner( + owner.publicKey, + { mint } +); + +if (accounts.items.length === 0) { + console.log("No compressed token accounts found for this mint"); + console.log("Mint address:", mint.toBase58()); + console.log("Owner address:", owner.publicKey.toBase58()); + return; +} + +console.log(`Found ${accounts.items.length} accounts to merge`); +``` + +
+ +## Advanced Configuration + +
+ +Conditional Merging + +```typescript +// Get account count +const accounts = await rpc.getCompressedTokenAccountsByOwner( + owner.publicKey, + { mint } +); + +// Only merge if more than 2 accounts +if (accounts.items.length > 2) { + console.log(`Merging ${accounts.items.length} accounts...`); + + const mergeTx = await mergeTokenAccounts( + rpc, + payer, + mint, + tokenOwner, + ); + + console.log("Merge completed:", mergeTx); +} else { + console.log("Merge not needed - optimal account structure"); +} +``` + +
+ +
+ +Merge Multiple Mints + +```typescript +const mints = [ + new PublicKey("MINT_1_ADDRESS"), + new PublicKey("MINT_2_ADDRESS"), +]; + +// Merge accounts for each mint +for (const mint of mints) { + console.log(`Merging accounts for mint: ${mint.toBase58()}`); + + const mergeTx = await mergeTokenAccounts( + rpc, + payer, + mint, + tokenOwner, + ); + + console.log(`Merge completed: ${mergeTx}`); +} +``` + +
+ +## Next Steps + +Learn how to create more compressed token pools for your SPL mint to increase write-lock limits. + +{% content-ref url="how-to-create-compressed-token-pools-for-mint-accounts.md" %} +[how-to-create-compressed-token-pools-for-mint-accounts.md](how-to-create-compressed-token-pools-for-mint-accounts.md) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/cookbook/how-to-mint-compressed-tokens.md b/compression-docs/compressed-tokens/cookbook/how-to-mint-compressed-tokens.md new file mode 100644 index 0000000..0624ecf --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-mint-compressed-tokens.md @@ -0,0 +1,333 @@ + + +# How to Mint Compressed Tokens + +The `mintTo()` function creates compressed token accounts for recipients and increases the mint's token supply. Only the mint authority can perform this operation. + +The `mintTo()` function + +1. Mints SPL tokens to token pool PDA, the [omnibus account](#user-content-fn-1)[^1] for compression and decompression of tokens +2. Create compressed accounts containing mint, owner, and amount for each recipient + +Before minting compressed tokens, you need an SPL mint registered with the compressed token program via [`createMint()`](how-to-create-and-register-a-mint-account-for-compression.md) for a new mint, or [`createTokenPool()`](how-to-create-compressed-token-pools-for-mint-accounts.md) for an existing mint. + +{% code title="function-mint-compressed-tokens.ts" %} +```typescript +import { mintTo } from '@lightprotocol/compressed-token'; +import { PublicKey } from '@solana/web3.js'; + +// Use existing mint with token pool for compression to mint compressed tokens +const mint = new PublicKey("MINT_ADDRESS"); +const recipient = new PublicKey("RECIPIENT_WALLET_ADDRESS"); +const amount = 1_000_000_000; // 1 token (9 decimals) + +// Mint compressed tokens - mints SPL tokens to pool, creates compressed token accounts +const transactionSignature = await mintTo( + rpc, + payer, + mint, // SPL mint with token pool for compression + recipient, // recipient address (toPubkey parameter) + payer, // mint authority + amount, +); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +**Dependencies** + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +**Developer Environment** + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} +Get your API Key [here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +#### Minting Compressed Tokens + +Run this script to mint compressed tokens to a recipient! + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL mint with token pool for compression
+// 3. Call mintTo() with mint, recipient, and amount - mint SPL tokens to pool and create compressed token accounts
+// 4. Verify via getCompressedTokenAccountsByOwner
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { createMint, mintTo } from '@lightprotocol/compressed-token';
+
+async function mintCompressedTokens() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2: Create SPL mint with token pool for compression
+    const { mint, transactionSignature: mintCreateTx } = await createMint(
+        rpc,
+        payer,
+        payer.publicKey, // mint authority
+        9
+    );
+
+    console.log("Mint with token pool for compression created!");
+    console.log("Mint address:", mint.toBase58());
+    console.log("Create mint transaction:", mintCreateTx);
+    
+    // Generate recipient keypair
+    const recipient = Keypair.generate();
+    
+    // Define amount to mint
+    const mintAmount = 1_000_000_000; // 1 token with 9 decimals
+
+    // Step 3: Call mintTo() with mint, recipient, and amount
+    // Mint SPL tokens to pool and create compressed token account
+    const transactionSignature = await mintTo(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        recipient.publicKey,
+        payer, // mint authority
+        mintAmount
+    );
+
+    console.log("\nCompressed token minted!");
+    console.log("Recipient:", recipient.publicKey.toBase58());
+    console.log("Compressed Token Balance:", mintAmount / 1_000_000_000, "tokens");
+    console.log("Mint token transaction:", transactionSignature);
+
+    // Step 4: Verify via getCompressedTokenAccountsByOwner
+    const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
+        recipient.publicKey,
+        { mint }
+    );
+
+    if (tokenAccounts.items.length > 0) {
+        const balance = tokenAccounts.items[0].parsed.amount;    }
+
+    return { transactionSignature, recipient: recipient.publicKey, amount: mintAmount };
+}
+
+mintCompressedTokens().catch(console.error);
+
+{% endstep %} + +{% step %} +**Success!** + +You've successfully minted compressed tokens. The output shows: + +* **Compressed token supply**: Increased the total supply of your mint +* **Compressed token balance** +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"TokenPool not found" + +```typescript +// Error message: "TokenPool not found. Please create a compressed token +// pool for mint: [ADDRESS] via createTokenPool(). +``` + +The mint does no have a token pool for compression. Ensure you created the mint using `createMint`. + +```typescript +// Create mint with token pool for compression +import { createMint } from '@lightprotocol/compressed-token'; +const { mint } = await createMint(rpc, payer, payer.publicKey, 9); +``` + +
+ +
+ +"TokenPool mint does not match the provided mint" + +The token pool info doesn't correspond to the mint address. Ensure you're fetching the correct pool: + +```typescript +// Get the correct token pool for your mint +const tokenPoolInfo = await getTokenPoolInfos(rpc, mint); +``` + +
+ +
+ +"Amount and toPubkey arrays must have the same length" + +When minting to multiple recipients, ensure arrays are the same size. + +```typescript +// Wrong: Mismatched array lengths +const recipients = [addr1, addr2, addr3]; +const amounts = [100, 200]; // Only 2 amounts for 3 recipients + +// Correct: Same length arrays +const recipients = [addr1, addr2, addr3]; +const amounts = [100, 200, 300]; // 3 amounts for 3 recipients +``` + +
+ +## Advanced Configuration + +
+ +Mint to Multiple Recipients + +```typescript +// Mint different amounts to multiple recipients +const recipients = [ + Keypair.generate().publicKey, + Keypair.generate().publicKey, + Keypair.generate().publicKey, +]; + +const amounts = [ + 1_000_000_000, // 1 token + 2_000_000_000, // 2 tokens + 500_000_000, // 0.5 tokens +]; + +const transactionSignature = await mintTo( + rpc, + payer, + mint, // SPL mint with token pool for compression + recipients, // array of recipients (toPubkey parameter) + payer, // mint authority + amounts, // array of amounts (amount parameter) +); +``` + +
+ +
+ +With Custom Mint Authority + +Mint tokens using a custom mint authority with `approveAndMintTo()`: + +```typescript +import { approveAndMintTo } from '@lightprotocol/compressed-token'; + +// Mint tokens with a separate mint authority +const transactionSignature = await approveAndMintTo( + rpc, + payer, + mint, // SPL mint with token pool for compression + recipient.publicKey, // recipient of minted tokens (toPubkey parameter) + mintAuthority, // mint authority + mintAmount, +); +``` + +
+ +## Next Steps + +Learn how to transfer compressed tokens you just minted. + +{% content-ref url="how-to-transfer-compressed-token.md" %} +[how-to-transfer-compressed-token.md](how-to-transfer-compressed-token.md) +{% endcontent-ref %} + +[^1]: SPL token account that holds SPL tokens corresponding to compressed tokens in circulation. Tokens are locked when compressed and withdrawn when decompressed. Owned by the compressed token program's CPI authority PDA. diff --git a/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-sol.md b/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-sol.md new file mode 100644 index 0000000..d481d20 --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-sol.md @@ -0,0 +1,357 @@ + + +# How to Transfer Compressed SOL + +Compressed SOL transfers work similar to regular SOL transfers with the `transfer()` function . + +The transfer nullifies input compressed accounts of the sender and creates new output accounts for the recipient. + +The transaction to transfer compressed SOL includes: + +1. **Select input accounts** - Find compressed accounts with sufficient lamports of the sender +2. **Generate validity proof** - Create proofs for the input accounts to verify their existence +3. **Execute transfer** - Consume input accounts and create new compressed accounts with updated balances + +{% code title="function-transfer-compressed-sol.ts" %} +```typescript +// Transfer compressed SOL using Light System Program +const transactionSignature = await transfer( + rpc, + payer, // fee payer + lamports, // amount in lamports + owner, // owner of compressed lamports + recipient.publicKey // destination address +); +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +#### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node +``` + +```bash +npm install \ + @lightprotocol/stateless.js \ + @solana/web3.js +``` + +#### Developer Environment + +**RPC Connection** + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("✅ Connection created successfully!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +**Alternative: Using Localnet** + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { Rpc, createRpc } from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +
+{% endstep %} + +{% step %} +### Transfer Compressed SOL + +Use the `transfer()` function to nullify input accounts and create output account with specified lamports. + +{% hint style="info" %} +Replace `` with your actual API key before running. [Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +{% hint style="warning" %} +This guide loads your filesystem wallet at `~/.config/solana/id.json`. If you don't have one yet, visit the [Solana documentation](https://docs.solanalabs.com/cli/wallets/file-system) for details. +{% endhint %} + +{% code title="transfer-compressed-sol.ts" %} +```typescript +import { Keypair, PublicKey } from '@solana/web3.js'; +import { + createRpc, + transfer, + createAccountWithLamports, + bn +} from '@lightprotocol/stateless.js'; +import * as fs from 'fs'; +import * as os from 'os'; + +// 1. Setup funded payer and RPC connection +// 2. Create compressed account with lamports +// 3. Call transfer() to move lamports to recipient - consume input accounts and create new compressed accounts for the recipient +// 4. Verify recipient received compressed lamports with getCompressedAccountsByOwner + +async function transferCompressedSol() { + // Step 1: Setup funded payer and RPC connection + const walletPath = os.homedir() + '/.config/solana/id.json'; + const payer = Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync(walletPath, 'utf8')))); + const recipient = Keypair.generate(); + const rpc = createRpc("https://devnet.helius-rpc.com?api-key="); + + const balance = await rpc.getBalance(payer.publicKey); + if (balance < 0.01 * 1e9) { + throw new Error("Wallet needs at least 0.01 SOL for transaction fees and rent"); + } + + console.log("Sender:", payer.publicKey.toBase58()); + console.log("Recipient:", recipient.publicKey.toBase58()); + + // Step 2: Create compressed account with lamports + const seeds = [Buffer.from("sol-transfer"), payer.publicKey.toBuffer()]; + const programId = new PublicKey("11111111111111111111111111111111"); + + const createTx = await createAccountWithLamports( + rpc, + payer, + seeds, + 2000000, // 2M lamports to compress + programId + ); + + console.log("Compressed account created:", createTx); + + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Step 3: Call transfer() to move lamports to recipient + // Consume input accounts and create new compressed accounts for the recipient + const transferTx = await transfer( + rpc, + payer, // fee payer + 1000000, // 1M lamports to transfer + payer, // owner of compressed lamports + recipient.publicKey // destination + ); + + console.log("Transfer completed:", transferTx); + + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Step 4: Verify recipient received compressed lamports + const recipientAccounts = await rpc.getCompressedAccountsByOwner(recipient.publicKey); + const totalReceived = recipientAccounts.items.reduce( + (sum, account) => sum.add(account.lamports), + bn(0) + ); + + console.log("Recipient compressed lamports:", totalReceived.toString()); + + console.log("\n=== View on Explorer ==="); + console.log(`Account Creation: https://explorer.solana.com/tx/${createTx}?cluster=devnet`); + console.log(`Transfer: https://explorer.solana.com/tx/${transferTx}?cluster=devnet`); + + return { + createTransaction: createTx, + transferTransaction: transferTx, + recipientBalance: totalReceived.toString() + }; +} + +transferCompressedSol().catch(console.error); +``` +{% endcode %} +{% endstep %} + +{% step %} +#### Success! + +You've successfully transferred compressed SOL between addresses. The output shows: + +* **Compressed lamport transfer**: Moved specified amount between wallet addresses +* **Balance preservation**: Total lamports maintained during transfer operation +* **Transaction proof**: Confirmed the transfer operation on-chain +* **Recipient verification**: The lamports now exist at the destination address + +The recipient now owns compressed lamports that can be transferred again, decompressed back to regular SOL, or used in other compressed operations. +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"Insufficient balance for transfer" + +**Not enough compressed lamports available** + +The function accumulates accounts until sufficient balance found: + +```typescript +// Check available compressed lamports first +const accounts = await rpc.getCompressedAccountsByOwner(owner.publicKey); +const totalBalance = accounts.items.reduce( + (sum, account) => sum.add(account.lamports), + bn(0) +); + +if (totalBalance.lt(bn(transferAmount))) { + console.log(`Need ${transferAmount}, have ${totalBalance}`); +} +``` + +
+ +
+ +"Account not found after creation" + +**Indexer delay after account operations** + +Wait for indexer synchronization: + +```typescript +// Increase wait time after account creation +await new Promise(resolve => setTimeout(resolve, 5000)); + +// Verify accounts exist before transfer +const accounts = await rpc.getCompressedAccountsByOwner(owner.publicKey); +if (accounts.items.length === 0) { + throw new Error("No compressed accounts found"); +} +``` + +
+ +## Advanced Configuration + +
+ +Custom Confirmation Options + +Control transaction confirmation behavior: + +```typescript +import { ConfirmOptions } from '@solana/web3.js'; + +const confirmOptions: ConfirmOptions = { + commitment: 'confirmed', + preflightCommitment: 'confirmed', + maxRetries: 3 +}; + +const transferTx = await transfer( + rpc, + payer, + transferAmount, + owner, + recipient.publicKey, + confirmOptions // Optional confirmation settings +); +``` + +
+ +
+ +Batch Transfer Operations + +Execute multiple transfers efficiently: + +```typescript +async function batchTransfer( + rpc: Rpc, + payer: Signer, + owner: Signer, + recipients: Array<{ address: PublicKey; amount: number }> +) { + const results = []; + + for (const { address, amount } of recipients) { + const tx = await transfer(rpc, payer, amount, owner, address); + results.push({ recipient: address, amount, transaction: tx }); + + // Wait between transfers + await new Promise(resolve => setTimeout(resolve, 2000)); + } + + return results; +} +``` + +
+ +
+ +Monitor Account State Changes + +Track compressed account nullification and creation: + +```typescript +async function monitorTransfer(rpc: Rpc, owner: PublicKey, recipient: PublicKey) { + // Check balances before transfer + const ownerAccountsBefore = await rpc.getCompressedAccountsByOwner(owner); + const recipientAccountsBefore = await rpc.getCompressedAccountsByOwner(recipient); + + console.log("Owner accounts before:", ownerAccountsBefore.items.length); + console.log("Recipient accounts before:", recipientAccountsBefore.items.length); + + // After transfer (wait for indexing) + await new Promise(resolve => setTimeout(resolve, 5000)); + + const ownerAccountsAfter = await rpc.getCompressedAccountsByOwner(owner); + const recipientAccountsAfter = await rpc.getCompressedAccountsByOwner(recipient); + + console.log("Owner accounts after:", ownerAccountsAfter.items.length); + console.log("Recipient accounts after:", recipientAccountsAfter.items.length); + + return { + ownerAccountChange: ownerAccountsAfter.items.length - ownerAccountsBefore.items.length, + recipientAccountChange: recipientAccountsAfter.items.length - recipientAccountsBefore.items.length + }; +} +``` + +
+ +## Related JSON RPC Methods + +| Method | Purpose | +| ------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| [getCompressedAccountsByOwner](../../resources/json-rpc-methods/getcompressedaccountsbyowner.md) | Discover input accounts for transfer | +| [getValidityProof](../../resources/json-rpc-methods/getvalidityproof.md) | Generate proofs for input account hashes | +| [getCompressedBalance](../../resources/json-rpc-methods/getcompressedbalance.md) | Check total compressed balance | diff --git a/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-token.md b/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-token.md new file mode 100644 index 0000000..ffa077f --- /dev/null +++ b/compression-docs/compressed-tokens/cookbook/how-to-transfer-compressed-token.md @@ -0,0 +1,386 @@ + + +# How to Transfer Compressed Token + +*** + +The `transfer()` function moves compressed tokens between accounts. Unlike regular SPL transfers that update existing account balances, compressed transfers consume input accounts from the sender and create new output accounts for sender and recipient with updated balances. + +Before we can transfer compressed tokens, we need: + +* SPL mint registered with the compressed token program via `createMint()` or `createTokenPool()` +* Source compressed token account with sufficient balance for the transfer amount. Regular SPL token accounts can be compressed in the same transaction with `compress_or_decompress_amount`, if needed. + +{% code title="function-transfer-compressed-tokens.ts" %} +```typescript + import { transfer } from '@lightprotocol/compressed-token'; + import { PublicKey } from '@solana/web3.js'; + + // Use existing mint with token pool for compression to transfer compressed tokens + const mint = new PublicKey("MINT_ADDRESS"); + const recipient = new PublicKey("RECIPIENT_WALLET_ADDRESS"); + const amount = 1_000_000_000; // 1 token (9 decimals) + + // Transfer compressed tokens + const transactionSignature = await transfer( + rpc, + payer, + mint, // SPL mint with token pool for compression + amount, + payer, + recipient, // destination address (toAddress parameter) +``` +{% endcode %} + +## Full Code Example + +{% stepper %} +{% step %} +### Prerequisites + +Make sure you have dependencies and developer environment set up! + +
+ +Prerequisites & Setup + +### Dependencies + +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +#### Alternatives: + +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` + +### Developer Environment + +By default, this guide uses Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Follow these steps to create an RPC Connection. Replace `` with your API key before running. + +{% hint style="info" %} +Get your API Key [here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+{% endstep %} + +{% step %} +### Transferring Compressed Tokens + +Run this script to transfer compressed tokens to a recipient! + +
// 1. Setup funded payer and connect to local validator
+// 2. Create SPL mint and token pool for compression with initial tokens
+// 3. Call transfer() with mint, amount, owner, recipient
+// 4. Verify transferred tokens via getCompressedTokenAccountsByOwner
+
+import { Keypair, PublicKey } from '@solana/web3.js';
+import { createRpc } from '@lightprotocol/stateless.js';
+import { createMint, mintTo, transfer } from '@lightprotocol/compressed-token';
+
+async function transferCompressedTokens() {
+    // Step 1: Setup funded payer and connect to local validator
+    const rpc = createRpc(); // defaults to localhost:8899
+    const payer = Keypair.generate();
+    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
+    await rpc.confirmTransaction(airdropSignature);
+
+    // Step 2a: Create SPL mint with token pool for compression
+    const { mint, transactionSignature: mintCreateTx } = await createMint(
+        rpc,
+        payer,
+        payer.publicKey, // mint authority
+        9
+    );
+
+    console.log("SPL mint with token pool for compression created");
+    console.log("Mint address:", mint.toBase58());
+    console.log("Create mint transaction:", mintCreateTx);
+
+    // Step 2b: Create token owner and mint initial tokens
+    const tokenOwner = Keypair.generate();
+    const initialMintAmount = 1_000_000_000; // 1 token with 9 decimals
+
+    const mintToTx = await mintTo(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        tokenOwner.publicKey, // recipient
+        payer, // mint authority
+        initialMintAmount
+    );
+
+    console.log("\nCompressed Tokens minted:", initialMintAmount / 1_000_000_000, "tokens");
+    console.log("Mint tokens transaction:", mintToTx);
+
+    // Generate recipient address and define transfer amount
+    const recipient = Keypair.generate();
+    const transferAmount = 500_000_000; // 0.5 tokens
+
+    // Step 3: Call transfer() with mint, amount, owner, recipient
+    const transferTx = await transfer(
+        rpc,
+        payer,
+        mint, // SPL mint with token pool for compression
+        transferAmount,
+        tokenOwner, // owner keypair
+        recipient.publicKey // recipient address
+    );
+
+    console.log("\nCompressed tokens transferred!");
+    console.log("From:", tokenOwner.publicKey.toBase58());
+    console.log("To:", recipient.publicKey.toBase58());
+    console.log("Amount transferred:", transferAmount / 1_000_000_000, "tokens");
+    console.log("Transfer transaction:", transferTx);
+
+    // Step 4: Verify transferred tokens via getCompressedTokenAccountsByOwner
+    const recipientAccounts = await rpc.getCompressedTokenAccountsByOwner(
+        recipient.publicKey,
+        { mint }
+    );
+
+    // Check recipient received the tokens
+    if (recipientAccounts.items.length > 0) {
+        const receivedBalance = recipientAccounts.items[0].parsed.amount;
+    }
+
+    return { 
+        transferTransaction: transferTx, 
+        recipient: recipient.publicKey, 
+        amount: transferAmount 
+    };
+}
+
+transferCompressedTokens().catch(console.error);
+
+{% endstep %} + +{% step %} +#### Success! + +You've successfully created and transferred compressed tokens. The output shows: + +* **Transfer confirmation**: Tokens moved from sender to recipient +* **Amount verification**: Exact tokens transferred with decimal precision +* **Balance verification**: Both sender and recipient balances confirmed +{% endstep %} +{% endstepper %} + +## Troubleshooting + +
+ +"Insufficient balance for transfer" + +The sender doesn't have enough compressed tokens for the requested transfer amount. + +```typescript +// Check current balance first +const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner( + owner.publicKey, + { mint } +); + +if (tokenAccounts.items.length === 0) { + throw new Error("No compressed token accounts found"); +} + +// Calculate total balance across all accounts +const totalBalance = tokenAccounts.items.reduce( + (sum, account) => sum.add(account.parsed.amount), + new BN(0) +); + +console.log("Available balance:", totalBalance.toString()); + +// Ensure transfer amount doesn't exceed balance +if (new BN(transferAmount).gt(totalBalance)) { + throw new Error(`Transfer amount ${transferAmount} exceeds balance ${totalBalance.toString()}`); +} +``` + +
+ +
+ +"Account limit exceeded" + +The transfer requires more than 4 compressed accounts, which exceeds the transaction limit. + +```typescript +// Error message: "Account limit exceeded: max X (4 accounts) per transaction. +// Total balance: Y (Z accounts). Consider multiple transfers to spend full balance." + +// Split into multiple smaller transfers +const maxTransferPerTx = 1_000_000_000; // Adjust based on your account sizes + +if (transferAmount > maxTransferPerTx) { + console.log("Large transfer detected, splitting into multiple transactions..."); + + let remainingAmount = transferAmount; + while (remainingAmount > 0) { + const currentTransfer = Math.min(remainingAmount, maxTransferPerTx); + + await transfer( + rpc, + payer, + mint, + currentTransfer, + owner, + recipient + ); + + remainingAmount -= currentTransfer; + console.log(`Transferred ${currentTransfer}, remaining: ${remainingAmount}`); + } +} +``` + +
+ +## Advanced Configuration + +
+ +Transfer to Multiple Recipients + +Transfer to multiple recipients in separate transactions: + +```typescript +const recipients = [ + Keypair.generate().publicKey, + Keypair.generate().publicKey, + Keypair.generate().publicKey, +]; + +const amounts = [ + 100_000_000, // 0.1 tokens + 200_000_000, // 0.2 tokens + 150_000_000, // 0.15 tokens +]; + +for (let i = 0; i < recipients.length; i++) { + const transactionSignature = await transfer( + rpc, + payer, + mint, + amounts[i], + owner, + recipients[i], + ); + + console.log(`Transfer ${i + 1} completed:`, transactionSignature); +} +``` + +
+ +
+ +Transfer with Delegate Authority + +Transfer tokens using delegate authority: + +```typescript +import { approve, transferDelegated } from '@lightprotocol/compressed-token'; + +// 1. Owner approves delegate +await approve( + rpc, + payer, + mint, + amount, + owner, // Signer + delegate.publicKey, // PublicKey +); + +// 2. Delegate transfers tokens +await transferDelegated( + rpc, + payer, + mint, + transferAmount, + delegate, // Signer - named "owner" in SDK + recipient, +); +``` + +
+ +## Next Steps + +Learn how compress and decompress SPL Tokens. + +{% content-ref url="how-to-compress-and-decompress-spl-tokens.md" %} +[how-to-compress-and-decompress-spl-tokens.md](how-to-compress-and-decompress-spl-tokens.md) +{% endcontent-ref %} diff --git a/compression-docs/compressed-tokens/overview.md b/compression-docs/compressed-tokens/overview.md new file mode 100644 index 0000000..ae7f836 --- /dev/null +++ b/compression-docs/compressed-tokens/overview.md @@ -0,0 +1,133 @@ + +# Overview + +*** + +Compressed tokens provide full SPL token functionality without per-account rent cost. + +
CreationRegular SPL TokenCompressed TokenCost Reduction
100 Token Accounts~ 0.2 SOL~ 0.00004 SOL5000x
+ +Compressed token accounts store information about an individual's ownership of a specific token (mint), but don't require an Associated Token Account (ATA) per token holder, unlike regular token accounts. + +For example, this simplifies [token distribution](advanced-guides/create-an-airdrop.md), since you don't need to allocate a token account per recipient. Once distributed, users can decompress and compress SPL tokens as needed and atomically in one transaction. + +### Compressed Tokens at a Glance + +

Rent-free tokens

Create token accounts without upfront rent exempt balance.

SPL Compatibility

Compatible with SPL tokens and Solana programs.

Wallet Support

Supported by leading wallets including Phantom and Backpack.
+ +## Start building + +Developing with compressed tokens works similar SPL tokens and involves minimal setup: + +1. Install dependencies + +{% tabs %} +{% tab title="npm" %} +```bash +npm install --save-dev typescript tsx @types/node && \ +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} + +{% tab title="yarn" %} +```bash +yarn add --dev typescript tsx @types/node && \ +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} + +{% tab title="pnpm" %} +```bash +pnpm add --save-dev typescript tsx @types/node && \ +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} +{% endtabs %} + +2. Set up your developer environment + +
+ +Setup Developer Environment + +By default, all guides use Localnet. + +```bash +# Install the development CLI +npm install @lightprotocol/zk-compression-cli +``` + +```bash +# Start a local test validator +light test-validator + +## ensure you have the Solana CLI accessible in your system PATH +``` + +```typescript +// createRpc() defaults to local test validator endpoints +import { + Rpc, + createRpc, +} from "@lightprotocol/stateless.js"; + +const connection: Rpc = createRpc(); + +async function main() { + let slot = await connection.getSlot(); + console.log(slot); + + let health = await connection.getIndexerHealth(slot); + console.log(health); + // "Ok" +} + +main(); +``` + +**Alternative: Using Devnet** + +Replace `` with your actual API key. [Get your API key here](https://www.helius.dev/zk-compression), if you don't have one yet. + +```typescript +import { createRpc } from "@lightprotocol/stateless.js"; + +// Helius exposes Solana and Photon RPC endpoints through a single URL +const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; +const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT); + +console.log("Connection created!"); +console.log("RPC Endpoint:", RPC_ENDPOINT); +``` + +
+ +3. Get started with our [cookbook](overview.md#cookbook) or [advanced guides](overview.md#advanced-guides) for implementations + +### Cookbook + +
GuideDescription
How to Create and Register a Mint Account for CompressionCreate new SPL mint with token pool for compression
How to Create Compressed Token AccountsDifference to regular token accounts
How to Mint Compressed TokensCreate new compressed tokens to existing mint
How to Transfer Compressed TokensMove compressed tokens between compressed accounts
How to Decompress and Compress TokensConvert SPL tokens between regular and compressed format
How to Compress complete SPL Token AccountsCompress complete SPL token accounts and reclaim rent afterwards
How to Merge Compressed AccountsConsolidate multiple compressed accounts of the same mint into a single compressed account
How to Create Token Pools for Mint AccountsCreate token pool for compression for existing SPL mints
How to Approve and Revoke Delegate AuthorityApprove or revoke delegates for compressed token accounts
+ +### Advanced Guides + +
GuideDescription
Create an AirdropCreate an airdrop with or without code
Use Token-2022 with compressed tokensCreate and transfer compressed tokens with Token-2022 extensions
Add Wallet Support for Compressed TokensImplement compressed token support in your wallet application
+ +*** + +## Next Steps + +{% content-ref url="cookbook/how-to-create-and-register-a-mint-account-for-compression.md" %} +[how-to-create-and-register-a-mint-account-for-compression.md](cookbook/how-to-create-and-register-a-mint-account-for-compression.md) +{% endcontent-ref %} diff --git a/compression-docs/developers/add-compressed-token-support-to-your-wallet.md b/compression-docs/developers/add-compressed-token-support-to-your-wallet.md deleted file mode 100644 index 0decf2a..0000000 --- a/compression-docs/developers/add-compressed-token-support-to-your-wallet.md +++ /dev/null @@ -1,410 +0,0 @@ -# Add Compressed Token Support to Your Wallet - -The following page describes how to add **compressed token** support to your wallet application. - -{% hint style="info" %} -_Key benefits of compressed tokens:_ - -* Up to **5000x** cheaper than uncompressed accounts -* Compatible with existing programs via atomic compression and decompression between SPL <> Compressed tokens -{% endhint %} - -## Integration Steps - -### 1. Install the SDK - -
Package ManagerCommand
npm
npm install --save \
-    @lightprotocol/stateless.js \
-    @lightprotocol/compressed-token \
-    @solana/web3.js
-
Yarn
yarn add \
-    @lightprotocol/stateless.js \
-    @lightprotocol/compressed-token \
-    @solana/web3.js
-
- -### 2. **Create an RPC Connection** - -```tsx -import { - Rpc, - createRpc, -} from "@lightprotocol/stateless.js"; - -const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; -const connection: Rpc = createRpc(RPC_ENDPOINT) -``` - -
- -Using Localnet - -```bash -# Install the development CLI -npm install @lightprotocol/zk-compression-cli -``` - -```bash -# Start a local test validator -light test-validator -``` - -```tsx -import { - Rpc, - createRpc, -} from "@lightprotocol/stateless.js"; - -const connection: Rpc = createRpc(); - -async function main() { - let slot = await connection.getSlot(); - console.log(slot); - - let health = await connection.getIndexerHealth(slot); - console.log(health); - // "Ok" -} - -main(); -``` - -
- -### 3. Display Compressed Token Balances - -```typescript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey } from '@solana/web3.js'; - -const RPC_ENDPOINT = 'https://devnet.helius-rpc.com?api-key='; -const connection: Rpc = createRpc(RPC_ENDPOINT); -const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG'); - -(async () => { - // Returns balance for owner per mint - // Can optionally apply filter: {mint, limit, cursor} - const balances = - await connection.getCompressedTokenBalancesByOwnerV2(publicKey); - console.log(balances); -})(); -``` - -### 4. Get Compression Signature History By Owner - -```typescript -import { Rpc, createRpc } from '@lightprotocol/stateless.js'; -import { PublicKey } from '@solana/web3.js'; - -const RPC_ENDPOINT = 'https://devnet.helius-rpc.com?api-key='; -const connection: Rpc = createRpc(RPC_ENDPOINT); -const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG'); - -(async () => { - // 1. Fetch signatures for the user - // - // Returns confirmed signatures for compression transactions involving the - // specified account owner - const signatures = - await connection.getCompressionSignaturesForOwner(publicKey); - console.log(signatures); - - // 2. Fetch transactions with compression info - // - // Returns pre- and post-compressed token balances grouped by owner - const parsedTransaction = - await connection.getTransactionWithCompressionInfo(signatures[0].signature) - console.log(parsedTransaction) -})(); -``` - -**Full JSON RPC API:** - -{% content-ref url="json-rpc-methods/" %} -[json-rpc-methods](json-rpc-methods/) -{% endcontent-ref %} - -### 5. Send Compressed Tokens - -
- -Setup Test Mint - -```typescript -import { Keypair } from "@solana/web3.js"; -import { Rpc, confirmTx, createRpc } from '@lightprotocol/stateless.js'; -import { createMint, mintTo } from '@lightprotocol/compressed-token'; - -// Set these values... -const RPC_ENDPOINT = ''; -const connection: Rpc = createRpc(RPC_ENDPOINT); -const PAYER = Keypair.generate(); -const PUBLIC_KEY = PAYER.publicKey; -const MINT_KEYPAIR = Keypair.generate(); -const RECIPIENT_PUBLIC_KEY = Keypair.generate().publicKey.toBase58(); - -/// Airdrop tokens to PAYER beforehand. -(async() => { - /// Create and register compressed-token mint - const { mint, transactionSignature } = await createMint( - connection, - PAYER, - PAYER.publicKey, - 9, - PAYER, - ); - console.log(`create-mint success! txId: ${transactionSignature}`); - - /// Mint compressed tokens - const mintToTxId = await mintTo( - connection, - PAYER, - mint, - PAYER.publicKey, - PAYER, - 1e9, - ); - - console.log(`mint-to success! txId: ${mintToTxId}`); -})(); -``` - -
- -```typescript -import { - Rpc, - createRpc, - bn, - dedupeSigner, - sendAndConfirmTx, - buildAndSignTx, -} from "@lightprotocol/stateless.js"; -import { - CompressedTokenProgram, - selectMinCompressedTokenAccountsForTransfer, -} from "@lightprotocol/compressed-token"; -import { ComputeBudgetProgram, Keypair } from "@solana/web3.js"; - -// 0. Set these values -const RPC_ENDPOINT = "https://mainnet.helius-rpc.com?api-key="; -const mint = ; -const payer = ; -const owner = payer; - -const recipient = Keypair.generate(); -const amount = bn(1e8); - -const connection: Rpc = createRpc(RPC_ENDPOINT); - -(async () => { - // 1. Fetch latest token account state - const compressedTokenAccounts = - await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { - mint, - }); - - // 2. Select accounts to transfer from based on the transfer amount - const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( - compressedTokenAccounts.items, - amount - ); - - // 3. Fetch recent validity proof - const proof = await connection.getValidityProof( - inputAccounts.map((account) => account.compressedAccount.hash) - ); - - // 4. Create transfer instruction - const ix = await CompressedTokenProgram.transfer({ - payer: payer.publicKey, - inputCompressedTokenAccounts: inputAccounts, - toAddress: recipient.publicKey, - amount, - recentInputStateRootIndices: proof.rootIndices, - recentValidityProof: proof.compressedProof, - }); - - // 8. Sign, send, and confirm... - const { blockhash } = await connection.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - const signedTx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], - payer, - blockhash, - additionalSigners - ); - return await sendAndConfirmTx(connection, signedTx); -})(); - -``` - -### Advanced Integration - -
- -Decompress SPL Tokens - -
import {
-  bn,
-  buildAndSignTx,
-  sendAndConfirmTx,
-  dedupeSigner,
-  Rpc,
-  createRpc,
-} from "@lightprotocol/stateless.js";
-import { ComputeBudgetProgram } from "@solana/web3.js";
-import {
-  CompressedTokenProgram,
-  getTokenPoolInfos,
-  selectMinCompressedTokenAccountsForTransfer,
-  selectTokenPoolInfosForDecompression,
-} from "@lightprotocol/compressed-token";
-
-// 0. Set these values.
-const connection: Rpc = createRpc("https://mainnet.helius-rpc.com?api-key=<api_key>";);
-const payer = PAYER_KEYPAIR;
-const owner = PAYER_KEYPAIR;
-const mint = MINT_ADDRESS;
-const amount = 1e5;
-
-(async () => {
-  // 1. Fetch compressed token accounts
-  const compressedTokenAccounts =
-    await connection.getCompressedTokenAccountsByOwner(owner.publicKey, {
-      mint,
-    });
-
-  // 2. Select
-  const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
-    compressedTokenAccounts.items,
-    bn(amount)
-  );
-
-  // 3. Fetch validity proof
-  const proof = await connection.getValidityProof(
-    inputAccounts.map((account) => account.compressedAccount.hash)
-  );
-
-  // 4. Fetch & Select tokenPoolInfos
-  const tokenPoolInfos = await getTokenPoolInfos(connection, mint);
-  const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression(
-    tokenPoolInfos,
-    amount
-  );
-
-  // 5. Build instruction
-  const ix = await CompressedTokenProgram.decompress({
-    payer: payer.publicKey,
-    inputCompressedTokenAccounts: inputAccounts,
-    toAddress: owner.publicKey,
-    amount,
-    tokenPoolInfos: selectedTokenPoolInfos,
-    recentInputStateRootIndices: proof.rootIndices,
-    recentValidityProof: proof.compressedProof,
-  });
-  
-  
-  // 6. Sign, send, and confirm.
-  // Example with keypair:
-  const { blockhash } = await connection.getLatestBlockhash();
-  const additionalSigners = dedupeSigner(payer, [owner]);
-  const signedTx = buildAndSignTx(
-    [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
-    payer,
-    blockhash,
-    additionalSigners
-  );
-
-  return await sendAndConfirmTx(connection, signedTx);
-})();
-
- -
- -
- -Compress SPL Tokens - -```typescript -import { - buildAndSignTx, - sendAndConfirmTx, - Rpc, - createRpc, - selectStateTreeInfo, -} from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram } from "@solana/web3.js"; -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectTokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; - -// 0. Set these values. -const connection: Rpc = createRpc( - "https://mainnet.helius-rpc.com?api-key=" -); - const payer = ; - const mint = ; -const amount = 1e5; - -(async () => { - // 1. Get user ATA - const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - payer, - mint, - payer.publicKey - ); - - // 2. Fetch & Select treeInfos - const treeInfos = await connection.getStateTreeInfos(); - const treeInfo = selectStateTreeInfo(treeInfos); - - // 3. Fetch & Select tokenPoolInfo - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); - - // 4. Build compress instruction - const compressInstruction = await CompressedTokenProgram.compress({ - payer: payer.publicKey, - owner: payer.publicKey, - source: sourceTokenAccount.address, - toAddress: payer.publicKey, // to self. - amount, - mint, - outputStateTreeInfo: treeInfo, - tokenPoolInfo, - }); - - // 5. Sign and send tx - // Example with Keypair: - const { blockhash } = await connection.getLatestBlockhash(); - const tx = buildAndSignTx( - [ - ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), - compressInstruction, - ], - payer, - blockhash, - [payer] - ); - await sendAndConfirmTx(connection, tx); -})(); -``` - -
- - - -## Best Practices - -* **Clear UI Indicators —** Provide clear visual distinctions between compressed and uncompressed SPL tokens -* **Transaction History** — Provide detailed transaction histories for compressed tokens -* **Decompression and Compression** — Provide a clear path for users to convert between compressed and uncompressed tokens when needed - -## Support - -For additional support or questions, please refer to our [documentation](https://www.zkcompression.com) or contact [Swen](https://t.me/swen_light) or [Mert](https://t.me/mert_helius) on Telegram or via [email](mailto:friends@lightprotocol.com). diff --git a/compression-docs/developers/addresses-and-urls.md b/compression-docs/developers/addresses-and-urls.md deleted file mode 100644 index 284e248..0000000 --- a/compression-docs/developers/addresses-and-urls.md +++ /dev/null @@ -1,23 +0,0 @@ -# Addresses and URLs - -### Mainnet URLs - -
Network Address (RPC)https://mainnet.helius-rpc.com?api-key=<api_key>
Photon RPC APIhttps://mainnet.helius-rpc.com?api-key=<api_key>
- -### Devnet URLs - -
Network Address (RPC)https://devnet.helius-rpc.com?api-key=<api_key>
Photon RPC APIhttps://devnet.helius-rpc.com?api-key=<api_key>
- -### Program IDs & Accounts - -
Light System ProgramSySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7
Compressed Token ProgramcTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m
Account Compression Programcompr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq
Public State Tree #1smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT
Public Nullifier Queue #1nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148
Public Address Tree #1amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2
Public Address Queue #1aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F
Token Escrow Owner PDAGXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy
- -### Lookup Tables - -{% hint style="info" %} -[Lookup tables](https://solana.com/docs/advanced/lookup-tables) reduce your transaction size. We provide pre-initialized lookup tables that cover the Light's program IDs and accounts: -{% endhint %} - -
Lookup Table #1 (Mainnet)9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ
Lookup Table #1 (Devnet)qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V
- -We provide a helper function [here](typescript-client.md#creating-lookup-tables) if you need to extend a custom lookup table. diff --git a/compression-docs/developers/creating-airdrops-with-compressed-tokens.md b/compression-docs/developers/creating-airdrops-with-compressed-tokens.md deleted file mode 100644 index bd375d1..0000000 --- a/compression-docs/developers/creating-airdrops-with-compressed-tokens.md +++ /dev/null @@ -1,845 +0,0 @@ -# Creating Airdrops with Compressed Tokens - -**ZK Compression is the most efficient way to distribute your SPL tokens.** - -**By the end of this guide, you'll have built a fully functioning, programmatic airdrop.** - -{% hint style="info" %} -_Key benefits of compressed tokens:_ - -* Up to 5000x cheaper than regular tokens -* Supported by leading Solana wallets, including Phantom and Backpack -* Compatible with existing programs via atomic compression and decompression between SPL <> Compressed tokens -{% endhint %} - -## Airdropping SPL Tokens - -
- -No-code Solution - -[Airship](https://airship.helius.dev/) by Helius Labs is an excellent no-code airdrop tool. Airship uses compressed tokens under the hood.\ -\ -For programmatic airdrops with more control, keep reading. :point\_down: - -
- -The high-level overview is this: - -1. Mint and send the to-be-airdropped SPL tokens to a wallet you control. -2. Create batches of instructions based on a list of recipients and amounts. -3. Build transactions from these instruction batches, then sign, send, and confirm them. - -{% hint style="info" %} -The code snippets work! You can copy + paste them into your IDE. -{% endhint %} - -### 1. Install the SDK - -{% tabs %} -{% tab title="npm" %} -```bash -npm install --save \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js \ - @solana/spl-token \ - bs58 \ - dotenv -``` -{% endtab %} - -{% tab title="yarn" %} -```bash -yarn add \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js \ - @solana/spl-token \ - bs58 \ - dotenv -``` -{% endtab %} - -{% tab title="pnpm" %} -```bash -pnpm add \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js \ - @solana/spl-token \ - bs58 \ - dotenv -``` -{% endtab %} -{% endtabs %} - -### 2. Mint SPL tokens to yourself - -{% tabs %} -{% tab title="Default" %} -```typescript -import { Keypair } from '@solana/web3.js'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { - createMint, - getOrCreateAssociatedTokenAccount, - mintTo, -} from "@solana/spl-token"; -import { createTokenPool } from '@lightprotocol/compressed-token'; -import bs58 from "bs58"; -import dotenv from "dotenv"; -dotenv.config(); - - -// Set these values in your .env file -const RPC_ENDPOINT = process.env.RPC_ENDPOINT!; -const PAYER = Keypair.fromSecretKey( - bs58.decode(process.env.PAYER_KEYPAIR!) -); -// Create Rpc endpoint -const connection = createRpc(RPC_ENDPOINT); - - -(async() => { - - /// Create an SPL mint - const mint = await createMint( - connection, - PAYER, - PAYER.publicKey, - null, - 9 - ); - console.log(`create-mint success! address: ${mint}`); - - /// Register mint for compression - const poolTxId = await createTokenPool(connection, PAYER, mint); - console.log(`createTokenPool success: ${poolTxId}`); - - - /// Create an associated SPL token account for the sender (PAYER) - const ata = await getOrCreateAssociatedTokenAccount( - connection, - PAYER, - mint, - PAYER.publicKey - ); - console.log(`ATA: ${ata.address.toBase58()}`); - - - /// Mint SPL tokens to the sender - const mintToTxId = await mintTo( - connection, - PAYER, - mint, - ata.address, - PAYER.publicKey, - 1e9 * 1e9 // 1b * decimals - ); - console.log(`mint-to success! txId: ${mintToTxId}`); -})(); -``` -{% endtab %} - -{% tab title="With `createMint` helper" %} -If you create a new mint, you can use the `createMint` helper from `@lightprotocol/compressed-token`. It creates the mint **and** registers it for compression. - -```typescript -import { Keypair } from '@solana/web3.js'; -import { createRpc } from '@lightprotocol/stateless.js'; -import { createMint } from '@lightprotocol/compressed-token'; -import { - getOrCreateAssociatedTokenAccount, - mintTo, -} from "@solana/spl-token"; -import bs58 from "bs58"; -import dotenv from "dotenv"; -dotenv.config(); - -// Set these values in your .env file -const RPC_ENDPOINT = process.env.RPC_ENDPOINT!; -const PAYER = Keypair.fromSecretKey( - bs58.decode(process.env.PAYER_KEYPAIR!) -); -// Create Rpc endpoint -const connection = createRpc(RPC_ENDPOINT); - - -(async() => { - - /// Create an SPL mint + register it for compression. - const { mint, transactionSignature } = await createMint( - connection, - PAYER, - PAYER.publicKey, - 9, - ); - console.log(`create-mint success! txId: ${transactionSignature}`); - - - /// Create an associated SPL token account for the sender (PAYER) - const ata = await getOrCreateAssociatedTokenAccount( - connection, - PAYER, - mint, - PAYER.publicKey - ); - console.log(`ATA: ${ata.address.toBase58()}`); - - - - /// Mint SPL tokens to the sender - const mintToTxId = await mintTo( - connection, - PAYER, - mint, - ata.address, - PAYER.publicKey, - 1e9 * 1e9 // 1b * decimals - ); - console.log(`mint-to success! txId: ${mintToTxId}`); -})(); -``` -{% endtab %} -{% endtabs %} - -You now have a regular SPL token account owned by `PAYER` that holds all minted tokens. - -### 3. Distribute the tokens - -Next, you want to distribute the SPL tokens from your distributor to all recipients. - -{% hint style="info" %} -Ensure you have the latest `@lightprotocol/stateless.js` and `@lightprotocol/compressed-token` versions `≥ 0.21.0`! -{% endhint %} - -#### A. Simple version - -
- -Simple airdrop script - -```typescript -import { Keypair, PublicKey, ComputeBudgetProgram } from "@solana/web3.js"; -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectTokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { - bn, - buildAndSignTx, - calculateComputeUnitPrice, - createRpc, - dedupeSigner, - Rpc, - selectStateTreeInfo, - sendAndConfirmTx, -} from "@lightprotocol/stateless.js"; -import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; -import dotenv from "dotenv"; -import bs58 from "bs58"; -dotenv.config(); - -// Set these values in your .env file -const RPC_ENDPOINT = process.env.RPC_ENDPOINT; -const MINT_ADDRESS = new PublicKey(process.env.MINT_ADDRESS!); -const PAYER_KEYPAIR = Keypair.fromSecretKey( - bs58.decode(process.env.PAYER_KEYPAIR!) -); - -(async () => { - const connection: Rpc = createRpc(RPC_ENDPOINT); - const mintAddress = MINT_ADDRESS; - const payer = PAYER_KEYPAIR; - const owner = payer; - - /// Select a new tree for each transaction. - const activeStateTrees = await connection.getStateTreeInfos(); - const treeInfo = selectStateTreeInfo(activeStateTrees); - - /// Select a tokenpool info - const infos = await getTokenPoolInfos(connection, mintAddress); - const info = selectTokenPoolInfo(infos); - - const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - payer, - mintAddress, - payer.publicKey - ); - - // Airdrop to example recipient - // 1 recipient = 120_000 CU - // 5 recipients = 170_000 CU - const airDropAddresses = ["GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7"].map( - (address) => new PublicKey(address) - ); - - const amount = bn(111); - - const instructions = []; - instructions.push( - ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 }), - ComputeBudgetProgram.setComputeUnitPrice({ - // Replace this with a dynamic priority_fee based on network conditions. - microLamports: calculateComputeUnitPrice(20_000, 120_000), - }) - ); - - const compressInstruction = await CompressedTokenProgram.compress({ - payer: payer.publicKey, - owner: owner.publicKey, - source: sourceTokenAccount.address, - toAddress: airDropAddresses, - amount: airDropAddresses.map(() => amount), - mint: mintAddress, - tokenPoolInfo: info, - outputStateTreeInfo: treeInfo, - }); - instructions.push(compressInstruction); - - const additionalSigners = dedupeSigner(payer, [owner]); - const { blockhash } = await connection.getLatestBlockhash(); - - const tx = buildAndSignTx(instructions, payer, blockhash, additionalSigners); - - const txId = await sendAndConfirmTx(connection, tx); - console.log(`txId: ${txId}`); -})(); - -``` - -
- -#### B. Optimized For large-scale airdrops - -First, create a helper that takes recipients and amounts and returns batches of instructions: - -
- -1. create-instructions.ts - -```typescript -// create-instructions.ts -import { - CompressedTokenProgram, - TokenPoolInfo, -} from "@lightprotocol/compressed-token"; -import { - bn, - selectStateTreeInfo, - StateTreeInfo, -} from "@lightprotocol/stateless.js"; -import { - ComputeBudgetProgram, - TransactionInstruction, - PublicKey, -} from "@solana/web3.js"; - -interface CreateAirdropInstructionsParams { - amount: number | bigint; - recipients: PublicKey[]; - payer: PublicKey; - sourceTokenAccount: PublicKey; - mint: PublicKey; - stateTreeInfos: StateTreeInfo[]; - tokenPoolInfos: TokenPoolInfo[]; - maxRecipientsPerInstruction?: number; - maxInstructionsPerTransaction?: number; - computeUnitLimit?: number; - computeUnitPrice?: number | undefined; -} - -export type InstructionBatch = TransactionInstruction[]; - -export async function createAirdropInstructions({ - amount, - recipients, - payer, - sourceTokenAccount, - mint, - stateTreeInfos, - tokenPoolInfos, - maxRecipientsPerInstruction = 5, - maxInstructionsPerTransaction = 3, - computeUnitLimit = 500_000, - computeUnitPrice = undefined, -}: CreateAirdropInstructionsParams): Promise { - const instructionBatches: InstructionBatch[] = []; - const amountBn = bn(amount.toString()); - - // Process recipients in chunks - for ( - let i = 0; - i < recipients.length; - i += maxRecipientsPerInstruction * maxInstructionsPerTransaction - ) { - const instructions: TransactionInstruction[] = []; - - instructions.push( - ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }) - ); - if (computeUnitPrice) { - instructions.push( - ComputeBudgetProgram.setComputeUnitPrice({ - microLamports: computeUnitPrice, - }) - ); - } - - const treeInfo = selectStateTreeInfo(stateTreeInfos); - const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos); - - for (let j = 0; j < maxInstructionsPerTransaction; j++) { - const startIdx = i + j * maxRecipientsPerInstruction; - const recipientBatch = recipients.slice( - startIdx, - startIdx + maxRecipientsPerInstruction - ); - - if (recipientBatch.length === 0) break; - - const compressIx = await CompressedTokenProgram.compress({ - payer, - owner: payer, - source: sourceTokenAccount, - toAddress: recipientBatch, - amount: recipientBatch.map(() => amountBn), - mint, - tokenPoolInfo, - outputStateTreeInfo: treeInfo, - }); - - instructions.push(compressIx); - } - - if ( - (computeUnitPrice && instructions.length > 2) || - (!computeUnitPrice && instructions.length > 1) - ) { - instructionBatches.push(instructions); - } - } - - return instructionBatches; -} -``` - - - -
- -Now, you can create the logic that signs and sends transactions in batches. For this, first add a helper method that refreshes Solana blockhashes in the background: - -
- -2. update-blockhash.ts - -```typescript -// update-blockhash.ts -import { Rpc } from "@lightprotocol/stateless.js"; - -export let currentBlockhash: string; - -export async function updateBlockhash( - connection: Rpc, - signal: AbortSignal -): Promise { - try { - const { blockhash } = await connection.getLatestBlockhash(); - currentBlockhash = blockhash; - console.log(`Initial blockhash: ${currentBlockhash}`); - } catch (error) { - console.error("Failed to fetch initial blockhash:", error); - return; - } - - // Update blockhash in the background - (function updateInBackground() { - if (signal.aborted) return; - const timeoutId = setTimeout(async () => { - if (signal.aborted) return; - try { - const { blockhash } = await connection.getLatestBlockhash(); - currentBlockhash = blockhash; - console.log(`Updated blockhash: ${currentBlockhash}`); - } catch (error) { - console.error("Failed to update blockhash:", error); - } - updateInBackground(); - }, 30_000); - - signal.addEventListener("abort", () => clearTimeout(timeoutId)); - })(); -} -``` - - - -
- -Then, add the helper that signs and sends the transactions using recent blockhashes. - -
- -3. sign-and-send.ts - -```typescript -// sign-and-send.ts -import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js"; -import { - Keypair, - PublicKey, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; -import { InstructionBatch } from "./create-instructions"; -import { currentBlockhash, updateBlockhash } from "./update-blockhash"; -import bs58 from "bs58"; - -export enum BatchResultType { - Success = "success", - Error = "error", -} - -export type BatchResult = - | { type: BatchResultType.Success; index: number; signature: string } - | { type: BatchResultType.Error; index: number; error: string }; - -export async function* signAndSendAirdropBatches( - batches: InstructionBatch[], - payer: Keypair, - connection: Rpc, - maxRetries = 3 -): AsyncGenerator { - const abortController = new AbortController(); - const { signal } = abortController; - - await updateBlockhash(connection, signal); - - const statusMap = new Array(batches.length).fill(0); // Initialize all as pending (0) - - // Use zk-compression LUT for your network - // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables - const lookupTableAddress = new PublicKey( - "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" - ); - - // Get the lookup table account - const lookupTableAccount = ( - await connection.getAddressLookupTable(lookupTableAddress) - ).value!; - - while (statusMap.includes(0)) { - // Continue until all are confirmed or errored - const pendingBatches = statusMap.filter((status) => status === 0).length; - console.log(`Sending ${pendingBatches} transactions`); - - const sends = statusMap.map(async (status, index) => { - if (status !== 0) return; // Skip non-pending batches - - let retries = 0; - while (retries < maxRetries && statusMap[index] === 0) { - if (!currentBlockhash) { - console.warn("Waiting for blockhash to be set..."); - await new Promise((resolve) => setTimeout(resolve, 1000)); - continue; - } - - try { - const tx = new VersionedTransaction( - new TransactionMessage({ - payerKey: payer.publicKey, - recentBlockhash: currentBlockhash, - instructions: batches[index], - }).compileToV0Message([lookupTableAccount]) - ); - tx.sign([payer]); - - const sig = bs58.encode(tx.signatures[0]); - console.log(`Batch ${index} signature: ${sig}`); - - const confirmedSig = await sendAndConfirmTx(connection, tx, { - skipPreflight: true, - commitment: "confirmed", - }); - - if (confirmedSig) { - statusMap[index] = 1; // Mark as confirmed - return { - type: BatchResultType.Success, - index, - signature: confirmedSig, - }; - } - } catch (e) { - retries++; - console.warn(`Retrying batch ${index}, attempt ${retries + 1}`); - if (retries >= maxRetries) { - statusMap[index] = `err: ${(e as Error).message}`; // Mark as error - return { - type: BatchResultType.Error, - index, - error: (e as Error).message, - }; - } - } - } - }); - - const results = await Promise.all(sends); - for (const result of results) { - if (result) yield result as BatchResult; - } - } - - // Stop the blockhash update loop - abortController.abort(); -} -``` - - - -
- -Finally, put it all together in your main file: - -
- -airdrop.ts (entrypoint file) - -```typescript -// airdrop.ts -import { Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; -import { - calculateComputeUnitPrice, - createRpc, - Rpc, -} from "@lightprotocol/stateless.js"; -import { createMint, getTokenPoolInfos } from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token"; -import { createAirdropInstructions } from "./create-instructions"; -import { BatchResultType, signAndSendAirdropBatches } from "./sign-and-send"; -import dotenv from "dotenv"; -import bs58 from "bs58"; -dotenv.config(); - -const RPC_ENDPOINT = `https://mainnet.helius-rpc.com?api-key=${process.env.HELIUS_API_KEY}`; -const connection: Rpc = createRpc(RPC_ENDPOINT); -const PAYER = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!)); - -// These are 20 example Solana Pubkeys -const recipients = [ - "GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7", - "GySGrTgPtPfMtYoYTmwUdUDFwVJbFMfip7QZdhgXp8dy", - "Bk1r2vcgX2uTzwV3AUyfRbSfGKktoQrQufBSrHzere74", - "8BvkadZ6ycFNmQF7S1MHRvEVNb1wvDBFdjkAUnxjK9Ug", - "EmxcvFKXsWLzUho8AhV9LCKeKRFHg5gAs4sKNJwhe5PF", - "6mqdHkSpcvNexmECjp5XLt9V9KnSQre9TvbMLGr6sEPM", - "3k4MViTWXBjFvoUZiJcNGPvzrqnTa41gcrbWCMMnV6ys", - "2k6BfYRUZQHquPtpkyJpUx3DzM7W3K6H95igtJk8ztpd", - "89jPyNNLCcqWn1RZThSS4jSqU5VCJkR5mAaSaVzuuqH4", - "3MzSRLf9jSt6d1MFFMMtPfUcDY6XziRxTB8C5mfvgxXG", - "9A1H6f3N8mpAPSdfqvYRD4cM1NwDZoMe3yF5DwibL2R2", - "PtUAhLvUsVcoesDacw198SsnMoFNVskR5pT3QvsBSQw", - "6C6W6WpgFK8TzTTMNCPMz2t9RaMs4XnkfB6jotrWWzYJ", - "8sLy9Jy8WSh6boq9xgDeBaTznn1wb1uFpyXphG3oNjL5", - "GTsQu2XCgkUczigdBFTWKrdDgNKLs885jKguyhkqdPgV", - "85UK4bjC71Jwpyn8mPSaW3oYyEAiHPbESByq9s5wLcke", - "9aEJT4CYHEUWwwSQwueZc9EUjhWSLD6AAbpVmmKDeP7H", - "CY8QjRio1zd9bYWMKiVRrDbwVenf3JzsGf5km5zLgY9n", - "CeHbdxgYifYhpB6sXGonKzmaejqEfq2ym5utTmB6XMVv", - "4z1qss12DjUzGUkK1fFesqrUwrEVJJvzPMNkwqYnbAR5", -].map((address) => new PublicKey(address)); - -(async () => { - /// Create an SPL mint + register it for compression. - const { mint, transactionSignature } = await createMint( - connection, - PAYER, - PAYER.publicKey, - 9 - ); - console.log( - `create-mint success! txId: ${transactionSignature}, mint: ${mint.toBase58()}` - ); - - /// Create an associated SPL token account for the sender (PAYER) - const ata = await getOrCreateAssociatedTokenAccount( - connection, - PAYER, - mint, - PAYER.publicKey - ); - console.log(`ATA: ${ata.address.toBase58()}`); - - /// Mint SPL tokens to the sender - const mintToTxId = await mintTo( - connection, - PAYER, - mint, - ata.address, - PAYER.publicKey, - 10e9 * LAMPORTS_PER_SOL // 10B tokens * decimals - ); - console.log(`mint-to success! txId: ${mintToTxId}`); - - const stateTreeInfos = await connection.getStateTreeInfos(); - - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - - const instructionBatches = await createAirdropInstructions({ - amount: 1e6, - recipients, - payer: PAYER.publicKey, - sourceTokenAccount: ata.address, - mint, - stateTreeInfos, - tokenPoolInfos, - computeUnitPrice: calculateComputeUnitPrice(10_000, 500_000), - }); - - for await (const result of signAndSendAirdropBatches( - instructionBatches, - PAYER, - connection - )) { - if (result.type === BatchResultType.Success) { - console.log(`Batch ${result.index} confirmed: ${result.signature}`); - } else if (result.type === BatchResultType.Error) { - console.log(`Batch ${result.index} failed: ${result.error}`); - // Use result.index to access the specific batch in instructionBatches - const failedBatch = instructionBatches[result.index]; - console.log(`Failed batch instructions:`, failedBatch); - // Additional logic to handle failed instructions - } - } - - console.log("Airdrop process complete."); -})(); - -``` - - - -
- -Ensure that you have all the necessary `.env` variables set up. You can now run your code and execute the airdrop! - -## Advanced: Decompress / Claim - -{% hint style="info" %} -Compressed tokens are supported in major Solana wallets like Phantom and Backpack. Still, you can let users decompress to SPL via your Frontend (FE) to customize claims. Here's how:point\_down: -{% endhint %} - -
- -Decompress SPL Tokens - -```typescript -import { - bn, - buildAndSignTx, - sendAndConfirmTx, - dedupeSigner, - Rpc, - createRpc, -} from "@lightprotocol/stateless.js"; -import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js"; -import { - CompressedTokenProgram, - getTokenPoolInfos, - selectMinCompressedTokenAccountsForTransfer, - selectTokenPoolInfosForDecompression, -} from "@lightprotocol/compressed-token"; -import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; -import bs58 from "bs58"; -import dotenv from "dotenv"; -dotenv.config(); - -// Set these values in your .env file -const RPC_ENDPOINT = process.env.RPC_ENDPOINT; -const mint = new PublicKey(process.env.MINT_ADDRESS!); -const payer = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!)); - -const owner = payer; -const amount = 1e5; -const connection: Rpc = createRpc(RPC_ENDPOINT); - -(async () => { - // 1. Create an associated token account for the user if it doesn't exist - const ata = await getOrCreateAssociatedTokenAccount( - connection, - payer, - mint, - payer.publicKey - ); - - // 2. Fetch compressed token accounts - const compressedTokenAccounts = - await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { - mint, - }); - - // 3. Select - const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer( - compressedTokenAccounts.items, - bn(amount) - ); - - // 4. Fetch validity proof - const proof = await connection.getValidityProof( - inputAccounts.map((account) => account.compressedAccount.hash) - ); - - // 5. Fetch token pool infos - const tokenPoolInfos = await getTokenPoolInfos(connection, mint); - - // 6. Select - const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression( - tokenPoolInfos, - amount - ); - - // 7. Build instruction - const ix = await CompressedTokenProgram.decompress({ - payer: payer.publicKey, - inputCompressedTokenAccounts: inputAccounts, - toAddress: ata.address, - amount, - tokenPoolInfos: selectedTokenPoolInfos, - recentInputStateRootIndices: proof.rootIndices, - recentValidityProof: proof.compressedProof, - }); - - // 8. Sign, send, and confirm - const { blockhash } = await connection.getLatestBlockhash(); - const additionalSigners = dedupeSigner(payer, [owner]); - const signedTx = buildAndSignTx( - [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix], - payer, - blockhash, - additionalSigners - ); - return await sendAndConfirmTx(connection, signedTx); -})(); - -``` - -
- -## Advanced Tips - -* Set priority fees dynamically for decompression. Learn more [here](https://docs.helius.dev/guides/sending-transactions-on-solana#summary). - -## Native Swap via Jup-API - -* If you have a custom FE, you can let users swap compressed tokens using the Jup-API. A reference implementation is available [here](https://github.com/Lightprotocol/example-jupiter-swap-node). - -*** - -## Support - -For additional support or questions, please refer to our [documentation](https://www.zkcompression.com), or contact [Swen](https://t.me/swen_light) or [Mert](https://t.me/mert_helius) on Telegram or via [email](mailto:friends@lightprotocol.com). diff --git a/compression-docs/developers/json-rpc-methods/README.md b/compression-docs/developers/json-rpc-methods/README.md deleted file mode 100644 index af09cd3..0000000 --- a/compression-docs/developers/json-rpc-methods/README.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: >- - Helius Labs maintains the canonical RPC API and indexer implementation. The - following pages outline all ZK Compression methods extending Solana's default - JSON RPC API. ---- - -# JSON RPC Methods - diff --git a/compression-docs/developers/json-rpc-methods/getcompressedaccount.md b/compression-docs/developers/json-rpc-methods/getcompressedaccount.md deleted file mode 100644 index 7c240d1..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedaccount.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedAccount - -Returns the compressed account with the given address or hash - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccount.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccount.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccount.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedaccountproof.md b/compression-docs/developers/json-rpc-methods/getcompressedaccountproof.md deleted file mode 100644 index 1a4b9bb..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedaccountproof.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedAccountProof - -Returns a proof the compression program uses to verify that the account is valid - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountProof.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountProof.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountProof.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedaccountsbyowner.md b/compression-docs/developers/json-rpc-methods/getcompressedaccountsbyowner.md deleted file mode 100644 index 83b7f80..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedaccountsbyowner.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedAccountsByOwner - -Returns the owner's compressed accounts. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountsByOwner.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountsByOwner.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountsByOwner.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedbalance.md b/compression-docs/developers/json-rpc-methods/getcompressedbalance.md deleted file mode 100644 index 6a11569..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedbalance.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedBalance - -Returns the balance for the compressed account with the given address or hash - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountBalance.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountBalance.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccountBalance.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedbalancebyowner.md b/compression-docs/developers/json-rpc-methods/getcompressedbalancebyowner.md deleted file mode 100644 index 852f36b..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedbalancebyowner.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedBalanceByOwner - -Returns the total balance of the owner's compressed accounts - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedBalanceByOwner.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedBalanceByOwner.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedminttokenholders.md b/compression-docs/developers/json-rpc-methods/getcompressedminttokenholders.md deleted file mode 100644 index 2bf5828..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedminttokenholders.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: >- - Returns the owner balances for a given mint in descending order. This is a - paginated endpoint ---- - -# getCompressedMintTokenHolders - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedMintTokenHolders.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedMintTokenHolders.yaml](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedMintTokenHolders.yaml) -{% endswagger %} - diff --git a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountbalance.md b/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountbalance.md deleted file mode 100644 index 6d4fcdc..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountbalance.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedTokenAccountBalance - -Returns the balance for a given token account - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountBalance.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountBalance.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountBalance.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbydelegate.md b/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbydelegate.md deleted file mode 100644 index fadbea1..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbydelegate.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedTokenAccountsByDelegate - -Returns the compressed token accounts that are partially or fully delegated to the given delegate. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByDelegate.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByDelegate.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByDelegate.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbyowner.md b/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbyowner.md deleted file mode 100644 index 6d807f1..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedtokenaccountsbyowner.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressedTokenAccountsByOwner - -Returns the compressed token accounts owned by a certain account - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByOwner.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByOwner.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedTokenAccountsByOwner.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressedtokenbalancesbyownerv2.md b/compression-docs/developers/json-rpc-methods/getcompressedtokenbalancesbyownerv2.md deleted file mode 100644 index daca8a7..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressedtokenbalancesbyownerv2.md +++ /dev/null @@ -1,8 +0,0 @@ -# getCompressedTokenBalancesByOwnerV2 - -Returns the token balances for a given owner. This is a paginated endpoint. The V2 version solves a minor naming issue. - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedTokenBalancesByOwnerV2.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedTokenBalancesByOwnerV2.yaml](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedTokenBalancesByOwnerV2.yaml) -{% endswagger %} - diff --git a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaccount.md b/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaccount.md deleted file mode 100644 index 005f6a8..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaccount.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressionSignaturesForAccount - -Return the signatures of the transactions that closed or opened a compressed account with the given hash. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAccount.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAccount.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAccount.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaddress.md b/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaddress.md deleted file mode 100644 index a287058..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforaddress.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressionSignaturesForAddress - -Return the signatures of the transactions that closed or opened a compressed account with the given address. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAddress.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAddress.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForAddress.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforowner.md b/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforowner.md deleted file mode 100644 index f8d839f..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesforowner.md +++ /dev/null @@ -1,7 +0,0 @@ -# getCompressionSignaturesForOwner - -Returns the signatures of the transactions that have modified an owner's compressed accounts. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForOwner.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForOwner.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForOwner.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesfortokenowner.md b/compression-docs/developers/json-rpc-methods/getcompressionsignaturesfortokenowner.md deleted file mode 100644 index 501f824..0000000 --- a/compression-docs/developers/json-rpc-methods/getcompressionsignaturesfortokenowner.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: >- - Returns the signatures of the transactions that have modified an owner's - compressed token accounts. This is a paginated endpoint -coverY: 0 ---- - -# getCompressionSignaturesForTokenOwner - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForTokenOwner.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForTokenOwner.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressionSignaturesForTokenOwner.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getindexerhealth.md b/compression-docs/developers/json-rpc-methods/getindexerhealth.md deleted file mode 100644 index 88f9cdf..0000000 --- a/compression-docs/developers/json-rpc-methods/getindexerhealth.md +++ /dev/null @@ -1,7 +0,0 @@ -# getIndexerHealth - -Returns an error if the indexer is stale by more than a configurable number of blocks. Otherwise, it returns `ok` - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerHealth.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerHealth.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerHealth.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getindexerslot.md b/compression-docs/developers/json-rpc-methods/getindexerslot.md deleted file mode 100644 index c4bab94..0000000 --- a/compression-docs/developers/json-rpc-methods/getindexerslot.md +++ /dev/null @@ -1,7 +0,0 @@ -# getIndexerSlot - -Returns the slot of the last block indexed by the indexer - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerSlot.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerSlot.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getIndexerSlot.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getlatestcompressionsignatures.md b/compression-docs/developers/json-rpc-methods/getlatestcompressionsignatures.md deleted file mode 100644 index a638cf2..0000000 --- a/compression-docs/developers/json-rpc-methods/getlatestcompressionsignatures.md +++ /dev/null @@ -1,7 +0,0 @@ -# getLatestCompressionSignatures - -Returns the signatures of the latest transactions that used the compression program. This is a paginated endpoint - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestCompressionSignatures.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestCompressionSignatures.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestCompressionSignatures.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getlatestnonvotingsignatures.md b/compression-docs/developers/json-rpc-methods/getlatestnonvotingsignatures.md deleted file mode 100644 index 64dc102..0000000 --- a/compression-docs/developers/json-rpc-methods/getlatestnonvotingsignatures.md +++ /dev/null @@ -1,7 +0,0 @@ -# getLatestNonVotingSignatures - -Returns the signatures of the latest transactions that are not voting transactions - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestNonVotingSignatures.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestNonVotingSignatures.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getLatestNonVotingSignatures.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccountproofs.md b/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccountproofs.md deleted file mode 100644 index f295209..0000000 --- a/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccountproofs.md +++ /dev/null @@ -1,7 +0,0 @@ -# getMultipleCompressedAccountProofs - -Returns multiple proofs used by the compression program to verify the accounts' validity - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccountProofs.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccountProofs.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccountProofs.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccounts.md b/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccounts.md deleted file mode 100644 index 1d6795b..0000000 --- a/compression-docs/developers/json-rpc-methods/getmultiplecompressedaccounts.md +++ /dev/null @@ -1,7 +0,0 @@ -# getMultipleCompressedAccounts - -Returns multiple compressed accounts with the given addresses or hashes - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccounts.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccounts.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleCompressedAccounts.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getmultiplenewaddressproofs.md b/compression-docs/developers/json-rpc-methods/getmultiplenewaddressproofs.md deleted file mode 100644 index 73eb965..0000000 --- a/compression-docs/developers/json-rpc-methods/getmultiplenewaddressproofs.md +++ /dev/null @@ -1,9 +0,0 @@ -# getMultipleNewAddressProofs - -Returns proofs that the new addresses are not taken already and can be created - - - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleNewAddressProofs.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleNewAddressProofs.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getMultipleNewAddressProofs.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/gettransactionwithcompressioninfo.md b/compression-docs/developers/json-rpc-methods/gettransactionwithcompressioninfo.md deleted file mode 100644 index 86642c1..0000000 --- a/compression-docs/developers/json-rpc-methods/gettransactionwithcompressioninfo.md +++ /dev/null @@ -1,7 +0,0 @@ -# getTransactionWithCompressionInfo - -Returns the transaction data for the transaction with the given signature along with parsed compression info - -{% swagger src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getTransactionWithCompressionInfo.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getTransactionWithCompressionInfo.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getTransactionWithCompressionInfo.yaml) -{% endswagger %} diff --git a/compression-docs/developers/json-rpc-methods/getvalidityproof.md b/compression-docs/developers/json-rpc-methods/getvalidityproof.md deleted file mode 100644 index 5af6f43..0000000 --- a/compression-docs/developers/json-rpc-methods/getvalidityproof.md +++ /dev/null @@ -1,17 +0,0 @@ -# getValidityProof - -Returns a single ZK Proof used by the compression program to verify that the given accounts are valid and that the new addresses can be created - -{% hint style="info" %} -* Proof limits per request are: - * `hashes`: 1, 2, 3, 4, or 8 - * `newAddressesWithTrees` : 1, 2 -* The `newAddresses` param field is supported but deprecated. Please use `newAddressesWithTrees`instead.\ - -{% endhint %} - - - -{% openapi src="https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getValidityProof.yaml" path="/" method="post" %} -[https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getValidityProof.yaml](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getValidityProof.yaml) -{% endopenapi %} diff --git a/compression-docs/developers/typescript-client.md b/compression-docs/developers/typescript-client.md deleted file mode 100644 index d638be7..0000000 --- a/compression-docs/developers/typescript-client.md +++ /dev/null @@ -1,187 +0,0 @@ -# TypeScript Client - -## Stateless.js API Reference Guide - -The [@lightprotocol/stateless.js library](https://www.npmjs.com/package/@lightprotocol/stateless.js) lets you build Javascript clients that interact with the ZK Compression primitive via the [ZK Compression JSON RPC API](json-rpc-methods/). - -## Installation - -
Package ManagerCommand
NPM
npm install --save \
-    @lightprotocol/stateless.js \
-    @lightprotocol/compressed-token \
-    @solana/web3.js \
-    @lightprotocol/zk-compression-cli
-
Yarn
yarn add \
-    @lightprotocol/stateless.js \
-    @solana/web3.js \
-    @lightprotocol/zk-compression-cli
-
- -## Basics - -### Rpc - -[Source Documentation](https://github.com/Lightprotocol/light-protocol/blob/main/js/stateless.js/src/rpc.ts) - -The `Rpc` connection is used to interact with the [ZK Compression JSON RPC](json-rpc-methods/). It's a thin wrapper extending [Solana's web3.js `Connection` class](https://solana-labs.github.io/solana-web3.js/classes/Connection.html) with compression-related endpoints, such as `getCompressedAccount`, `getCompressedTokenBalancesByOwner`, and more. - -**Example Usage with Devnet** - -```typescript -const stateless = require("@lightprotocol/stateless.js"); - - -/// Helius exposes Solana and compression RPC endpoints through a single URL -const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; -const COMPRESSION_RPC_ENDPOINT = RPC_ENDPOINT; -const PROVER_ENDPOINT = RPC_ENDPOINT -const connection: Rpc = createRpc(RPC_ENDPOINT, COMPRESSION_RPC_ENDPOINT, PROVER_ENDPOINT) - -async function main() { - let slot = await connection.getSlot(); - console.log(slot); - - let health = await connection.getIndexerHealth(slot); - console.log(health); - // "Ok" -} - -main(); -``` - -Visit the [JSON RPC Methods](json-rpc-methods/) section for the full list of compression endpoints supported in `Rpc` . - -## Quickstart - -### Starting the `test-validator` for Local Development - -```sh -light test-validator -``` - -The command above will start a single-node Solana cluster, an RPC node, and a prover node at ports 8899, 8784, and 3001. - -### Creating and Sending Transactions - -#### Creating, Minting, and Transferring a Compressed Token - -{% hint style="info" %} -This example uses the **compressed token program**, which is built using ZK Compression and offers an SPL-compatible token layout. -{% endhint %} - -```typescript -import { - LightSystemProgram, - Rpc, - confirmTx, - createRpc, -} from "@lightprotocol/stateless.js"; -import { createMint, mintTo, transfer } from "@lightprotocol/compressed-token"; -import { Keypair } from "@solana/web3.js"; - -const payer = Keypair.generate(); -const tokenRecipient = Keypair.generate(); - -/// Localnet -const connection: Rpc = createRpc(); - -const main = async () => { - /// Airdrop lamports to pay fees - await confirmTx( - connection, - await connection.requestAirdrop(payer.publicKey, 10e9) - ); - - await confirmTx( - connection, - await connection.requestAirdrop(tokenRecipient.publicKey, 1e6) - ); - - /// Create a compressed token mint - const { mint, transactionSignature } = await createMint( - connection, - payer, - payer.publicKey, - 9 // Number of decimals - ); - - console.log(`create-mint success! txId: ${transactionSignature}`); - - /// Mint compressed tokens to the payer's account - const mintToTxId = await mintTo( - connection, - payer, - mint, - payer.publicKey, // Destination - payer, - 1e9 // Amount - ); - - console.log(`Minted 1e9 tokens to ${payer.publicKey} was a success!`); - console.log(`txId: ${mintToTxId}`); - - /// Transfer compressed tokens - const transferTxId = await transfer( - connection, - payer, - mint, - 7e8, // Amount - payer, // Owner - tokenRecipient.publicKey // To address - ); - - console.log(`Transfer of 7e8 ${mint} to ${tokenRecipient.publicKey} was a success!`); - console.log(`txId: ${transferTxId}`); -}; - -main(); -``` - -### Creating Lookup Tables - -{% hint style="info" %} -For public networks, we provide [shared lookup tables](addresses-and-urls.md#lookup-tables) for Light's common program IDs and accounts -{% endhint %} - -```typescript -import { Rpc, confirmTx, createRpc } from "@lightprotocol/stateless.js"; -import { createTokenProgramLookupTable } from "@lightprotocol/compressed-token"; -import { Keypair, PublicKey} from "@solana/web3.js"; -import { RPC_ENDPOINT } from "./constants"; -const payer = Keypair.generate(); -const authority = payer; -const additionalTokenMints : PublicKey[] = []; -const additionalAccounts : PublicKey[] = []; - -// Localnet -const connection: Rpc = createRpc(); - -const main = async () => { - /// airdrop lamports to pay gas and rent - await confirmTx( - connection, - await connection.requestAirdrop(payer.publicKey, 1e7) - ); - - /// Create LUT - const { address } = await createTokenProgramLookupTable( - connection, - payer, - authority, - additionalTokenMints, - additionalAccounts - ); - - console.log("Created lookup table:", address.toBase58()); -}; - -main(); -``` - -### Examples - -To get started building with examples, check out these GitHub repositories: - -* [Web Example Client](https://github.com/Lightprotocol/example-web-client) -* [Node Example Client](https://github.com/Lightprotocol/example-nodejs-client) -* [Token Escrow Program Example](https://github.com/Lightprotocol/light-protocol/tree/light-v0.3.0/examples/token-escrow) diff --git a/compression-docs/developers/using-token-2022.md b/compression-docs/developers/using-token-2022.md deleted file mode 100644 index 46ee8e7..0000000 --- a/compression-docs/developers/using-token-2022.md +++ /dev/null @@ -1,189 +0,0 @@ -# Using Token-2022 - -You can compress token-2022 accounts of mints with the following mint-extensions: - -* MetadataPointer -* TokenMetadata -* InterestBearingConfig -* GroupPointer -* GroupMemberPointer -* TokenGroup -* TokenGroupMember - -All other extensions are not yet supported. - -{% hint style="info" %} -If you require support for other mint-extensions, [let us know](https://t.me/swen_light)! -{% endhint %} - -## Minting, compressing, and transferring tokens with Token-2022 Metadata - -{% hint style="info" %} -You need the following SDK versions: - -* `@lightprotocol/stateless.js` ≥ 0.21.0 -* `@lightprotocol/compressed-token` ≥ 0.21.0 -* `@solana/web3.js` ≥ 1.95.3 -{% endhint %} - -```typescript -import { confirmTx, createRpc } from "@lightprotocol/stateless.js"; -import { - compress, - createTokenPool, - transfer, -} from "@lightprotocol/compressed-token"; -import { - getOrCreateAssociatedTokenAccount, - mintTo as mintToSpl, - TOKEN_2022_PROGRAM_ID, - createInitializeMetadataPointerInstruction, - createInitializeMintInstruction, - ExtensionType, - getMintLen, - LENGTH_SIZE, - TYPE_SIZE, -} from "@solana/spl-token"; -import { - Keypair, - sendAndConfirmTransaction, - SystemProgram, - Transaction, -} from "@solana/web3.js"; -import { - createInitializeInstruction, - pack, - TokenMetadata, -} from "@solana/spl-token-metadata"; -import dotenv from "dotenv"; -import bs58 from "bs58"; -dotenv.config(); - -// set these values in your .env file -const payer = Keypair.fromSecretKey(bs58.decode(process.env.PAYER_KEYPAIR!)); -const RPC_ENDPOINT = process.env.RPC_ENDPOINT!; -const connection = createRpc(RPC_ENDPOINT); - -(async () => { - const mint = Keypair.generate(); - const decimals = 9; - - const metadata: TokenMetadata = { - mint: mint.publicKey, - name: "name", - symbol: "symbol", - uri: "uri", - additionalMetadata: [["key", "value"]], - }; - - const mintLen = getMintLen([ExtensionType.MetadataPointer]); - - const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; - - // airdrop to pay gas - await confirmTx( - connection, - await connection.requestAirdrop(payer.publicKey, 1e7) - ); - - const mintLamports = await connection.getMinimumBalanceForRentExemption( - mintLen + metadataLen - ); - const mintTransaction = new Transaction().add( - SystemProgram.createAccount({ - fromPubkey: payer.publicKey, - newAccountPubkey: mint.publicKey, - space: mintLen, - lamports: mintLamports, - programId: TOKEN_2022_PROGRAM_ID, - }), - createInitializeMetadataPointerInstruction( - mint.publicKey, - payer.publicKey, - mint.publicKey, - TOKEN_2022_PROGRAM_ID - ), - createInitializeMintInstruction( - mint.publicKey, - decimals, - payer.publicKey, - null, - TOKEN_2022_PROGRAM_ID - ), - createInitializeInstruction({ - programId: TOKEN_2022_PROGRAM_ID, - mint: mint.publicKey, - metadata: mint.publicKey, - name: metadata.name, - symbol: metadata.symbol, - uri: metadata.uri, - mintAuthority: payer.publicKey, - updateAuthority: payer.publicKey, - }) - ); - const txId = await sendAndConfirmTransaction(connection, mintTransaction, [ - payer, - mint, - ]); - - console.log(`txId: ${txId}`); - - // register the mint with the Compressed-Token program - const txId2 = await createTokenPool( - connection, - payer, - mint.publicKey, - undefined, - TOKEN_2022_PROGRAM_ID - ); - console.log(`register-mint success! txId: ${txId2}`); - - const ata = await getOrCreateAssociatedTokenAccount( - connection, - payer, - mint.publicKey, - payer.publicKey, - undefined, - undefined, - undefined, - TOKEN_2022_PROGRAM_ID - ); - - console.log(`ATA: ${ata.address}`); - // Mint SPL - const mintTxId = await mintToSpl( - connection, - payer, - mint.publicKey, - ata.address, - payer.publicKey, - 1e5, - undefined, - undefined, - TOKEN_2022_PROGRAM_ID - ); - console.log(`mint-spl success! txId: ${mintTxId}`); - - const compressedTokenTxId = await compress( - connection, - payer, - mint.publicKey, - 1e5, - payer, - ata.address, - payer.publicKey - ); - console.log(`compressed-token success! txId: ${compressedTokenTxId}`); - - const transferCompressedTxId = await transfer( - connection, - payer, - mint.publicKey, - 1e5, - payer, - payer.publicKey // self-transfer - ); - console.log(`transfer-compressed success! txId: ${transferCompressedTxId}`); -})(); - -``` diff --git a/compression-docs/get-started/intro-to-development.md b/compression-docs/get-started/intro-to-development.md deleted file mode 100644 index 4d983cc..0000000 --- a/compression-docs/get-started/intro-to-development.md +++ /dev/null @@ -1,236 +0,0 @@ ---- -description: >- - Welcome! This guide has everything you need to know to start developing with - ZK Compression on Solana. ---- - -# Intro to Development - -{% hint style="info" %} -For the sake of brevity, this guide assumes you are familiar with the basics of Solana. If you aren't, we recommend reading the following: - -* [Solana documentation](https://solana.com/docs/intro/dev) -* [The Solana Programming Model: An Introduction to Developing on Solana](https://www.helius.dev/blog/the-solana-programming-model-an-introduction-to-developing-on-solana) -{% endhint %} - -## What You'll Need to Get Started - -Development with ZK Compression on Solana consists of two main parts: - -* [Client development](intro-to-development.md#client-side-development) -* [On-chain program development](intro-to-development.md#on-chain-program-development) - -The [ZK Compression RPC API](../developers/json-rpc-methods/) is the glue between clients and on-chain programs. It extends Solana's [JSON RPC API](https://solana.com/docs/rpc) with additional endpoints for interacting with ZK compressed accounts. You can view the complete list of supported endpoints [here](../developers/json-rpc-methods/). - -### A Quick Intro to Client-side Development - -The following TypeScript and Rust SDKs are used to interact with ZK Compression: - -
LanguageSDKDescription
TypeScript@lightprotocol/stateless.jsSDK to interact with compression programs via the ZK Compression RPC API
TypeScript@lightprotocol/compressed-tokenSDK to interact with the compressed token program
Rustlight-sdkRust client
- -#### RPC Connection - -You need an RPC connection to interact with the network. You can either work with an RPC infrastructure provider that supports ZK Compression or run your own RPC Node. - -{% hint style="info" %} -[Helius Labs](https://github.com/helius-labs) supports ZK Compression and maintains its canonical RPC and [Photon indexer implementation](https://github.com/helius-labs/photon). - -Our local dev tooling supports Photon out of the box via the `light test-validator` command. To learn how to run a standalone Photon RPC node, visit the [Run a Node](../node-operators/run-a-node.md#photon-indexer-node) section. -{% endhint %} - -### Quickstart - -{% hint style="info" %} -The code samples work! You can copy & paste them into your IDE and run! -{% endhint %} - -#### Installation (Node.js, Web) - -{% tabs %} -{% tab title="npm" %} -```bash -npm install -g @lightprotocol/zk-compression-cli && \ -npm install --save \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js -``` -{% endtab %} - -{% tab title="yarn" %} -```bash -yarn global add @lightprotocol/zk-compression-cli && \ -yarn add \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js -``` -{% endtab %} - -{% tab title="pnpm" %} -```bash -pnpm add -g @lightprotocol/zk-compression-cli && \ -pnpm add \ - @lightprotocol/stateless.js \ - @lightprotocol/compressed-token \ - @solana/web3.js -``` -{% endtab %} -{% endtabs %} - -#### Creating an RPC Connection - -
import {
-  Rpc,
-  createRpc,
-} from "@lightprotocol/stateless.js";
-
-// Helius exposes Solana and Photon RPC endpoints through a single URL
-const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<api_key>";
-const PHOTON_ENDPOINT = RPC_ENDPOINT;
-const PROVER_ENDPOINT = RPC_ENDPOINT;
-const connection: Rpc = createRpc(RPC_ENDPOINT, PHOTON_ENDPOINT, PROVER_ENDPOINT)
-
-console.log("connection", connection);
-
- -#### Using Localnet - -```sh -# Start a local test validator -light test-validator -``` - -```typescript -const stateless = require("@lightprotocol/stateless.js"); - -const connection = stateless.createRpc(); - -async function main() { - let slot = await connection.getSlot(); - console.log(slot); - - let health = await connection.getIndexerHealth(slot); - console.log(health); - // "Ok" -} - -main(); -``` - -#### Minting and Transferring Compressed Tokens - -{% hint style="info" %} -This example uses the **compressed token program**, which is built using ZK Compression and offers an SPL-compatible token layout. -{% endhint %} - -```typescript -import { - LightSystemProgram, - Rpc, - confirmTx, - createRpc, -} from "@lightprotocol/stateless.js"; -import { createMint, mintTo, transfer } from "@lightprotocol/compressed-token"; -import { Keypair } from "@solana/web3.js"; - -const payer = Keypair.generate(); -const tokenRecipient = Keypair.generate(); - -/// Helius exposes Solana and compression RPC endpoints through a single URL -const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key="; -const COMPRESSION_ENDPOINT = RPC_ENDPOINT; -const PROVER_ENDPOINT = RPC_ENDPOINT; -const connection: Rpc = createRpc(RPC_ENDPOINT, COMPRESSION_ENDPOINT, PROVER_ENDPOINT) - -const main = async () => { - /// Airdrop lamports to pay fees - await confirmTx( - connection, - await connection.requestAirdrop(payer.publicKey, 10e9) - ); - - await confirmTx( - connection, - await connection.requestAirdrop(tokenRecipient.publicKey, 1e6) - ); - - /// Create compressed token mint - const { mint, transactionSignature } = await createMint( - connection, - payer, - payer.publicKey, - 9 // Number of decimals - ); - - console.log(`create-mint success! txId: ${transactionSignature}`); - - /// Mint compressed tokens to the payer's account - const mintToTxId = await mintTo( - connection, - payer, - mint, - payer.publicKey, // Destination - payer, - 1e9 // Amount - ); - - console.log(`Minted 1e9 tokens to ${payer.publicKey} was a success!`); - console.log(`txId: ${mintToTxId}`); - - /// Transfer compressed tokens from payer to tokenRecipient's pubkey - const transferTxId = await transfer( - connection, - payer, - mint, - 7e8, // Amount - payer, // Owner - tokenRecipient.publicKey // To address - ); - - console.log(`Transfer of 7e8 ${mint} to ${tokenRecipient.publicKey} was a success!`); - console.log(`txId: ${transferTxId}`); -}; - -main(); -``` - -### On-chain Program Development - -{% hint style="info" %} -The ZK Compression primitive is the core of [the Light protocol](https://github.com/Lightprotocol). To leverage ZK Compression, your custom program invokes the _Light system program_ via Cross-Program Invocation (CPI). -{% endhint %} - -You can write custom programs using ZK compression in Anchor or native Rust. - -First, ensure your development environment has installed Rust, the Solana CLI, and Anchor. If you haven't installed them, refer to this [setup guide](https://solana.com/developers/guides/getstarted/setup-local-development). - -We provide tooling for testing your on-chain program on a local Solana cluster. The `light test-validator` command, available with the [ZK Compression CLI](https://github.com/Lightprotocol/light-protocol/blob/main/cli/README.md), automatically initializes a local Solana cluster with the compression programs, all necessary system accounts, and syscalls activated. By default, it also starts a local Photon RPC instance and Prover node. - -
ProgramDescription
light-system-program

The system program. It enforces the compressed account layout with ownership and sum checks and verifies the validity of your input state

It is also invoked to create/write to compressed accounts and PDAs

light-compressed-tokenA compressed token implementation built on top of ZK Compression. It enforces a SPL-compatible token layout and allows for arbitrary compression/decompression between this and the SPL standard
account-compressionImplements state and address trees. It is used by the Light System program
- -## Build by Example - -While you get started building with ZK Compression, use these GitHub resources available to help accelerate your journey: - -* [Web Example Client](https://github.com/Lightprotocol/example-web-client) -* [Node Example Client](https://github.com/Lightprotocol/example-nodejs-client) -* [Token Escrow Anchor Program](https://github.com/Lightprotocol/light-protocol/tree/light-v0.3.0/examples/token-escrow) - -## Developer Environments - -ZK Compression is available on Localnet using `light test-validator`, Devnet, and Mainnet-Beta. - -## Getting Support - -For the best support, head to the: - -* [Solana StackExchange](https://solana.stackexchange.com/) for Solana-specific questions -* [Light Developer Discord](https://discord.gg/CYvjBgzRFP) for program and client-related questions -* [Helius Developer Discord](https://discord.gg/Uzzf6a7zKr) for RPC-related questions - -Remember to include as much detail as possible in your question, and please use text (not screenshots) to show error messages so other people with the same problem can find your question! - -## Next Steps - -You're now ready to start building with ZK Compression! Head to the [Client Quickstart](../developers/typescript-client.md) section, or [build ](intro-to-development.md#build-by-example)[a program](intro-to-development.md#build-by-example)! diff --git a/compression-docs/introduction.md b/compression-docs/introduction.md new file mode 100644 index 0000000..0e2781b --- /dev/null +++ b/compression-docs/introduction.md @@ -0,0 +1,61 @@ + +# Introduction + +
+ +ZK Compression is a Solana account primitive that lets you create tokens and PDAs at a fraction of the cost. Scale your application and tokens to millions of users without sacrificing composability, security and performance. + +
CreationRegular AccountCompressed AccountCost Reduction
100-byte PDA Account0.0016 SOL~ 0.00001 SOL 160x
100 Token Accounts~ 0.2 SOL~ 0.00004 SOL5000x
+ +## Core Features + +

Rent-free accounts

Create accounts without upfront rent exempt balance.

L1 Performance

Execution and data availability on Solana.

Fully Composable

Compatible with existing Solana programs.
+ +## Start Building + +
Cover image (dark)

Quick Start

Deploy and test compressed tokens in under 5 minutes.Light Protocol v2 - Batched Merkle trees-8 (1).pngquickstart.mdLight Protocol v2 - Batched Merkle trees-7.png

Compressed Tokens

Create and distribute tokens 5000x cheaper with full SPL compatibility.Light Protocol v2 - Batched Merkle trees-54.pngBroken linkLight Protocol v2 - Batched Merkle trees-39 (1).png

Compressed PDAs

Store user and application state 160x cheaper with full PDA functionality.Light Protocol v2 - Batched Merkle trees-55.pngBroken linkLight Protocol v2 - Batched Merkle trees-40 (1).png
+ +## ZK and Compression in a Nutshell + +ZK Compression uses state compression and [zero-knowledge proofs](#user-content-fn-1)[^1] to reduce on-chain state cost. + +{% stepper %} +{% step %} +### Compression + +State compression removes rent cost per account. Only a fingerprint of millions of compressed accounts (the state root) is stored in on-chain accounts for secure verification. The underlying data is stored on the [Solana ledger](#user-content-fn-2)[^2]. +{% endstep %} + +{% step %} +### ZK + +The protocol uses small zero-knowledge proofs (validity proofs) to verify the integrity of the compressed accounts. This is all done under the hood. You can fetch validity proofs from RPC providers that support ZK Compression. +{% endstep %} +{% endstepper %} + +## Using AI to work with ZK Compression + +These are some simple tools to use AI for your development with ZK Compression. + +
ToolDescriptionLink
LLMs.txtIndex of site that helps LLMs map documentation.https://zkcompression.com/llms.txt
.mdCopy the page you are working on as Markdown for LLMs for questions.Top right corner on each page, or add suffix e.g. https://zkcompression.com.md
+ +## Resources + +
Cover image (dark)Cover image (dark)Cover image (dark)

RPC Methods

Browse ZK Compression's JSON RPC methods. Light Protocol v2 - Batched Merkle trees-62.pngLight Protocol v2 - Batched Merkle trees-9 (1).pngjson-rpc-methodsLight Protocol v2 - Batched Merkle trees-62.pngLight Protocol v2 - Batched Merkle trees-10 (1).png

SDKs

Explore our TypeScript and Rust SDKs.Light Protocol v2 - Batched Merkle trees-63.pngLight Protocol v2 - Batched Merkle trees-73 (1).pngsdksLight Protocol v2 - Batched Merkle trees-63.pngLight Protocol v2 - Batched Merkle trees-62 (1).png

CLI Installation

Install the ZK Compression CLI.Light Protocol v2 - Batched Merkle trees-41 (1).pngLight Protocol v2 - Batched Merkle trees-68.pngLight Protocol v2 - Batched Merkle trees-68.png
+ +## Learn & Community + +
Cover image (dark)Cover image (dark)

Start Learning

Learn about ZK Compression's core concepts.Light Protocol v2 - Batched Merkle trees-38.pngcore-conceptsLight Protocol v2 - Batched Merkle trees-70.pngLight Protocol v2 - Batched Merkle trees-70.png

Security

Read our external audit and formal verification reports.Light Protocol v2 - Batched Merkle trees-53.pngLight Protocol v2 - Batched Merkle trees-2.png

Discord

Join our Discord for support and discussionsLight Protocol v2 - Batched Merkle trees-50.pnghttps://discord.com/invite/CYvjBgzRFPLight Protocol v2 - Batched Merkle trees-69.png
+ +*** + +{% content-ref url="quickstart.md" %} +[quickstart.md](quickstart.md) +{% endcontent-ref %} + +[^1]: A cryptographic proof to verify the validity of a statement without revealing the underlying data.\ + + + ZK Compression uses a Groth16 SNARK zk proof with a constant 128 bytes size. + +[^2]: The ledger is an immutable historical record of all Solana transactions signed by clients since the genesis block. diff --git a/compression-docs/learn/core-concepts/README.md b/compression-docs/learn/core-concepts/README.md deleted file mode 100644 index aaebb87..0000000 --- a/compression-docs/learn/core-concepts/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Core Concepts - -ZK Compression is a new primitive on Solana that drastically reduces on-chain state costs while maintaining security, composability, and performance. ZK Compression allows developers to efficiently scale their applications to millions of users with the help of zero-knowledge proofs. - -In the following sections, we'll dive into the core concepts that make ZK Compression possible: - -* **Compressed Account Model**: Understand how compressed accounts differ from regular Solana accounts (not much!) and how they enhance efficiency. -* **State Trees**: Learn about the Merkle tree structure used to store compressed accounts and how it minimizes on-chain storage. -* **Validity Proofs**: Explore how the protocol uses zero-knowledge proofs to verify data validity while keeping proof sizes small. -* **Lifecycle of a Transaction**: Follow the journey of a ZK-compressed transaction from creation to execution. -* **Limitations and Considerations**: Discover the trade-offs and scenarios where ZK Compression may or may not be the best solution. diff --git a/compression-docs/learn/core-concepts/compressed-account-model.md b/compression-docs/learn/core-concepts/compressed-account-model.md index 4597ba0..e5b9053 100644 --- a/compression-docs/learn/core-concepts/compressed-account-model.md +++ b/compression-docs/learn/core-concepts/compressed-account-model.md @@ -1,5 +1,8 @@ + # Compressed Account Model +*** + {% hint style="info" %} This guide assumes that you're familiar with [Solana's account model](https://solana.com/docs/core/accounts). {% endhint %} @@ -11,7 +14,7 @@ ZK compressed state is stored in compressed accounts. Compressed accounts are si * Each compressed account can be identified by its hash * Each write to a compressed account changes its hash * An `address` can optionally be set as a permanent unique ID of the compressed account -* All compressed accounts are stored in [sparse state trees](state-trees.md). Only the tree's state root (i.e., a small fingerprint of all compressed accounts) is stored in the on-chain account space +* All compressed accounts are stored in [sparse state trees](compressed-account-model.md#state-merkle-trees). Only the tree's state root (i.e., a small fingerprint of all compressed accounts) is stored in the on-chain account space These differences allow the protocol to store states as calldata in the less expensive Solana ledger space instead of costly on-chain account space @@ -19,15 +22,15 @@ To understand the similarities and differences between Solana's regular account > If you don't know what PDAs are, read [this explainer](https://solana.com/docs/core/pda) first -## Compressed PDA Accounts +### Compressed PDA Accounts Like regular accounts, each compressed PDA account can be identified by its unique persistent address, represented as 32 bytes in the format of a `PublicKey`. Like PDAs, compressed account addresses don't belong to a private key; rather, they're derived from the program that owns them -

Compressed PDA Accounts

+
A diagram illustrating the structure of a compressed account in the Light Protocol system. The compressed account is owned by a program and is associated with a compressed PDA (Program Derived Address) account. The compressed account contains data bytes, lamports (account balance), an owner (the program that owns the account), and an address (represented as a public key). The owner of the compressed account has the authority to modify its data and transfer lamports from it. The compressed account is identified by its unique hash, which changes with each write operation. An address can be optionally set as a permanent unique identifier for the compressed account. Compressed accounts are stored in sparse Merkle trees, with only the trees' sparse state structure and roots stored in the on-chain account space, while the underlying data is stored off-chain.

Compressed PDA Accounts

The compressed PDA account layout is similar to Solana's regular PDA account layout — it has the **Data**, **Lamports**, **Owner**, and **Address** fields. The **Data** field stores the program state. Notice the enshrined **AccountData** structure: **Discriminator**, **Data**, **DataHash**: -

Compressed PDA Account with AccountData

+
A diagram depicting the structure of a compressed PDA (Program Derived Address) account in the Light Protocol system. The compressed PDA account consists of a data field, which stores program-owned state, lamports (account balance), an owner field indicating the program that owns the account, and an address field represented as a public key. The AccountData structure within the compressed PDA account is composed of a discriminator (a unique identifier for the account type), the actual data (program state), and a DataHash (a hash of the account data). This AccountData structure is specific to the Light Protocol and differs from Solana's regular account data field.

Compressed PDA Account with AccountData

The [Anchor](https://www.anchor-lang.com/) framework reserves the first 8 bytes of a regular account's data field for the discriminator. This helps programs distinguish between different program-owned accounts. The default compressed account layout is opinionated in this regard and enforces a discriminator in the Data field. You can ignore the **DataHash** field for now; we cover its importance for ZK Compression later. @@ -44,5 +47,76 @@ Instead, each compressed account can be identified by its hash, regardless of wh By definition, whenever the data of a compressed account changes, its hash changes. This impacts how developers interact with fungible state: -* Check out the [examples](../../get-started/intro-to-development.md#build-by-example) section to see what using hashes instead of addresses looks like in practice -* Visit the [State Trees](state-trees.md) section to understand why using the account's hash as its ID makes sense for the compression protocol +Check out the examples to see what using hashes instead of addresses looks like in practice + +* [Web Example Client](https://github.com/Lightprotocol/example-web-client) +* [Node Example Client](https://github.com/Lightprotocol/example-nodejs-client) +* [Token Escrow Anchor Program](https://github.com/Lightprotocol/light-protocol/tree/light-v0.3.0/examples/token-escrow) + +## State Merkle trees + +Compressed accounts are stored as hashes in state Merkle trees. + +A State tree is a binary Merkle tree that stores data of millions of compressed Solana accounts in leaves for efficient cryptographic verification the integrity of all leaves in a tree. + +{% stepper %} +{% step %} +### Merkle Tree + +A Merkle tree compresses data by hashing adjacent leaves repeatedly into a single root hash, starting from the lowest level. The hash of a compressed Solana account is stored as a leaf in a State tree. +{% endstep %} + +{% step %} +### Merkle Root Hash + +Only this root hash is stored on chain as single value on chain to secure the integrity of all compressed state in a tree. The raw state can thus be stored as calldata in the much cheaper Solana ledger space while preserving Solana's security guarantees. +{% endstep %} +{% endstepper %} + +
A diagram illustrating a binary state Merkle tree with a depth of 3 and a proof path for Leaf 1. The tree consists of 8 leaves, each representing a compressed account hash. The leaves are paired up to form nodes at each level, culminating in a single root node at the top. The proof path for Leaf 1 is highlighted, showing the hashes needed to verify the integrity of Leaf 1 up to the root node. For ZK Compression V1, "Light" state trees have a depth of 26, allowing for approximately 67 million leaves with compressed account hashes per tree.

A Binary state Merkle tree, with depth 3 and proof path for Leaf 1.
For ZK Compression, “Light” state trees have a depth of 26, i.e. contain ~67 million leaves with compressed account hashes per tree.

+ +{% hint style="success" %} +State trees are fungible and provided by the protocol. Developers don’t need to maintain or initialize State trees themselves. +{% endhint %} + +### Leaf Hash Structure: Compressed Account Hashes + +For compressed Solana accounts, the 32 byte leaf hashes effectively mirror the regular Solana account layout: `{DataHash, StateHash; Owner, Lamports`. + +The `data_hash` represents the fingerprint of the actual account data. + +The `state_hash` ensures that each account hash is globally unique. It includes + +* the public key of the state tree's respective on-chain account (i.e., `state_tree_hash`) and +* the compressed account's position in the tree (i.e., `leafIndex`). + +Lastly, `owner_hashed` determines which program owns this account and `lamports` show the account balance. + +
Here's an alt text for this image:  "Diagram showing the compressed account data structure within leaves of a Merkle tree. Four leaf nodes (Leaf 0, Leaf 1, Leaf 2, Leaf 3) are displayed at the top, each containing 'Compressed Account Hash'. Below this, the data structure is broken down into components: Datahash, Lamports, OwnerHash, Address, Discriminator, StateTreeHash, and Leaf_Index in the top row, with Data, Owner, and State Tree shown in the bottom row. Leaf 1 is highlighted in green. The diagram illustrates how compressed account data is organized within the Merkle tree structure."

Compressed Account Data Structure in Leaf of Merkle Tree.

+ +### Validity Proofs + +[Validity proofs](#user-content-fn-1)[^1] prove the existence of compressed accounts as leaves within state trees with a constant 128-byte size. These proofs are generated off-chain and verified on-chain. ZK Compression uses [Groth16](https://docs.rs/groth16-solana/latest/groth16_solana/), a well-known [pairing-based](https://en.wikipedia.org/wiki/Pairing-based_cryptography) [zk-SNARK](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana#-zk-snarks-and-circuits), for its proof system. + +{% hint style="success" %} +Developers don’t need to generate validity proofs or learn about ZK to use ZK Compression. +{% endhint %} + + + +
Merkle tree diagram illustrating a leaf proof for Leaf 1. The tree shows a purple root node at the top, branching down through intermediate nodes (Node 4 and Node 5 in green and blue respectively) to eight leaf nodes at the bottom (Leaf 0 through Leaf 7). Leaf 1 is highlighted in green and labeled as 'compressed account'. The proof path is shown with three highlighted elements in a box at the bottom right: Leaf 0, Node 1, and Node 5, representing the sibling hashes needed to verify Leaf 1's inclusion in the tree. Caption explains that a Merkle proof consists of sibling node hashes required to calculate the final root node, with only adjacent hashes along the path to the root needed.

A Merkle proof path (blue nodes) consists of all sibling node hashes required to calculate the final root node.
Only the adjacent hashes along the path to the root are needed.

+ +For those interested in learning more about the fundamentals of ZK and its applications on Solana, we recommend reading the following: + +* [Zero-Knowledge Proofs: An Introduction to the Fundamentals](https://www.helius.dev/blog/zero-knowledge-proofs-an-introduction-to-the-fundamentals) +* [Zero-Knowledge Proofs: Its Applications on Solana](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana) + +## Next Steps + +Now that you understand the core concepts of ZK Compression, here's the lifecycle of a transaction. + +{% content-ref url="lifecycle-of-a-transaction.md" %} +[lifecycle-of-a-transaction.md](lifecycle-of-a-transaction.md) +{% endcontent-ref %} + +[^1]: are succinct zero-knowledge proofs (ZKPs) diff --git a/compression-docs/learn/core-concepts/lifecycle-of-a-transaction.md b/compression-docs/learn/core-concepts/lifecycle-of-a-transaction.md index 52cff70..075730b 100644 --- a/compression-docs/learn/core-concepts/lifecycle-of-a-transaction.md +++ b/compression-docs/learn/core-concepts/lifecycle-of-a-transaction.md @@ -1,6 +1,15 @@ + # Lifecycle of a Transaction -ZK Compression transactions are fully compatible with Solana's Transaction and Versioned Transaction formats. There are three key nuances in building transactions with compressed accounts as compared to regular accounts: +*** + +{% hint style="info" %} +This guide assumes you are familiar with transactions on Solana. If you aren't, we recommend to read the [Solana documentation on transactions](https://solana.com/docs/core/transactions). +{% endhint %} + +ZK Compression transactions are fully compatible with Solana's Transaction and Versioned Transaction formats. + +There are three key nuances in building transactions with compressed accounts as compared to regular accounts: * Instructions must specify the list of all compressed accounts being read or written to. To read or write to a compressed account, the instruction must send the current account state on-chain and prove its validity * Each unique state tree that gets read or written to (via any compressed account) needs to be specified as per Solana's regular on-chain [account access lists](https://solana.com/docs/core/transactions#array-of-account-addresses) @@ -12,15 +21,15 @@ We can express a transaction more generally as: Here's what this looks like when updating a single compressed PDA account: -

Simplified: Read and Write compressed accounts

+

Simplified: Read and Write compressed accounts

-In this example, we assume that the client previously created said compressed account and thereafter fetched its compressed account info from an [RPC node](../../node-operators/run-a-node.md#photon-indexer-node) +In this example, we assume that the client previously created said compressed account and thereafter fetched its compressed account info from an [RPC node](../node-operators.md#photon-indexer-node) The custom Solana program executing the state transition _Data_ -> _Data'_ should require its client to pack the instructions efficiently. In the above scenario, the total data that's sent to the chain is: `address (same)`, `owner program (same)`, `data`, `data'-data`, `validity proof` The compressed account after its update looks like this: -

Full representation of a compressed account with PDA

+

Full representation of a compressed account with PDA

## On-chain Protocol Execution @@ -32,4 +41,8 @@ To write compressed state, a custom caller program must invoke the Light System 4. [Appends](https://github.com/Lightprotocol/light-protocol/blob/v.1.0.0/programs/system/src/invoke/processor.rs#L245-L254) the new compressed account hash to the state tree and advances the tree's state root 5. [Emits](https://github.com/Lightprotocol/light-protocol/blob/v.1.0.0/programs/system/src/invoke/processor.rs#L272-L279) the new compressed account state onto the Solana ledger -An[ RPC node](../../node-operators/run-a-node.md#photon-indexer-node) then parses the transaction and compressed state and provides the read state to clients via the [ZK Compression RPC API](../../developers/json-rpc-methods/) +An[ RPC node](../node-operators.md#photon-indexer-node) then parses the transaction and compressed state and provides the read state to clients via the [ZK Compression RPC API](../../resources/json-rpc-methods/) + +{% content-ref url="limitations.md" %} +[limitations.md](limitations.md) +{% endcontent-ref %} diff --git a/compression-docs/learn/core-concepts/limitations.md b/compression-docs/learn/core-concepts/limitations.md index 83a943f..ef380d0 100644 --- a/compression-docs/learn/core-concepts/limitations.md +++ b/compression-docs/learn/core-concepts/limitations.md @@ -1,5 +1,11 @@ + + # Limitations +*** + +## Overview + Before using ZK Compression to scale your application state, consider the following limitations of compressed accounts: * [Larger Transaction Size](limitations.md#larger-transaction-size) @@ -42,7 +48,7 @@ Higher CU usage can: * **Lead to usage limits:** The total CU limit per transaction is 1,400,000 CU, and the per-block write lock limit per State tree is 12,000,000 CU. * **Require your users to increase their** [**priority fee**](https://solana.com/developers/guides/advanced/how-to-use-priority-fees) **during congestion:** Whenever Solana's global per-block CU limit (48,000,000 CU) is reached, validator clients may prioritize transactions with higher per-CU priority fees. -## State Cost Per Transaction +## State Cost per Transaction Each write operation incurs a small additional network cost. If you expect a single compressed account to amass a large amount of state updates, the lifetime cost of the compressed account may be higher than its uncompressed equivalent, which currently has a fixed per-byte rent cost at creation. @@ -50,13 +56,22 @@ Each write operation incurs a small additional network cost. If you expect a sin Whenever a [transaction](lifecycle-of-a-transaction.md) writes to a compressed account, it nullifies the previous compressed account state and appends the new compressed account as a leaf to the state tree. Both of these actions incur costs that add to Solana's base fee. {% endhint %} -
TypeLamportsNotes
Solana base fee5000 per signatureCompensates validators for processing transactions
Write new compressed account state ~300 per leaf (default)Depends on tree depth:
\left( 2^{\text{tree\_depth}} \times \text{tree\_account\_rent\_cost} \times \text{rollover\_threshold} \right)
~300 for the default depth of 26
Nullify old compressed account state5000 per transactionReimburses the cost of running a Forester transaction. The current default Forester node implementation can be found here
Create addresses5000 per transactionSame as nullify
+
TypeLamportsNotes
Solana base fee5000 per signatureCompensates validators for processing transactions
Write new compressed account state ~300 per leaf (default)Depends on tree depth:
\left( 2^{\text{tree\_depth}} \times \text{tree\_account\_rent\_cost} \times \text{rollover\_threshold} \right)
~300 for the default depth of 26
Nullify old compressed account state5000 per transactionReimburses the cost of running a Forester transaction. The current default Forester node implementation can be found here
Create addresses5000 per transactionSame as nullify
## Next Steps -Now you're familiar with the core concepts of ZK Compression, you're ready to take the next step! Dive into [building a program](https://www.zkcompression.com/introduction/intro-to-development#on-chain-program-development) or [application](https://www.zkcompression.com/introduction/intro-to-development#client-side-development) with ZK Compression, or learn how to [set up and run your own node](../../node-operators/run-a-node.md).\ -\ -For those interested in learning more about the fundamentals of ZK and its applications on Solana, we recommend reading the following: - -* [Zero-Knowledge Proofs: An Introduction to the Fundamentals](https://www.helius.dev/blog/zero-knowledge-proofs-an-introduction-to-the-fundamentals) -* [Zero-Knowledge Proofs: Its Applications on Solana](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana) +Now you're familiar with the core concepts of ZK Compression, you're ready to take the next step! + +{% columns %} +{% column width="41.66666666666667%" %} +{% content-ref url="../../references/whitepaper.md" %} +[whitepaper.md](../../references/whitepaper.md) +{% endcontent-ref %} +{% endcolumn %} + +{% column width="58.33333333333333%" %} +{% content-ref url="broken-reference" %} +[Broken link](broken-reference) +{% endcontent-ref %} +{% endcolumn %} +{% endcolumns %} diff --git a/compression-docs/learn/core-concepts/nutshell.md b/compression-docs/learn/core-concepts/nutshell.md new file mode 100644 index 0000000..359bf05 --- /dev/null +++ b/compression-docs/learn/core-concepts/nutshell.md @@ -0,0 +1,96 @@ + + +# Core Concepts + +*** + +ZK Compression is an account primitive on Solana that drastically reduces on-chain state costs while maintaining Solana's security, composability, and performance. + +### High Level System Overview + +This is how it works at a high level: + +{% stepper %} +{% step %} +### **Storage of Compressed State** + +Compressed accounts store state as call data in [Solana's ledger](#user-content-fn-1)[^1], removing rent per account +{% endstep %} + +{% step %} +### **Accessing Compressed State** + +Transactions specify state they access (read/write) and include it in the transaction payload +{% endstep %} + +{% step %} +### **State Validation** + +Solana Programs invoke[^2] the [Light System Program](#user-content-fn-3)[^3] to update compressed state + +1. **The protocol validates the state** (validity of existing state, sum checks, ownership checks) +2. **The protocol enforces a schema:** Classic Accounts → Compressed Accounts. \ + Compressed accounts have a layout similar to classic accounts. +{% endstep %} + +{% step %} +### **State Updates** + +At the end of an instruction, the new state is recorded as a log on the Solana ledger +{% endstep %} + +{% step %} +### **Photon RPC Nodes** + +[Photon RPC nodes](#user-content-fn-4)[^4] index the state changes, making the compressed account state available to clients via the [ZK Compression RPC API](../../resources/json-rpc-methods/). +{% endstep %} + +{% step %} +### Forester Nodes + +Keeper nodes that interact with the [Account Compression Program](#user-content-fn-5)[^5] to empty queues and rollover state trees. +{% endstep %} +{% endstepper %} + +## Next Steps + +In the following sections, we'll dive into the core concepts that make ZK Compression possible: + +* **Compressed Account Model**: Understand how compressed accounts differ from regular Solana accounts (not much!) and how they enhance efficiency. +* **State Trees**: Learn about the Merkle tree structure used to store compressed accounts and how it minimizes on-chain storage. +* **Validity Proofs**: Explore how the protocol uses zero-knowledge proofs to verify data validity while keeping proof sizes small. +* **Lifecycle of a Transaction**: Follow the journey of a ZK-compressed transaction from creation to execution. +* **Limitations and Considerations**: Discover the trade-offs and scenarios where ZK Compression may or may not be the best solution. + + + +{% content-ref url="compressed-account-model.md" %} +[compressed-account-model.md](compressed-account-model.md) +{% endcontent-ref %} + +[^1]: The ledger is an immutable historical record of all Solana transactions signed by clients since the genesis block. + + + + Learn more here in this [blog post](https://www.helius.dev/blog/all-you-need-to-know-about-compression-on-solana#state-vs-ledger). + +[^2]: A Cross Program Invocation (CPI) refers to when one program invokes the instructions of another program. This allows for the composability of Solana programs.\ + \ + Learn more about CPIs [here](https://solana.com/docs/core/cpi). + +[^3]: The system program enforces the compressed account layout with ownership and sum checks and verifies the validity of your input state.\ + \ + It is also invoked to create/write to compressed accounts and PDAs.\ + \ + See the program address [here](../../resources/addresses-and-urls.md). + +[^4]: RPC nodes index the Light Protocol programs, enabling clients to read and build transactions interacting with compressed state.\ + + + The canonical ZK Compression indexer is named Photon. \ + \ + See the Github [repo](https://github.com/helius-labs/photon) for more info. + +[^5]: Implements state and address trees. It is used by the Light System program.\ + \ + See the program addresses [here](../../resources/addresses-and-urls.md). diff --git a/compression-docs/learn/core-concepts/state-trees.md b/compression-docs/learn/core-concepts/state-trees.md deleted file mode 100644 index 6b2f11a..0000000 --- a/compression-docs/learn/core-concepts/state-trees.md +++ /dev/null @@ -1,18 +0,0 @@ -# State Trees - -The protocol stores compressed state in multiple state trees - -{% hint style="info" %} -A state tree is a binary [Merkle tree](https://brilliant.org/wiki/merkle-tree/) that organizes data into a tree structure where each parent node is the hash of its two children nodes. This leads to a single unique root hash that allows for efficient cryptographic verification of the integrity of all the leaves in the tree. -{% endhint %} - -

A small binary Merkle tree (depth 2)

- -The hash of each [compressed account](compressed-account-model.md) is stored as a leaf in a state tree: - -

Each compressed account hash is a leaf in the state tree

- -Note that each compressed account hash includes the public key of the state tree's respective on-chain account (i.e., `state tree hash`) and the compressed account's position in the tree (i.e., `leafIndex`). This ensures that each account hash is globally unique. - -Each state tree has a single on-chain state tree account that stores only the tree's final root hash and other metadata. Storing the final tree root hash on-chain allows the protocol to efficiently verify the validity of any leaf (compressed account) in the tree. The raw state can thus be stored as calldata in the much cheaper Solana ledger space while preserving Solana's security guarantees. - diff --git a/compression-docs/learn/core-concepts/validity-proofs.md b/compression-docs/learn/core-concepts/validity-proofs.md deleted file mode 100644 index b737a3c..0000000 --- a/compression-docs/learn/core-concepts/validity-proofs.md +++ /dev/null @@ -1,7 +0,0 @@ -# Validity Proofs - -Validity proofs are succinct zero-knowledge proofs (ZKPs) that can prove the existence of compressed accounts as leaves within state trees while maintaining a constant 128-byte size. These proofs are generated off-chain and verified on-chain. ZK Compression uses [Groth16](https://docs.rs/groth16-solana/latest/groth16_solana/), a well-known [pairing-based](https://en.wikipedia.org/wiki/Pairing-based_cryptography) [zk-SNARK](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana#-zk-snarks-and-circuits), for its proof system. - -

A Merkle proof path (purple nodes) consists of all sibling node hashes required to calculate the final root node.

- -The protocol leverages ZKP generation and verification under the hood, so you don't have to learn about ZK directly to use ZK Compression. diff --git a/compression-docs/learn/faq-overview.md b/compression-docs/learn/faq-overview.md new file mode 100644 index 0000000..3a213db --- /dev/null +++ b/compression-docs/learn/faq-overview.md @@ -0,0 +1,137 @@ + + +# FAQ Overview + +*** + +## General FAQ + +
+ +Do I need to maintain Merkle Trees or understand ZK to start building? + +**No**, the protocol provides the full infrastructure. Developers just build. + +* The protocol maintains Merkle trees + +- Validity proofs are fetched with RPC calls from indexers that support the [ZK Compression RPC API](../resources/json-rpc-methods/). + +> _Learn more in our_ [_Core Concepts Section_](core-concepts/). + +
+ +
+ +How does ZK Compression store data securely? + +1. **State roots** are committed **on-chain** to ensure the same security guarantees as when data is stored in regular accounts. + +2) The **raw account data** is stored on the [**Solana ledger**](#user-content-fn-1)[^1] as call data – the compressed account state is fetched with each transaction from supporting RPC providers. + +3. **Validity proofs** prevent invalid state transitions. + +> _Learn more in our_ [_Core Concepts Section_](core-concepts/). + +
+ +
+ +Is ZK Compression a Layer 2? + +**No –** ZK Compression offers + +* direct and atomic transaction **execution** **on Solana's L1** with instant finality, +* full **composability** between compressed and regular state, +* Solana's **security** guarantees, and +* no batching of transactions, bridging or liquidity fragmentation. + +
+ +
+ +What are limitations and trade-offs? + +**Most applications benefit** from compression **despite** these trade-offs: + +* **Transactions are larger in size** due to + * a 128-byte proof to verify compressed account validity and + * account data inclusion. +* **Transactions require more compute units** for on-chain hashing and proof verification - we recommend to increase priority fee during congestion + +- Interactions with compressed accounts add **marginal per-transaction fees** to Solana's base fees (<10,000 lamports). + +* If a compressed account will be **updated more than 1,000 times** in its lifetime,\ + cumulative transaction costs **exceed one-time rent cost**. + +> _Learn more in our_ [_Limitations section_](core-concepts/limitations.md)_._ + +
+ +## Compressed Tokens FAQ + +
+ +Who are compressed tokens for? + +Compressed tokens can optimize applications needing to mint or distribute many tokens for: + +* **Reward programs** with **periodic** **claims**, +* **Airdrops** with one transaction per recipient, +* User wallets or accounts with **infrequent updates or transfers**, ... + +Regular SPL Tokens remain more efficient for high frequency trading and liquidity pools due to larger transaction size and compute unit consumption for state updates. + +
+ +
+ +How do SPL and Compressed Tokens compare? + +
SPL TokensCompressed Tokens
Rent CostYesNo
Creation Cost
per token account
0.002 SOL0.0000004 SOL
FunctionalityAll SPL operations
  • All SPL operations
  • Convert between formats atomically in one transaction
Wallet SupportYesYes, with Phantom and Backpack
+ +
+ +## Compressed PDA FAQ + +
+ +Who are compressed PDAs for? + +Compressed PDAs can optimize applications with + +* user specific state +* accounts with infrequent updates + +Regular PDAs remain more efficient for shared state and PDAs with high frequency updates, due to larger transaction size and compute unit consumption. + +
+ +
+ +How do regular and Compressed PDAs compare? + +| | Regular PDA | Compressed PDA | +| ----------------------------------------- | ---------------------- | ---------------------------------------------------------- | +| **Rent Cost** | Yes | No | +| **Creation Cost 100 byte account at PDA** | 0.0016 SOL | \~ 0.00001 SOL | +| **Functionality** | Full PDA operations |

Full PDA operations via Light system program
CPI

| +| **Implementation** | Standard Anchor | Light SDK | +| **Read Access** | Direct in program | Via RPC Calls | +| **Write Access** | Direct in program | Via Light System CPI | +| **Seed Derivation** | `findProgramAddress()` |

derive_address() with seed
inputs

| + +
+ +*** + +## Next Steps + +
Cover imageCover image (dark)

Start Learning

Learn ZK Compression's core concepts.Light Protocol v2 - Batched Merkle trees-38.pngcore-conceptsLight Protocol v2 - Batched Merkle trees-70.png

Whitepaper

Dive deeper in the Lightpaper.

Get Support

Join our Discord for support and discussions.
+ + + +[^1]: **The ledger is an immutable historical record of all Solana transactions signed by clients since the genesis block.** + + + + _**Learn more on the Solana Ledger in this**_ [_**blog post from Helius**_](https://www.helius.dev/blog/all-you-need-to-know-about-compression-on-solana#state-vs-ledger)_**.**_ diff --git a/compression-docs/learn/in-a-nutshell.md b/compression-docs/learn/in-a-nutshell.md deleted file mode 100644 index 0ea56d3..0000000 --- a/compression-docs/learn/in-a-nutshell.md +++ /dev/null @@ -1,13 +0,0 @@ -# In a Nutshell - -## High-level System Overview - -1. **State is stored as call data in** [**Solana's ledger**](https://www.helius.dev/blog/all-you-need-to-know-about-compression-on-solana#state-vs-ledger)**,** resulting in very low rent cost -2. **Transactions specify the state they access** (read/write) and include it in the transaction payload -3. **State Validation:** - 1. Solana Programs [invoke](https://solana.com/docs/core/cpi) the [Light Protocol System Program](../developers/addresses-and-urls.md#program-ids-and-accounts-from-27th-aug-2024-onward) to update compressed state - 1. **The protocol** [**validates**](core-concepts/validity-proofs.md) **the state** (validity of existing state, sum checks, ownership checks) - 2. **The protocol enforces a schema:** Classic Accounts → Compressed Accounts. [Compressed accounts](core-concepts/compressed-account-model.md) have a layout similar to classic accounts -4. **State Updates:** At the end of an instruction, the new state is recorded as a log on the Solana ledger -5. [**Photon RPC Nodes**](https://www.zkcompression.com/node-operators/run-a-node#photon-rpc-node)**:** These nodes index the state changes, making the compressed account state available to clients via the [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-methods) -6. **Forester Nodes (Keeper Nodes)**: Interact with the [Account Compression Program](../developers/addresses-and-urls.md#program-ids-and-accounts-from-27th-aug-2024-onward) to empty queues and rollover state trees. diff --git a/compression-docs/node-operators/run-a-node.md b/compression-docs/learn/node-operators.md similarity index 54% rename from compression-docs/node-operators/run-a-node.md rename to compression-docs/learn/node-operators.md index b296628..eab584d 100644 --- a/compression-docs/node-operators/run-a-node.md +++ b/compression-docs/learn/node-operators.md @@ -1,23 +1,21 @@ -# Run a Node -## Running a Node for Local Development -For local development, the [ZK Compression CLI](https://github.com/Lightprotocol/light-protocol/blob/main/cli/) `test-validator` command starts a single-node Solana cluster with all relevant nodes (Photon RPC and Prover), system programs, accounts, and runtime features: +# Node Operators -```sh -light test-validator -``` +*** + +{% hint style="success" %} +Developers don't need to run any nodes to start building with ZK Compression. +{% endhint %} To connect to public networks (i.e., Devnet, Mainnet-Beta), you can either work with an RPC infrastructure provider that supports ZK Compression, such as [Helius Labs](https://helius.xyz/), or run your own nodes: {% hint style="info" %} There are three different types of nodes: -* [Photon RPC nodes](run-a-node.md#photon-rpc-node) -* [Prover nodes](run-a-node.md#prover-node) -* [Light forester nodes](run-a-node.md#forester-node) - -_Becoming an operator for any node type is permissionless_ +* Photon RPC nodes +* Prover nodes +* Light forester nodes {% endhint %} ## Photon RPC Node @@ -32,14 +30,14 @@ The canonical ZK Compression indexer is named Photon. It can be run locally and Provers generate validity proofs for state inclusion on behalf of app developers -**Prover nodes** can be operated either stand-alone or with an RPC node: in its default configuration, the Photon RPC node implementation by [Helius Labs](https://github.com/helius-labs/photon) bundles a Prover node. The [ZK Compression RPC API ](https://docs.lightprotocol.com/developers/json-rpc-methods)specification supports proof generation via the `getValidityProof` endpoint, making it easy to serve proofs using regular RPC methods via the same port. +**Prover nodes** can be operated either stand-alone or with an RPC node: in its default configuration, the Photon RPC node implementation by [Helius Labs](https://github.com/helius-labs/photon) bundles a Prover node. The [ZK Compression RPC API ](../resources/json-rpc-methods/)specification supports proof generation via the `getValidityProof` endpoint, making it easy to serve proofs using regular RPC methods via the same port. Please refer to the Github repo for more info: {% embed url="https://github.com/Lightprotocol/light-protocol/tree/main/light-prover" %} -## Light Forester Node +## Forester Node -Developers may choose to have their program-owned state trees serviced by a network of Light Forester nodes. These nodes manage the creation, rollover, and updating of shared and program-owned state trees. +These nodes manage the creation, rollover, and updating of shared and program-owned state trees. {% embed url="https://github.com/Lightprotocol/light-protocol/tree/main/forester" %} diff --git a/compression-docs/quickstart.md b/compression-docs/quickstart.md new file mode 100644 index 0000000..741b2fb --- /dev/null +++ b/compression-docs/quickstart.md @@ -0,0 +1,188 @@ + + +# Quickstart + +*** + +## Create your first compressed tokens + +With this guide you will mint compressed tokens in under 5 minutes. + +{% stepper %} +{% step %} +#### **Install Dependencies** + +{% tabs %} +{% tab title="npm" %} +```bash +npm install --save-dev typescript tsx @types/node && +npm install \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} + +{% tab title="yarn" %} +```bash +yarn add --dev typescript tsx @types/node && +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} + +{% tab title="pnpm" %} +```bash +pnpm add --save-dev typescript tsx @types/node && +pnpm add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @solana/spl-token +``` +{% endtab %} +{% endtabs %} +{% endstep %} + +{% step %} +#### **Create an RPC connection** + +Run `test-connection.ts` to verify your setup: + +{% hint style="success" %} +Replace `` with your actual API key before running! Get one [here](https://www.helius.dev/zk-compression), if you don't have one yet. +{% endhint %} + +
import { createRpc } from "@lightprotocol/stateless.js";
+
+// Helius exposes Solana and Photon RPC endpoints through a single URL
+const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<api_key>";
+const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT);
+
+console.log("Connection created");
+console.log("RPC Endpoint:", RPC_ENDPOINT);
+
+{% endstep %} + +{% step %} +#### **Create and Mint Compressed Tokens** + +
+ +Generate your keypair, if you don't have one yet. + +1. Install Solana + +```bash +sh -c "$(curl -sSfL https://release.anza.xyz/v3.0.0/install)" +``` + +2. Then run the command below to create a keypair at `.config/solana/id.json` + +```bash +solana-keygen new +``` + +3. fund wallet with devnet SOL + +```bash +# Check current balance +solana balance --url devnet + +# Airdrop 1 SOL to your default wallet +solana airdrop 1 --url devnet + +# or use https://faucet.solana.com/ +``` + +
+ +Run `quickstart.ts` with the following code: + +
// ZK Compression Quickstart - DevNet
+// 1. Load wallet and connect to DevNet via Helius RPC
+// 2. Create SPL mint with token pool for compression via createMint()
+// 3. Mint compressed tokens to recipient account via mintTo() 
+// 4. Verify compressed token balance via getCompressedTokenAccountsByOwner
+
+import { createRpc } from "@lightprotocol/stateless.js";
+import { createMint, mintTo } from "@lightprotocol/compressed-token";
+import { Keypair } from "@solana/web3.js";
+import { readFileSync } from "fs";
+import { homedir } from "os";
+
+// Step 1: Load wallet from filesystem
+const payer = Keypair.fromSecretKey(new Uint8Array(JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))));
+
+// Helius exposes Solana and compression RPC endpoints through a single URL
+const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<API_KEY>";
+const connection = createRpc(RPC_ENDPOINT, RPC_ENDPOINT, RPC_ENDPOINT);
+
+const main = async () => {
+
+  try {
+    // Step 2: Create SPL mint with token pool for compression
+    console.log("\nCreating SPL mint with token pool for compression");
+    const { mint, transactionSignature } = await createMint(
+      connection,
+      payer,
+      payer.publicKey, // mintAuthority
+      9
+    );
+
+    console.log(`Mint address: ${mint.toBase58()}`);
+    console.log(`Create mint transaction: https://explorer.solana.com/tx/${transactionSignature}?cluster=devnet`);
+
+    // Step 3: Mint compressed tokens to recipient account
+    console.log("\nMinting compressed token...");
+    const mintAmount = 1000000000; // mintAmount with decimals   
+    const mintToTxId = await mintTo(
+      connection,
+      payer,
+      mint, // SPL mint with token pool for compression
+      payer.publicKey, // recipient.publicKey
+      payer, // mintAuthority
+      mintAmount
+    );
+
+    console.log(`Compressed Token Balance ${mintAmount / 1e9} token`);
+    console.log(`Mint tokens transaction: https://explorer.solana.com/tx/${mintToTxId}?cluster=devnet`);
+
+    // Step 4: Verify compressed token balance via getCompressedTokenAccountsByOwner
+    const tokenAccounts = await connection.getCompressedTokenAccountsByOwner(
+      payer.publicKey,
+      { mint } // SPL mint with token pool for compression
+    );
+
+  } catch (error: any) {
+    console.error("Error:", error.message);
+  }
+};
+
+main().catch(console.error);
+
+{% endstep %} + +{% step %} +#### **Success!** + +That's it - you've created and minted compressed tokens. The output shows: + +* Address of SPL mint with token pool for compression +* Compressed Token balance +{% endstep %} +{% endstepper %} + +*** + +## Next Steps + +Get an overview of compressed tokens and dive right into the cookbook. + +{% content-ref url="broken-reference" %} +[Broken link](broken-reference) +{% endcontent-ref %} diff --git a/compression-docs/resources/addresses-and-urls.md b/compression-docs/resources/addresses-and-urls.md new file mode 100644 index 0000000..c21c975 --- /dev/null +++ b/compression-docs/resources/addresses-and-urls.md @@ -0,0 +1,86 @@ +--- +description: >- + Quick access to all of ZK Compression's RPC URLs, Program IDs & Accounts and + Lookup Tables. +--- + +# Addresses & URLs + +*** + +### RPC URLs + +{% tabs %} +{% tab title="Mainnet" %} +
NetworkServiceURL
MainnetNetwork Address (RPC)https://mainnet.helius-rpc.com?api-key=<api_key>
MainnetPhoton RPC APIhttps://mainnet.helius-rpc.com?api-key=<api_key>
+{% endtab %} + +{% tab title="Devnet" %} +
NetworkServiceURL
DevnetNetwork Address (RPC)https://devnet.helius-rpc.com?api-key=<api_key>
DevnetPhoton RPC APIhttps://devnet.helius-rpc.com?api-key=<api_key>
+{% endtab %} +{% endtabs %} + +{% hint style="info" %} +Find all JSON RPC Methods for ZK Compression [here](json-rpc-methods/). +{% endhint %} + +### Program IDs + +
Light System ProgramSySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7
Compressed Token ProgramcTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m
Account Compression Programcompr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq
+ +### State Trees, Queues and Token Escrow PDA + +
Public State Tree #1smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT
Public Nullifier Queue #1nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148
Public Address Tree #1amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2
Public Address Queue #1aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F
Token Escrow Owner PDAGXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy
+ +### Lookup Tables + +{% hint style="info" %} +[Lookup tables](https://solana.com/docs/advanced/lookup-tables) reduce your transaction size. We provide pre-initialized lookup tables that cover the Light's program IDs and accounts: +{% endhint %} + +
Lookup Table #1 (Mainnet)9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ
Lookup Table #1 (Devnet)qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V
+ +We provide a helper function below if you need to extend a custom lookup table. + +
+ +ExtendLookUpTables.ts + +```typescript +import { Rpc, confirmTx, createRpc } from "@lightprotocol/stateless.js"; +import { createTokenProgramLookupTable } from "@lightprotocol/compressed-token"; +import { Keypair, PublicKey} from "@solana/web3.js"; +import { RPC_ENDPOINT } from "./constants"; +const payer = Keypair.generate(); +const authority = payer; +const additionalTokenMints : PublicKey[] = []; +const additionalAccounts : PublicKey[] = []; + +// Localnet +const connection: Rpc = createRpc(); + +const main = async () => { + /// airdrop lamports to pay gas and rent + await confirmTx( + connection, + await connection.requestAirdrop(payer.publicKey, 1e7) + ); + + /// Create LUT + const { address } = await createTokenProgramLookupTable( + connection, + payer, + authority, + additionalTokenMints, + additionalAccounts + ); + + console.log("Created lookup table:", address.toBase58()); +}; + +main(); +``` + + + +
diff --git a/compression-docs/resources/changelog/0.3.0.md b/compression-docs/resources/changelog/0.3.0.md deleted file mode 100644 index d06d308..0000000 --- a/compression-docs/resources/changelog/0.3.0.md +++ /dev/null @@ -1,31 +0,0 @@ -# 0.3.0 - -The first pre-release is here! - -{% hint style="info" %} -_Note: It’s an alpha release, so bugs & breaking changes can occur. If you have questions, bugs, or feedback, check out the_ [_Helius_](https://discord.gg/Uzzf6a7zKr) _and_ [_Light_](https://discord.gg/CYvjBgzRFP) _Developer Discord for help, or message us directly via_ [_Telegram_](https://t.me/swen\_light) -{% endhint %} - -**You can find a list of all crates here:** - -{% embed url="https://github.com/Lightprotocol/light-protocol/releases/tag/light-v0.3.0" %} - -## Tooling - -* [Local Development + Token CLI](https://github.com/Lightprotocol/light-protocol/tree/main/cli) -* [ZK Compression RPC](https://github.com/helius-labs/photon) - -## Active Feature Set - -* **Compressed accounts** (no upfront rent) -* **Compressed-token program**. Create + mint (tested for up to 25x token mint batch, \~5000x cheaper than SPL-token mints) -* **Compress/decompress** SOL and tokens -* Rust-SDK -* Ts-sdk - -## Upcoming Changes - -* Support unique PDA address in typescript client -* End-to-end program example with a typescript anchor client -* Public Testnet -* Program-owned state tree support diff --git a/compression-docs/resources/changelog/README.md b/compression-docs/resources/changelog/README.md deleted file mode 100644 index 4dc68c6..0000000 --- a/compression-docs/resources/changelog/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Changelog - diff --git a/compression-docs/resources/cli-installation.md b/compression-docs/resources/cli-installation.md new file mode 100644 index 0000000..16f8c47 --- /dev/null +++ b/compression-docs/resources/cli-installation.md @@ -0,0 +1,236 @@ +--- +description: >- + This section covers the steps to set up your local environment for ZK + Compression development. CLI to interact with compressed accounts and + compressed tokens on Solana. +--- + +# CLI Installation + +{% hint style="info" %} +Make sure you have a wallet set up at `~/.config/solana/id.json`. [Get one here](https://docs.solanalabs.com/cli/wallets/file-system), if you don't have one. \ +The CLI will use this wallet as the default fee payer and mint authority. +{% endhint %} + +## Installation + +{% stepper %} +{% step %} +#### Install the ZK Compression CLI + +{% hint style="info" %} +Ensure you have Node >= v20.9.0 installed on your machine. Windows users do not require WSL. +{% endhint %} + +Run this single command to install the ZK Compression CLI. + +```bash +npm install -g @lightprotocol/zk-compression-cli +``` + +
+ +Building from source + +If you prefer to build the CLI from source, follow the steps below to install the necessary prerequisites. + +**1. Activate the Development Environment** + +Ensure you are at the root of the monorepo. + +```bash +. ./scripts/devenv +``` + +**2. Install and build the monorepo from source. This also builds the CLI.** + +```bash +./scripts/install.sh +``` + +```bash +./scripts/build.sh +``` + +**3. Make your CLI available globally** + +```bash +pnpm link --global +``` + +```bash +# Verify the CLI was correctly installed +which light +``` + +
+{% endstep %} + +{% step %} +#### Set Up Your Environment + +By default, the CLI interacts with localnet. You can view the current config by running: + +```bash +light config --get +``` + +**1. Once globally installed, start the Light test validator** + +```bash +light test-validator +``` + +This starts a Solana test-validator with the Light System programs and accounts, a prover server, and the Photon indexer as background processes against a clean ledger. + +```bash +# Pass --skip-indexer to start without the indexer +light test-validator --skip-indexer + +# Pass --skip-prover to start without the prover +light test-validator --skip-prover + +``` + +> **Note:** The CLI currently runs the photon indexer and light-prover as background processes at port: `8784` and `3001` respectively. + +**2. Ensure you have sufficient localnet funds** + +```bash +# Airdrop 1 SOL +solana airdrop 1 + +# Print your address +solana address + +# Print your balance +solana balance +``` + +Now you're all set up to run CLI commands! + +
+ +Alternative: Using Devnet + +To switch to Devnet, point the URLs to an RPC supporting ZK Compression. For example, run: + +```bash + light config --indexerUrl "https://devnet.helius-rpc.com/?api-key=" \ + --proverUrl "https://devnet.helius-rpc.com/?api-key=" \ + --solanaRpcUrl "https://devnet.helius-rpc.com/?api-key=" +``` + +Also adjust your solana config: + +```bash +# Set config +solana config set --url "https://devnet.helius-rpc.com/?api-key=" + +# Airdrop 1 SOL +solana airdrop 1 + +# Print your address +solana address +``` + +
+{% endstep %} +{% endstepper %} + +## Commands + +#### **Create a compressed token mint** + +```bash +light create-mint +``` + +``` +USAGE + $ light create-mint [--mint-keypair ] [--mint-authority ] + [--mint-decimals ] + +FLAGS + --mint-authority= Path to the mint authority keypair file. + Defaults to default local Solana wallet file + path. + --mint-decimals= Number of base 10 digits to the right + of the decimal place [default: 9]. + --mint-keypair= Path to a mint keypair file. Defaults to a + random keypair. +``` + +#### **Mint compressed tokens to a Solana wallet** + +```bash +light mint-to --mint "YOUR_MINT_ADDRESS" --to "YOUR_WALLET_ADDRESS" --amount 4200000000 +``` + +``` +USAGE + $ light mint-to --mint --to --amount + [--mint-authority ] + +FLAGS + --amount= (required) Amount to mint. + --mint= (required) Mint address. + --mint-authority= File path of the mint authority keypair. + Defaults to local Solana wallet. + --to= (required) Recipient address. +``` + +#### **Transfer compressed tokens from one wallet to another** + +```bash +light transfer --mint "YOUR_MINT_ADDRESS" --to "RECIPIENT_WALLET_ADDRESS" --amount 4200000000 +``` + +``` +USAGE + $ light transfer --mint --to --amount + [--fee-payer ] + +FLAGS + --amount= (required) Amount to send. + --fee-payer= Fee payer account. Defaults to the client + keypair. + --mint= (required) Mint to transfer + --to= (required) Recipient address + +``` + +#### **Assign native SOL to a compressed account** + +```bash +light compress-sol --amount 1000 --to "YOUR_WALLET_ADDRESS_BASE58" +``` + +``` +USAGE + $ light compress-sol --to --amount + +FLAGS + --amount= (required) Amount to compress in lamports. + --to= (required) Specify the recipient address. +``` + +#### **Decompress into native SOL** + +```bash +light decompress-sol --amount 42 --to "YOUR_WALLET_ADDRESS_BASE58" +``` + +``` +USAGE + $ light decompress-sol --to --amount + +FLAGS + --amount= (required) Amount to decompress in lamports. + --to= (required) Specify the recipient address. +``` + +### Support + +* Always feel free to join the [Developer Discord](https://discord.gg/D2cEphnvcY) for help! +* For more info about the canonical indexer implementation built and maintained by Helius Labs, refer to the [Photon codebase](https://github.com/helius-labs/photon). diff --git a/compression-docs/resources/json-rpc-methods/getcompressedaccount.md b/compression-docs/resources/json-rpc-methods/getcompressedaccount.md new file mode 100644 index 0000000..166cab0 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedaccount.md @@ -0,0 +1,232 @@ +--- +description: >- + Returns the compressed account with the given address or hash. Overview to + method, use cases, examples and developer tips. +--- + +# getCompressedAccount + +The `getCompressedAccount` RPC method retrieves information about a specific compressed account using either its address or hash. Information includes the compressed account state, balance, and metadata. + +{% hint style="info" %} +You can test this methods via the OpenAPI example or [custom examples below.](getcompressedaccount.md#examples) +{% endhint %} + +{% openapi-operation spec="light-protocol-api" path="/getCompressedAccount" method="post" %} +[OpenAPI light-protocol-api](https://raw.githubusercontent.com/helius-labs/photon/main/src/openapi/specs/getCompressedAccount.yaml) +{% endopenapi-operation %} + +### Common Use Cases + +* **Checking Compressed Account Balance:** Retrieve the lamport balance of any compressed account. +* **Verifying Account Existence:** Check if a compressed account exists and has been initialized with data or lamports. +* **Inspecting Compressed Account Data:** Access the stored data within a compressed account, including program-specific state. +* **Identifying Account Owner:** Determine which program owns a compressed account to understand how its data should be interpreted. +* **Merkle Tree Information:** Get the merkle tree context including leaf index and sequence number for proof generation. + +### Parameters + +1. `address` (string, optional): The base-58 encoded address of the compressed account to query. +2. `hash` (string, optional): The base-58 encoded 32-byte hash of the compressed account. + +**Note:** Either `address` OR `hash` must be provided, but not both. If neither is provided, the request will fail. + +### Response + +Returns compressed account information directly, or null if account not found: + +* `hash` (string): The 32-byte hash of the compressed account as a base-58 string. +* `owner` (string): The base-58 encoded public key of the program that owns this account. +* `lamports` (number): The number of lamports owned by the account. +* `tree` (string): The public key of the merkle tree storing this account. +* `leafIndex` (number): The leaf index of this account in the merkle tree. +* `seq` (number): The sequence number of this account. +* `slotCreated` (number): The slot when this account was created. +* `data` (object, optional): The account data object containing: + * `discriminator` (BN): The account discriminator as BN object. + * `data` (string): Base64 encoded account data. + * `dataHash` (string): The 32-byte hash of the account data. + +### Developer Tips + +* **Address vs Hash**: Use `address` when you know the account's derived address, and `hash` when you have the specific hash from a previous query or transaction +* **Hash Format**: When using the `hash` parameter, pass the hash value directly as returned from other RPC methods (like `getCompressedAccountsByOwner`). Hash values must be used immediately after retrieval for accurate results +* **Error Handling**: Always check if the returned value is `null` before accessing account properties, as the account may not exist +* **Merkle Context**: The returned `leafIndex`, `tree`, and `seq` fields are crucial for generating merkle proofs when updating or transferring compressed accounts +* **Data Interpretation**: The `data` field contains program-specific information. Use the `discriminator` to identify the account type and parse the `data` accordingly +* **Rate Limits**: Be mindful of RPC rate limits when making frequent queries. Consider batching requests when possible +* **Hash Verification**: The `dataHash` field allows you to verify the integrity of the account data without storing the full data on-chain + +### Examples + +The below examples work - just make sure you installed the dependencies. + +
+ +Dependencies & Setup + +```bash +npm install @lightprotocol/stateless.js @solana/web3.js +``` + +**For Rust examples**: Create a Cargo project with these dependencies in `Cargo.toml`: + +```toml +[dependencies] +tokio = { version = "1.0", features = ["full"] } +anyhow = "1.0" +solana-sdk = "1.18" +# light-client = "0.1" # Add when available +serde_json = "1.0" +``` + +
+ +#### Example: Fetching Compressed Account by Address + +Let's fetch information for a compressed account using its address. + +{% tabs %} +{% tab title="cURL" %} +```bash +curl -X POST https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getCompressedAccount", + "params": { + "address": "11111113R2cuenjG5nFubqX9Wzuukdin2YfGQVzu5" + } + }' +``` +{% endtab %} + +{% tab title="TypeScript" %} +```typescript +import { Rpc, createRpc, createBN254, BN254, CompressedAccountWithMerkleContext } from '@lightprotocol/stateless.js'; + +async function getCompressedAccountInfo(): Promise { + const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' + ); + + try { + // Query by address + const address: BN254 = createBN254('11111113R2cuenjG5nFubqX9Wzuukdin2YfGQVzu5', 'base58'); + const accountInfo: CompressedAccountWithMerkleContext | null = + await rpc.getCompressedAccount(address); + + if (!accountInfo) { + console.log('Compressed account not found.'); + return; + } + + console.log('Compressed Account Info:', { + hash: accountInfo.hash, + owner: accountInfo.owner, + lamports: accountInfo.lamports, + tree: accountInfo.tree, + leafIndex: accountInfo.leafIndex, + sequence: accountInfo.seq, + slotCreated: accountInfo.slotCreated, + dataHash: accountInfo.data?.dataHash, + }); + } catch (error: unknown) { + console.error('Error fetching compressed account:', error); + } +} + +getCompressedAccountInfo(); +``` +{% endtab %} + +{% tab title="Rust" %} +{% hint style="info" %} +**Rust Client**: `light-client` v0.14.0 is available on crates.io. Use `LightClient` for the current stable API. +{% endhint %} + +```rust +// Current API: light-client 0.14.0 +use light_client::rpc::LightClient; +use solana_sdk::bs58; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?; + + // Query compressed account by address + let address = "11111113R2cuenjG5nFubqX9Wzuukdin2YfGQVzu5"; + + match client.get_compressed_account(Some(address), None).await? { + Some(account_info) => { + println!("Compressed Account Info:"); + println!(" Hash: {}", account_info.hash); + println!(" Owner: {}", account_info.owner); + println!(" Lamports: {}", account_info.lamports); + println!(" Tree: {}", account_info.tree); + println!(" Leaf Index: {}", account_info.leaf_index); + } + None => { + println!("Compressed account not found."); + } + } + + Ok(()) +} +``` +{% endtab %} +{% endtabs %} + +#### Example: Fetching Compressed Account by Hash + +You can also query a compressed account using its hash. + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { Rpc, createRpc, bn } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +async function getCompressedAccountByHash(): Promise { + const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' + ); + + try { + // First, get compressed accounts to obtain a real hash + const owner = new PublicKey('4LhEEtzAhM6wEXJR2YQHPEs79UEx8e6HncmeHbqbW1w1'); + const accounts = await rpc.getCompressedAccountsByOwner(owner); + + if (accounts.items.length === 0) { + console.log('No compressed accounts found'); + return; + } + + // Query by hash using a real hash from the first account + const hash = bn(accounts.items[0].hash); + const accountInfo = await rpc.getCompressedAccount(undefined, hash); + + if (accountInfo) { + console.log('Compressed Account Info:', { + hash: accountInfo.hash, + owner: accountInfo.owner, + lamports: accountInfo.lamports, + tree: accountInfo.tree, + leafIndex: accountInfo.leafIndex + }); + } else { + console.log('Compressed account not found.'); + } + } catch (error: unknown) { + console.error('Error:', error); + } +} + +getCompressedAccountByHash(); +``` +{% endtab %} +{% endtabs %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedaccountsbyowner.md b/compression-docs/resources/json-rpc-methods/getcompressedaccountsbyowner.md new file mode 100644 index 0000000..b1f0c8a --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedaccountsbyowner.md @@ -0,0 +1,287 @@ +--- +description: >- + Returns the owner’s compressed accounts. Overview to method, use cases, + examples and developer tips. +--- + +# getCompressedAccountsByOwner + +The`getCompressedAccountByOwner` RPC method returns all compressed accounts owned by a specific address, with support for filtering, pagination, and data slicing. + +{% hint style="info" %} +You can test this method via the OpenAPI example or [custom examples below](getcompressedaccountsbyowner.md#examples). +{% endhint %} + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getCompressedAccountsByOwner" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} + +### Common Use Cases + +* **Portfolio Discovery**: Find all compressed accounts for a wallet +* **Token Account Enumeration**: Discover user's compressed token holdings +* **Account Migration**: Identify accounts to migrate from regular to compressed +* **Balance Aggregation**: Calculate total holdings across all accounts + +### Parameters + +1. `owner` (PublicKey, required): Base58-encoded public key of the account owner to query compressed accounts for. +2. `options` (object, optional): Configuration object for filtering and pagination: + * `filters` (array, optional): Array of filter objects to narrow results by specific criteria + * `dataSlice` (object, optional): Slice of account data to return with `offset` and `length` fields + * `cursor` (string, optional): Cursor for pagination from previous response to fetch next page + * `limit` (BN, optional): Maximum number of accounts to return (use `bn()` helper) + +**Note**: All options parameters are optional. Without filters, returns all compressed accounts for the owner. + +### Response + +The response contains a paginated list of compressed accounts: + +* `items` (array): Array of compressed account objects with merkle context + * `hash` (string): Unique hash identifying the account for merkle proof generation + * `address` (string, optional): Account address if available + * `lamports` (number): Account balance in lamports + * `owner` (string): Public key of the account owner + * `data` (object): Account data information including discriminator and data hash + * `tree` (string): Public key of the merkle tree storing this account + * `leafIndex` (number): Position of account in the merkle tree + * `seq` (number): Sequence number for account ordering + * `slotCreated` (number): Slot when account was created +* `cursor` (string | null): Pagination cursor for next batch, null if no more results + +### Developer Tips + +* **Pagination Strategy**: Use cursor-based pagination for owners with many accounts to avoid timeouts and ensure consistent results +* **Data Slicing Optimization**: Implement data slicing when you only need account metadata to reduce response size and improve performance +* **Empty Response Handling**: Handle cases gracefully where new addresses have no compressed accounts - this is normal behavior +* **Caching Considerations**: Cache results appropriately as compressed account states can change with each transaction +* **Batch Size**: Start with smaller batch sizes (50-100) and adjust based on response times and data needs + +### Troubleshooting + +
+ +"No accounts found" + +**Owner has no compressed accounts** + +This is normal for new addresses or those that haven't used compression: + +```typescript +const accounts = await rpc.getCompressedAccountsByOwner(owner); + +if (accounts.items.length === 0) { + console.log("No compressed accounts found for this owner"); + console.log("Create compressed accounts first using createAccountWithLamports or token operations"); +} +``` + +
+ +
+ +"Request timeout with large responses" + +**Too many accounts returned at once** + +Use pagination and data slicing to reduce response size: + +```typescript +const accounts = await rpc.getCompressedAccountsByOwner(owner, { + limit: bn(50), // Smaller batch size + dataSlice: { // Reduce data per account + offset: 0, + length: 10 + } +}); +``` + +
+ +### Examples + +The below examples work - just make sure you installed the dependencies. + +
+ +Dependencies & Setup + +```bash +npm install @lightprotocol/stateless.js @solana/web3.js +``` + +**For Rust examples**: Requires `light-client`, `solana-sdk`, `anyhow`, and `tokio` crates. See Rust example comments for setup details. + +
+ +#### Example: Get All Compressed Accounts + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function getAllCompressedAccounts() { + const owner = new PublicKey('OWNER_ADDRESS_HERE'); + const result = await rpc.getCompressedAccountsByOwner(owner); + + console.log(`Found ${result.items.length} compressed accounts`); + + result.items.forEach((account, index) => { + console.log(`Account ${index + 1}:`); + console.log(` Hash: ${account.hash.toString()}`); + console.log(` Lamports: ${account.lamports.toString()}`); + console.log(` Owner: ${account.owner.toBase58()}`); + }); + + return result; +} + +getAllCompressedAccounts(); +``` +{% endtab %} + +{% tab title="cURL" %} +```bash +curl -X POST https://devnet.helius-rpc.com?api-key=YOUR_API_KEY \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getCompressedAccountsByOwner", + "params": { + "owner": "OWNER_PUBKEY_HERE" + } + }' +``` +{% endtab %} + +{% tab title="Rust" %} +{% hint style="info" %} +**Rust Client**: `light-client` v0.14.0 is available on crates.io. Use `LightClient` for the current stable API. +{% endhint %} + +```rust +// Current API: light-client 0.14.0 +use light_client::rpc::LightClient; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?; + + // Query compressed accounts by owner + let owner = Pubkey::from_str("OWNER_PUBKEY_HERE")?; + + match client.get_compressed_accounts_by_owner(&owner, None).await? { + response => { + println!("Found {} compressed accounts", response.value.items.len()); + + for (index, account) in response.value.items.iter().enumerate() { + println!("Account {}:", index + 1); + println!(" Hash: {}", account.hash); + println!(" Lamports: {}", account.lamports); + println!(" Owner: {}", account.owner); + println!(" Tree: {}", account.tree); + println!(" Leaf Index: {}", account.leaf_index); + } + } + } + + Ok(()) +} +``` +{% endtab %} +{% endtabs %} + +#### Example: Paginated Account Discovery with Balance Aggregation + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, bn, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function getAllAccountsPaginated(owner: PublicKey) { + let allAccounts = []; + let cursor = undefined; + let totalBalance = bn(0); + const batchSize = 100; + + do { + const batch = await rpc.getCompressedAccountsByOwner(owner, { + cursor, + limit: bn(batchSize) + }); + + allAccounts.push(...batch.items); + cursor = batch.cursor; + + // Calculate running balance + batch.items.forEach(account => { + totalBalance = totalBalance.add(account.lamports); + }); + + console.log(`Fetched ${batch.items.length} accounts, total: ${allAccounts.length}`); + + // Rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + + } while (cursor); + + console.log(`Total compressed accounts: ${allAccounts.length}`); + console.log(`Total balance: ${totalBalance.toString()} lamports`); + return { accounts: allAccounts, totalBalance }; +} + +// Usage +const owner = new PublicKey('OWNER_ADDRESS_HERE'); +getAllAccountsPaginated(owner); +``` +{% endtab %} +{% endtabs %} + +#### Example: Filter by Data Slice + +```typescript +import { createRpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc = createRpc("https://devnet.helius-rpc.com?api-key="); +const owner = new PublicKey("OWNER_PUBKEY_HERE"); + +async function getAccountsWithDataSlice() { + const accounts = await rpc.getCompressedAccountsByOwner(owner, { + dataSlice: { + offset: 0, + length: 32 // First 32 bytes only + } + }); + + console.log(`Found ${accounts.items.length} accounts with data slice`); + + accounts.items.forEach((account, index) => { + console.log(`Account ${index + 1}:`); + console.log(` Hash: ${account.hash.toString()}`); + console.log(` Data length: ${account.data?.data?.length || 0} bytes`); + }); + + return accounts; +} + +getAccountsWithDataSlice(); +``` diff --git a/compression-docs/resources/json-rpc-methods/getcompressedbalance.md b/compression-docs/resources/json-rpc-methods/getcompressedbalance.md new file mode 100644 index 0000000..0e7c208 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedbalance.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the balance for the compressed account with the given address or hash. + Overview to method, use cases, examples and developer tips. +--- + +# getCompressedBalance + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getCompressedBalance" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedbalancebyowner.md b/compression-docs/resources/json-rpc-methods/getcompressedbalancebyowner.md new file mode 100644 index 0000000..bfc0107 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedbalancebyowner.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the total balance of the owner's compressed accounts. Overview to + method, use cases, examples and developer tips. +--- + +# getCompressedBalanceByOwner + +{% openapi-operation spec="get-compressed-accounts-by-owner" path="/getCompressedAccountsByOwner" method="post" %} +[OpenAPI get-compressed-accounts-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedAccountsByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedminttokenholders.md b/compression-docs/resources/json-rpc-methods/getcompressedminttokenholders.md new file mode 100644 index 0000000..c6df65c --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedminttokenholders.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the owner balances for a given mint in descending order. Overview to + method, use cases, examples and developer tips. +--- + +# getCompressedMintTokenHolders + +{% openapi-operation spec="get-compressed-mint-token-holders" path="/" method="post" %} +[OpenAPI get-compressed-mint-token-holders](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedMintTokenHolders.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbalances.md b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbalances.md new file mode 100644 index 0000000..be33c48 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbalances.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the balance for a given token account. Overview to method, use cases, + examples and developer tips. +--- + +# getCompressedTokenAccountBalances + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getCompressedTokenAccountBalance" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbydelegate.md b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbydelegate.md new file mode 100644 index 0000000..a6ba53d --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountbydelegate.md @@ -0,0 +1,12 @@ +--- +description: >- + Returns the compressed token accounts that are partially or fully delegated to + the given delegate. Overview to method, use cases, examples and developer + tips. +--- + +# getCompressedTokenAccountByDelegate + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getCompressedTokenAccountsByDelegate" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountsbyowner.md b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountsbyowner.md new file mode 100644 index 0000000..da4ecb8 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedtokenaccountsbyowner.md @@ -0,0 +1,318 @@ +--- +description: >- + Returns the compressed token accounts owned by a certain account. Overview to + method, use cases, examples and developer tips. +--- + +# getCompressedTokenAccountsByOwner + +The`getCompressedTokenAccountsByOwner` RPC method retrieves all compressed token accounts owned by a specific public key. The method supports mint filtering to query specific tokens, cursor-based pagination for handling large result sets, and returns parsed token data with merkle context for verification. + +{% hint style="info" %} +You can test this method via the OpenAPI example or [custom examples below](getcompressedtokenaccountsbyowner.md#examples). +{% endhint %} + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getCompressedTokenAccountsByOwner" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} + +### Common Use Cases + +* **Token Portfolio Display**: Show all compressed tokens owned by a wallet address +* **Balance Aggregation**: Calculate total token holdings across multiple compressed accounts +* **Token Transfer Setup**: Find available token accounts to use as transfer sources +* **Wallet Integration**: Support compressed token discovery in wallet interfaces +* **DeFi Protocol Integration**: Query user token holdings for lending, trading, or staking + +### Parameters + +1. `owner` (PublicKey, required): Base58-encoded public key of the account owner to query token accounts for. +2. `options` (object, optional): Configuration object containing filtering and pagination parameters: + * `mint` (PublicKey, optional): Base58-encoded mint address to filter results by specific token + * `cursor` (string, optional): Pagination cursor from previous response for fetching next page + * `limit` (BN, optional): Maximum number of accounts to return (use `bn()` helper function) + +**Note**: All parameters are optional within the options object. Without filters, returns all compressed token accounts for the owner. + +### Response + +The response contains a paginated list of compressed token accounts: + +* `items` (array): Array of compressed token account objects + * `compressedAccount` (object): Compressed account data and merkle proof context + * `address` (string): Compressed account address (if available) + * `owner` (string): Account owner public key + * `lamports` (number): Account lamports + * `data` (object): Account data information + * `hash` (string): Account hash for merkle proof verification + * `parsed` (object): Parsed token-specific information + * `mint` (PublicKey): Token mint address + * `owner` (PublicKey): Token account owner + * `amount` (BN): Token amount as BN object - use .toString() for exact value + * `delegate` (PublicKey | null): Delegate address if set, null otherwise + * `state` (number): Account state (0=initialized, 1=frozen, etc.) + * `tlv` (Buffer | null): Type-Length-Value extension data if present +* `cursor` (string | null): Pagination cursor for next batch, null if no more results + +### Developer Tips + +* **BN Object Handling**: The `amount` field is a BN object - use `.toString()` for exact values or `.toNumber()` for small amounts +* **State Values**: The `state` field is a numeric enum (0=initialized, 1=frozen, etc.) not a string +* **Parameter Types**: Use `bn()` helper function for the `limit` parameter, not raw numbers +* **Pagination**: Use cursor-based pagination for large token portfolios to avoid timeouts +* **Performance**: Filter by mint address when querying specific tokens to reduce response size +* **Empty Responses**: Handle cases where owners have no compressed token accounts gracefully + +### Examples + +The below examples work - just make sure you installed the dependencies. + +
+ +Dependencies & Setup + +```bash +npm install @lightprotocol/stateless.js +``` + +
+ +#### Example: Get All Token Accounts + +{% tabs %} +{% tab title="cURL" %} +```bash +curl -X POST https://devnet.helius-rpc.com?api-key=YOUR_API_KEY \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getCompressedTokenAccountsByOwner", + "params": { + "owner": "OWNER_PUBKEY_HERE" + } + }' +``` +{% endtab %} + +{% tab title="TypeScript" %} +```typescript +import { createRpc, bn, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function getAllTokenAccounts(): Promise { + const owner = new PublicKey('OWNER_ADDRESS_HERE'); + const result = await rpc.getCompressedTokenAccountsByOwner(owner); + + console.log(`Found ${result.items.length} compressed token accounts`); + + result.items.forEach((account, index) => { + console.log(`Token Account ${index + 1}:`); + console.log(` Mint: ${account.parsed.mint.toBase58()}`); + console.log(` Amount: ${account.parsed.amount.toString()}`); + console.log(` Owner: ${account.parsed.owner.toBase58()}`); + console.log(` State: ${account.parsed.state}`); + }); + + return result; +} + +getAllTokenAccounts(); +``` +{% endtab %} + +{% tab title="Rust" %} +{% hint style="info" %} +**Rust Client**: `light-client` v0.14.0 is available on crates.io. Use `LightClient` for the current stable API. +{% endhint %} + +```rust +// Current API: light-client 0.14.0 +use light_client::rpc::LightClient; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?; + + // Query compressed token accounts by owner + let owner = Pubkey::from_str("OWNER_PUBKEY_HERE")?; + + match client.get_compressed_token_accounts_by_owner(&owner, None).await? { + response => { + println!("Found {} compressed token accounts", response.value.items.len()); + + for (index, account) in response.value.items.iter().enumerate() { + println!("Token Account {}:", index + 1); + println!(" Mint: {}", account.parsed.mint); + println!(" Amount: {}", account.parsed.amount); + println!(" Owner: {}", account.parsed.owner); + println!(" State: {}", account.parsed.state); + } + } + } + + Ok(()) +} +``` +{% endtab %} +{% endtabs %} + +#### Example: Filter by Specific Token Mint + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, bn, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function getTokenAccountsByMint(): Promise { + const owner = new PublicKey('OWNER_ADDRESS_HERE'); + const mint = new PublicKey('TOKEN_MINT_HERE'); + + const result = await rpc.getCompressedTokenAccountsByOwner(owner, { + mint + }); + + if (result.items.length === 0) { + console.log('No compressed token accounts found for this mint'); + return; + } + + let totalBalance = bn(0); + result.items.forEach((account, index) => { + const amount = account.parsed.amount; + totalBalance = totalBalance.add(amount); + + console.log(`Account ${index + 1}:`); + console.log(` Amount: ${amount.toString()}`); + console.log(` Hash: ${account.compressedAccount.hash}`); + }); + + console.log(`Total Balance: ${totalBalance.toString()}`); + + return result; +} + +getTokenAccountsByMint(); +``` +{% endtab %} +{% endtabs %} + +#### Example: Paginated Token Discovery + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, bn, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function getAllTokenAccountsPaginated(owner: PublicKey) { + let allAccounts = []; + let cursor = undefined; + const batchSize = 50; // Smaller batch size for DevNet + + do { + const batch = await rpc.getCompressedTokenAccountsByOwner(owner, { + cursor, + limit: bn(batchSize) + }); + + allAccounts.push(...batch.items); + cursor = batch.cursor; + + console.log(`Fetched ${batch.items.length} token accounts, total: ${allAccounts.length}`); + + // Rate limiting for DevNet + await new Promise(resolve => setTimeout(resolve, 100)); + + } while (cursor); + + // Group by mint + const accountsByMint = new Map(); + allAccounts.forEach(account => { + const mintKey = account.parsed.mint.toBase58(); + if (!accountsByMint.has(mintKey)) { + accountsByMint.set(mintKey, []); + } + accountsByMint.get(mintKey).push(account); + }); + + console.log(`Total compressed token accounts: ${allAccounts.length}`); + console.log(`Unique token mints: ${accountsByMint.size}`); + + return { allAccounts, accountsByMint }; +} + +// Usage +const owner = new PublicKey('OWNER_ADDRESS_HERE'); +getAllTokenAccountsPaginated(owner); +``` +{% endtab %} +{% endtabs %} + +#### Example: Calculate Token Portfolio Value + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, bn, Rpc } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function calculateTokenPortfolio(): Promise> { + const owner = new PublicKey('OWNER_ADDRESS_HERE'); + const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner(owner); + + if (tokenAccounts.items.length === 0) { + console.log('No compressed token accounts found'); + return; + } + + const portfolio = new Map(); + + tokenAccounts.items.forEach(account => { + const mintKey = account.parsed.mint.toBase58(); + const amount = account.parsed.amount; + + if (portfolio.has(mintKey)) { + const existing = portfolio.get(mintKey); + portfolio.set(mintKey, existing.add(amount)); + } else { + portfolio.set(mintKey, amount); + } + }); + + console.log('Compressed Token Portfolio:'); + portfolio.forEach((balance, mint) => { + console.log(` ${mint}: ${balance.toString()}`); + }); + + return portfolio; +} + +calculateTokenPortfolio(); +``` +{% endtab %} +{% endtabs %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressedtokenbalancesbyowner.md b/compression-docs/resources/json-rpc-methods/getcompressedtokenbalancesbyowner.md new file mode 100644 index 0000000..33a0b8b --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressedtokenbalancesbyowner.md @@ -0,0 +1,11 @@ +--- +description: >- + Retrieves all token balances for compressed accounts owned by an address. + Overview to method, use cases, examples and developer tips. +--- + +# getCompressedTokenBalancesByOwner + +{% openapi-operation spec="get-compressed-token-balances-by-owner" path="/getCompressedTokenBalancesByOwner" method="post" %} +[OpenAPI get-compressed-token-balances-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedTokenBalancesByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaccount.md b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaccount.md new file mode 100644 index 0000000..e2fd61c --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaccount.md @@ -0,0 +1,12 @@ +--- +description: >- + Return the signatures of the transactions that closed or opened a compressed + account with the given hash. Overview to method, use cases, examples and + developer tips. +--- + +# getCompressionSignaturesForAccount + +{% openapi-operation spec="get-compression-signatures-for-account" path="/getCompressionSignaturesForAccount" method="post" %} +[OpenAPI get-compression-signatures-for-account](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressionSignaturesForAccount.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaddress.md b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaddress.md new file mode 100644 index 0000000..6a92c84 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforaddress.md @@ -0,0 +1,12 @@ +--- +description: >- + Return the signatures of the transactions that closed or opened a compressed + account with the given address. Overview to method, use cases, examples and + developer tips. +--- + +# getCompressionSignaturesForAddress + +{% openapi-operation spec="get-compression-signatures-for-address" path="/getCompressionSignaturesForAddress" method="post" %} +[OpenAPI get-compression-signatures-for-address](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressionSignaturesForAddress.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforowner.md b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforowner.md new file mode 100644 index 0000000..20a6f26 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesforowner.md @@ -0,0 +1,12 @@ +--- +description: >- + Returns the signatures of the transactions that have modified an owner’s + compressed accounts. Overview to method, use cases, examples and developer + tips. +--- + +# getCompressionSignaturesForOwner + +{% openapi-operation spec="get-compression-signatures-for-owner" path="/getCompressionSignaturesForOwner" method="post" %} +[OpenAPI get-compression-signatures-for-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressionSignaturesForOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getcompressionsignaturesfortokenowner.md b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesfortokenowner.md new file mode 100644 index 0000000..328a0d2 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getcompressionsignaturesfortokenowner.md @@ -0,0 +1,12 @@ +--- +description: >- + Returns the signatures of the transactions that have modified an owner’s + compressed token accounts. Overview to method, use cases, examples and + developer tips. +--- + +# getCompressionSignaturesForTokenOwner + +{% openapi-operation spec="get-compression-signatures-for-token-owner" path="/getCompressionSignaturesForTokenOwner" method="post" %} +[OpenAPI get-compression-signatures-for-token-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressionSignaturesForTokenOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getindexerhealth.md b/compression-docs/resources/json-rpc-methods/getindexerhealth.md new file mode 100644 index 0000000..5a8dcbe --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getindexerhealth.md @@ -0,0 +1,12 @@ +--- +description: >- + Returns an error if the indexer is stale by more than a configurable number of + blocks. Otherwise, it returns ok. Overview to method, use cases, examples and + developer tips. +--- + +# getIndexerHealth + +{% openapi-operation spec="get-indexer-health" path="/" method="post" %} +[OpenAPI get-indexer-health](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getIndexerHealth.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getindexerslot.md b/compression-docs/resources/json-rpc-methods/getindexerslot.md new file mode 100644 index 0000000..69f4386 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getindexerslot.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the slot of the last block indexed by the indexer. Overview to method, + use cases, examples and developer tips. +--- + +# getIndexerSlot + +{% openapi-operation spec="get-indexer-slot" path="/" method="post" %} +[OpenAPI get-indexer-slot](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getIndexerSlot.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getlatestcompressionsignatures.md b/compression-docs/resources/json-rpc-methods/getlatestcompressionsignatures.md new file mode 100644 index 0000000..ea1f4ff --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getlatestcompressionsignatures.md @@ -0,0 +1,12 @@ +--- +description: >- + Returns the signatures of the latest transactions that used the compression + program. This is a paginated endpoint. Overview to method, use cases, examples + and developer tips. +--- + +# getLatestCompressionSignatures + +{% openapi-operation spec="get-latest-compression-signatures" path="/" method="post" %} +[OpenAPI get-latest-compression-signatures](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getLatestCompressionSignatures.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getlatestnonvotingsignatures.md b/compression-docs/resources/json-rpc-methods/getlatestnonvotingsignatures.md new file mode 100644 index 0000000..5383c21 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getlatestnonvotingsignatures.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns the signatures of the latest transactions that are not voting + transactions. Overview to method, use cases, examples and developer tips. +--- + +# getLatestNonVotingSignatures + +{% openapi-operation spec="get-latest-non-voting-signatures" path="/" method="post" %} +[OpenAPI get-latest-non-voting-signatures](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getLatestNonVotingSignatures.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getmultiplecompressedaccounts.md b/compression-docs/resources/json-rpc-methods/getmultiplecompressedaccounts.md new file mode 100644 index 0000000..dd9da65 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getmultiplecompressedaccounts.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns multiple compressed accounts with the given addresses or hashes. + Overview to method, use cases, examples and developer tips. +--- + +# getMultipleCompressedAccounts + +{% openapi-operation spec="get-compressed-balance-by-owner" path="/getMultipleCompressedAccounts" method="post" %} +[OpenAPI get-compressed-balance-by-owner](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressedBalanceByOwner.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/getmultiplenewaddressproofs.md b/compression-docs/resources/json-rpc-methods/getmultiplenewaddressproofs.md new file mode 100644 index 0000000..de52500 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getmultiplenewaddressproofs.md @@ -0,0 +1,11 @@ +--- +description: >- + Returns proofs that the new addresses are not taken already and can be + created. Overview to method, use cases, examples and developer tips. +--- + +# getMultipleNewAddressProofs + +{% openapi-operation spec="get-multiple-new-address-proofs" path="/" method="post" %} +[OpenAPI get-multiple-new-address-proofs](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getMultipleNewAddressProofs.yaml) +{% endopenapi-operation %} diff --git a/compression-docs/resources/json-rpc-methods/gettransactionwithcompressioninfo.md b/compression-docs/resources/json-rpc-methods/gettransactionwithcompressioninfo.md new file mode 100644 index 0000000..9d8c4ac --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/gettransactionwithcompressioninfo.md @@ -0,0 +1,304 @@ +--- +description: >- + Returns the transaction data for the transaction with the given signature + along with parsed compression info. Overview to method, use cases, examples + and developer tips. +--- + +# getTransactionWithCompressionInfo + +The`getTransactionWithCompressionInfo` RPC method returns transaction data along with compression information showing which compressed accounts were opened (created) and closed (consumed) during the transaction. This method helps with transaction analysis, account lifecycle tracking, and debugging of compression operations. + +{% hint style="info" %} +You can test this method via the OpenAPI example or [custom examples below](gettransactionwithcompressioninfo.md#examples). +{% endhint %} + +{% openapi-operation spec="get-compression-signatures-for-account" path="/getTransactionWithCompressionInfo" method="post" %} +[OpenAPI get-compression-signatures-for-account](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getCompressionSignaturesForAccount.yaml) +{% endopenapi-operation %} + +### Common Use Cases + +* **Transaction Analysis**: Understand what compressed accounts were affected by a transaction +* **Account Lifecycle Tracking**: Monitor when compressed accounts are created and consumed +* **Debugging**: Identify which accounts changed during failed or unexpected transactions +* **Audit Trails**: Track compressed account state changes for compliance + +### Parameters + +1. `signature` (string, required): Base58-encoded transaction signature to query compression information for. + +**Note**: Only transactions involving compressed accounts will return compression data. Regular Solana transactions return null. + +### Response + +The response contains compression information and transaction data, or null if transaction not found: + +* `compressionInfo` (object): Contains details about compressed account changes + * `closedAccounts` (array): Compressed accounts consumed (spent) in this transaction + * `account` (object): Complete compressed account data with merkle context + * `maybeTokenData` (object | null): Token data if this is a compressed token account + * `openedAccounts` (array): New compressed accounts created in this transaction + * `account` (object): Complete compressed account data with merkle context + * `maybeTokenData` (object | null): Token data if this is a compressed token account + * `preTokenBalances` (array, optional): Token balances before transaction + * `owner` (PublicKey): Public key of token account owner + * `mint` (PublicKey): Public key of token mint + * `amount` (BN): Token amount as BN object + * `postTokenBalances` (array, optional): Token balances after transaction + * `owner` (PublicKey): Public key of token account owner + * `mint` (PublicKey): Public key of token mint + * `amount` (BN): Token amount as BN object +* `transaction` (object): Standard Solana transaction data + +### Developer Tips + +* **Compression-only**: This method only works with transactions that involve compressed accounts +* **Real signatures required**: Use actual transaction signatures from compression operations +* **Account lifecycle**: opened = created, closed = consumed/spent in the transaction +* **Token data**: maybeTokenData is null for regular compressed accounts, populated for token accounts +* **Balance tracking**: Use pre/postTokenBalances for detailed token amount changes +* **State analysis**: Compare opened vs closed accounts to understand transaction effects + +### Troubleshooting + +
+ +"Transaction not found" + +**Invalid or non-existent transaction signature** + +Verify the signature format and check transaction existence: + +```typescript +const result = await connection.getTransactionWithCompressionInfo(signature); + +if (!result) { + console.log('Transaction not found or contains no compression operations'); +} +``` + +
+ +
+ +"No compression info" + +**Transaction exists but has no compression data** + +This method only returns data for transactions involving compressed accounts: + +```typescript +const result = await connection.getTransactionWithCompressionInfo(signature); + +if (!result) { + console.log('Transaction does not involve compressed accounts'); + console.log('Use regular getTransaction for non-compression transactions'); +} +``` + +
+ +
+ +"Empty account arrays" + +**Transaction has compression info but no account changes shown** + +Some compression operations may not create/consume accounts: + +```typescript +const { compressionInfo } = result; + +if (compressionInfo.openedAccounts.length === 0 && + compressionInfo.closedAccounts.length === 0) { + console.log('Compression transaction with no visible account changes'); + console.log('May be a proof verification or state update operation'); +} +``` + +
+ +### Examples + +The below examples work - just make sure you installed the dependencies. + +
+ +Dependencies & Setup + +```bash +npm install @lightprotocol/stateless.js +``` + +
+ +#### Example: Analyze Transaction + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { createRpc, Rpc, CompressedTransaction } from '@lightprotocol/stateless.js'; + +const rpc: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' +); + +async function analyzeTransactionCompression(): Promise { + const signature: string = 'TRANSACTION_SIGNATURE_HERE'; + + const result: CompressedTransaction | null = + await rpc.getTransactionWithCompressionInfo(signature); + + if (!result) { + console.log('Transaction not found or has no compression info'); + return; + } + + const { compressionInfo } = result; + + console.log('Transaction Compression Summary:', { + openedCount: compressionInfo.openedAccounts.length, + closedCount: compressionInfo.closedAccounts.length, + hasTokenBalances: !!(compressionInfo.preTokenBalances || compressionInfo.postTokenBalances) + }); + + // Type-safe account analysis + compressionInfo.closedAccounts.forEach((entry, index) => { + console.log(`Closed Account ${index + 1}:`, { + hash: entry.account.hash.toString(), + lamports: entry.account.lamports.toString(), + hasTokenData: !!entry.maybeTokenData + }); + }); + + compressionInfo.openedAccounts.forEach((entry, index) => { + console.log(`Opened Account ${index + 1}:`, { + hash: entry.account.hash.toString(), + lamports: entry.account.lamports.toString(), + hasTokenData: !!entry.maybeTokenData + }); + }); +} + +analyzeTransactionCompression(); +``` +{% endtab %} + +{% tab title="cURL" %} +```bash +curl -X POST https://devnet.helius-rpc.com?api-key=YOUR_API_KEY \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getTransactionWithCompressionInfo", + "params": { + "signature": "TRANSACTION_SIGNATURE_HERE" + } + }' +``` +{% endtab %} + +{% tab title="Rust" %} +{% hint style="info" %} +**Rust Client**: `light-client` v0.14.0 is available on crates.io. Use `LightClient` for the current stable API. +{% endhint %} + +```rust +// Current API: light-client 0.14.0 +use light_client::rpc::LightClient; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?; + + // Analyze compression transaction + let signature = "TRANSACTION_SIGNATURE_HERE"; + + match client.get_transaction_with_compression_info(signature).await? { + Some(result) => { + let compression_info = &result.compression_info; + + println!("Compression Analysis:"); + println!(" Accounts opened: {}", compression_info.opened_accounts.len()); + println!(" Accounts closed: {}", compression_info.closed_accounts.len()); + + // Show account details + for (i, entry) in compression_info.opened_accounts.iter().enumerate() { + println!(" Opened Account {}:", i + 1); + println!(" Hash: {}", entry.account.hash); + println!(" Lamports: {}", entry.account.lamports); + } + } + None => { + println!("Transaction not found or has no compression info"); + } + } + + Ok(()) +} +``` +{% endtab %} +{% endtabs %} + +#### Example: Track Token Balance Changes + +```typescript +async function trackTokenBalanceChanges(): Promise { + const connection = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' + ); + + const signature = 'TOKEN_TRANSACTION_SIGNATURE_HERE'; + + const result = await connection.getTransactionWithCompressionInfo(signature); + + if (!result) { + console.log('Transaction not found'); + return; + } + + const { compressionInfo } = result; + + // Analyze token balance changes if available + if (compressionInfo.preTokenBalances && compressionInfo.postTokenBalances) { + console.log('Token Balance Changes:'); + + compressionInfo.preTokenBalances.forEach((preBalance, index) => { + const postBalance = compressionInfo.postTokenBalances![index]; + const change = postBalance.amount.sub(preBalance.amount); + + if (!change.isZero()) { + console.log(` Owner: ${preBalance.owner.toBase58()}`); + console.log(` Mint: ${preBalance.mint.toBase58()}`); + console.log(` Change: ${change.toString()}`); + } + }); + } else { + console.log('No token balance data available for this transaction'); + } + + // Analyze token accounts in opened/closed arrays + const tokenAccountsOpened = compressionInfo.openedAccounts.filter( + entry => entry.maybeTokenData !== null + ); + + const tokenAccountsClosed = compressionInfo.closedAccounts.filter( + entry => entry.maybeTokenData !== null + ); + + console.log(`Token accounts opened: ${tokenAccountsOpened.length}`); + console.log(`Token accounts closed: ${tokenAccountsClosed.length}`); + + return { + tokenAccountsOpened: tokenAccountsOpened.length, + tokenAccountsClosed: tokenAccountsClosed.length + }; +} + +trackTokenBalanceChanges(); +``` diff --git a/compression-docs/resources/json-rpc-methods/getvalidityproof.md b/compression-docs/resources/json-rpc-methods/getvalidityproof.md new file mode 100644 index 0000000..cd03018 --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/getvalidityproof.md @@ -0,0 +1,242 @@ +--- +description: >- + Returns a single ZK Proof used by the compression program to verify that the + given accounts are valid and the new addresses can be created. Overview to + method, use cases, examples and developer tips. +--- + +# getValidityProof + +The`getValidityProof` RPC method generates zero-knowledge proofs to verify that the given accounts are valid or the new addresses can be created. This proof is required for any operation on compressed accounts (transfer, approve, decompress, etc.) and enables on-chain verification + +{% hint style="info" %} +You can test this method via the OpenAPI example or [custom examples below](getvalidityproof.md#examples). +{% endhint %} + +{% hint style="info" %} +* Proof limits per request are: + * `hashes`: 1, 2, 3, 4, or 8 + * `newAddressesWithTrees` : 1, 2 +* The `newAddresses` param field is supported but deprecated. Please use `newAddressesWithTrees`instead. +{% endhint %} + +{% openapi-operation spec="get-validity-proof" path="/" method="post" %} +[OpenAPI get-validity-proof](https://raw.githubusercontent.com/helius-labs/photon/refs/heads/main/src/openapi/specs/getValidityProof.yaml) +{% endopenapi-operation %} + +### Common Use Cases + +* **Token Transfers:** Generate proofs required for transferring compressed tokens. +* **Account Operations:** Create proofs needed for any compressed account modification. +* **Batch Processing:** Generate proofs for multiple accounts in a single call. +* **State Tree Verification:** Prove account inclusion in the current state tree. +* **Transaction Building:** Obtain proof data needed for compressed transaction instructions. +* **Program Integration:** Get validity proofs for custom program operations on compressed accounts. + +### Parameters + +1. `hashes` (BN254\[], required): Array of BN254 objects representing compressed account hashes to generate proofs for. +2. `newAddresses` (BN254\[], optional): Array of BN254 objects representing new addresses to include in the proof for address tree verification. + +### Response + +The response contains ValidityProofWithContext data: + +* `compressedProof` (ValidityProof | null): The compressed validity proof object for zero-knowledge verification +* `roots` (BN\[]): Array of merkle tree roots used in proof generation +* `rootIndices` (number\[]): Array of indices of the roots in the state tree +* `leafIndices` (number\[]): Array of indices of the leaves being proven +* `leaves` (BN\[]): Array of leaf values in the merkle tree +* `treeInfos` (TreeInfo\[]): Array of information about the state trees used +* `proveByIndices` (boolean\[]): Array indicating whether to prove by indices for each element + +### Developer Tips + +* **Fetch Accounts First**: Always get compressed accounts before generating proofs - you need existing account hashes +* **Batch Processing**: You can generate proofs for multiple accounts in a single call. Allowed counts: 1, 2, 3, 4, or 8 accounts +* **Proof Usage**: The proof is required for any operation that modifies compressed accounts (transfers, burns, etc.) +* **Caching**: Validity proofs are only valid for the current state - don't cache them for long periods +* **Error Handling**: If accounts don't exist or have been modified, proof generation will fail +* **Integration**: Use `proof.compressedProof` and `proof.rootIndices` when building transactions +* **State Changes**: After any transaction, previously generated proofs become invalid for those accounts + +### Examples + +The below examples work - just make sure you installed the dependencies. + +
+ +Dependencies & Setup + +```bash +npm install @lightprotocol/stateless.js @solana/web3.js +``` + +
+ +#### Example: Generate Validity Proof + +Generate a validity proof for compressed accounts. + +{% tabs %} +{% tab title="TypeScript" %} +```typescript +import { Rpc, createRpc, bn } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +async function generateValidityProof(): Promise { + const connection: Rpc = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' + ); + + try { + // Get compressed accounts + const owner = new PublicKey('OWNER_PUBLIC_KEY_HERE'); + const accounts = await connection.getCompressedAccountsByOwner(owner); + + if (accounts.items.length === 0) { + console.log('No compressed accounts found'); + return; + } + + // Generate validity proof + const hashes = accounts.items.slice(0, 2).map(acc => bn(acc.hash)); + const validityProof = await connection.getValidityProof(hashes); + + console.log('Validity Proof Generated:'); + console.log(` Accounts: ${hashes.length}`); + console.log(` Roots: ${validityProof.roots.length}`); + console.log(` Root Indices: ${validityProof.rootIndices.length} elements`); + console.log(` Leaf Indices: ${validityProof.leafIndices.length} elements`); + + // Ready for use in transactions + console.log('\nProof ready for compressed transactions'); + + } catch (error: unknown) { + console.error('Error generating validity proof:', error); + } +} + +generateValidityProof(); +``` +{% endtab %} + +{% tab title="cURL" %} +```bash +# Use account hashes from getCompressedAccountsByOwner +curl -X POST https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getValidityProof", + "params": { + "hashes": [ + "ACCOUNT_HASH_1", + "ACCOUNT_HASH_2" + ] + } + }' +``` +{% endtab %} + +{% tab title="Rust" %} +{% hint style="info" %} +**Rust Client**: `light-client` v0.14.0 is available on crates.io. Use `LightClient` for the current stable API. +{% endhint %} + +```rust +use light_client::rpc::LightClient; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?; + + // Get compressed accounts first + let owner = "OWNER_PUBLIC_KEY_HERE"; + let accounts = client.get_compressed_accounts_by_owner(owner).await?; + + if accounts.items.is_empty() { + println!("No compressed accounts found"); + return Ok(()); + } + + // Generate validity proof for the accounts + let hashes: Vec = accounts.items.iter() + .take(2) + .map(|acc| acc.hash.clone()) + .collect(); + + let validity_proof = client.get_validity_proof(hashes.clone()).await?; + + println!("Validity Proof Generated:"); + println!(" Accounts: {}", hashes.len()); + println!(" Roots: {}", validity_proof.roots.len()); + println!(" Root Indices: {}", validity_proof.root_indices.len()); + + Ok(()) +} +``` +{% endtab %} +{% endtabs %} + +### Example: Proof Generation for Token Transfer + +Generate a validity proof for a compressed token transfer operation: + +{% tabs %} +{% tab title="JavaScript" %} +```javascript +import { Rpc, createRpc, bn } from '@lightprotocol/stateless.js'; +import { PublicKey } from '@solana/web3.js'; + +async function generateProofForTransfer() { + const connection = createRpc( + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY', + 'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY' + ); + + try { + // Get compressed token accounts + const owner = new PublicKey('OWNER_PUBLIC_KEY_HERE'); + const mint = new PublicKey('TOKEN_MINT_HERE'); + + const tokenAccounts = await connection.getCompressedTokenAccountsByOwner( + owner, + { mint } + ); + + if (tokenAccounts.items.length === 0) { + console.log('No compressed token accounts found'); + return; + } + + // Generate proof for the token accounts (limit to allowed numbers: 1, 2, 3, 4, or 8) + const limitedAccounts = tokenAccounts.items.slice(0, 4); + const hashes = limitedAccounts.map(acc => bn(acc.compressedAccount.hash)); + const validityProof = await connection.getValidityProof(hashes); + + console.log('Validity Proof for Transfer:'); + console.log(` Token accounts: ${hashes.length}`); + console.log(` Roots: ${validityProof.roots.length}`); + + // This proof is now ready to use with CompressedTokenProgram.transfer() + console.log('\nProof ready for token transfer instruction'); + console.log('Use this with:'); + console.log(' recentValidityProof: validityProof.compressedProof'); + console.log(' recentInputStateRootIndices: validityProof.rootIndices'); + + } catch (error) { + console.error('Error generating proof:', error); + } +} + +generateProofForTransfer(); +``` +{% endtab %} +{% endtabs %} + diff --git a/compression-docs/resources/json-rpc-methods/json-rpc-methods.md b/compression-docs/resources/json-rpc-methods/json-rpc-methods.md new file mode 100644 index 0000000..15ad16d --- /dev/null +++ b/compression-docs/resources/json-rpc-methods/json-rpc-methods.md @@ -0,0 +1,79 @@ + + +# JSON RPC methods + +*** + +{% hint style="info" %} +[Helius Labs](https://github.com/helius-labs) maintains the canonical RPC API and [Photon indexer implementation](https://github.com/helius-labs/photon). +{% endhint %} + +Interact with compressed accounts directly with the [ZK Compression JSON RPC](./) API. It's a thin wrapper extending [Solana's web3.js `Connection` class](https://solana-labs.github.io/solana-web3.js/classes/Connection.html) with compression-related endpoints. + +The API exposed by the indexer closely mirrors existing RPC calls, with one-to-one mapping: + +| Solana RPC | Photon RPC Calls | +| ----------------------- | --------------------------------- | +| getAccountInfo | getCompressedAccount | +| getBalance | getCompressedBalance | +| getTokenAccountsByOwner | getCompressedTokenAccountsByOwner | +| getProgramAccounts | getCompressedAccountsByOwner | + +## Creating an RPC Connection + +Connect to a specific RPC endpoint + +{% tabs %} +{% tab title="Mainnet" %} +
import {
+  Rpc,
+  createRpc,
+} from "@lightprotocol/stateless.js";
+
+// Helius exposes Solana and Photon RPC endpoints through a single URL
+const RPC_ENDPOINT = "https://mainnet.helius-rpc.com?api-key=<api_key>";
+const PHOTON_ENDPOINT = RPC_ENDPOINT;
+const PROVER_ENDPOINT = RPC_ENDPOINT;
+const connection: Rpc = createRpc(RPC_ENDPOINT, PHOTON_ENDPOINT, PROVER_ENDPOINT)
+
+console.log("connection", connection);
+
+{% endtab %} + +{% tab title="Devnet" %} +
import {
+  Rpc,
+  createRpc,
+} from "@lightprotocol/stateless.js";
+
+// Helius exposes Solana and Photon RPC endpoints through a single URL
+const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<api_key>";
+const PHOTON_ENDPOINT = RPC_ENDPOINT;
+const PROVER_ENDPOINT = RPC_ENDPOINT;
+const connection: Rpc = createRpc(RPC_ENDPOINT, PHOTON_ENDPOINT, PROVER_ENDPOINT)
+
+console.log("connection", connection);
+
+{% endtab %} +{% endtabs %} + +### Best Practices + +
Commitment LevelsUse appropriate commitment levels: processed(fastest), confirmed (balanced), finalized (most reliable)
Rate LimitingImplement retry logic and respect rate limits. Public endpoints: 100 req/s, Private: 1000+ req/s
Batch RequestsUse batch requests when possible to improve efficiency and reduce API calls
CachingCache frequently accessed data to reduce API calls and improve performance
+ +### Error Codes + +| Code | Message | Description | +| ------ | -------------------- | -------------------------------------------- | +| -32600 | Invalid Request | The JSON sent is not a valid Request object | +| -32601 | Method not found | The method does not exist / is not available | +| -32602 | Invalid params | Invalid method parameter(s) | +| -32603 | Internal error | Internal JSON-RPC error | +| -32000 | Account not found | The compressed account was not found | +| -32001 | Invalid account hash | The provided account hash is invalid | + +*** + +## Mainnet ZK Compression API endpoints + +

GetCompressedAccount

Retrieves information about a specific compressed account by its address or hash.getcompressedaccount.md

GetCompressedAccountsByOwner

Returns all compressed accounts owned by a specific address.getcompressedaccountsbyowner.md

GetCompressedBalance

Retrieves the balance of a compressed account.getcompressedbalance.md

GetCompressedBalanceByOwner

Gets the total balance of all compressed accounts owned by an address.getcompressedbalancebyowner.md

GetCompressedMintTokenHolders

Lists all holders of a specific compressed token mint.getcompressedminttokenholders.md

GetCompressedTokenAccountBalances

Retrieves the token balance of a compressed token account.getcompressedtokenaccountbalances.md

GetCompressedTokenAccountsByDelegate

Returns all compressed token accounts delegated to a specific address.getcompressedtokenaccountbydelegate.md

GetCompressedTokenAccountsByOwner

Lists all compressed token accounts owned by a specific address.getcompressedtokenaccountsbyowner.md

GetCompressedTokenBalancesByOwner

Retrieves all token balances for compressed accounts owned by an address.getcompressedtokenbalancesbyowner.md

GetCompressionSignaturesForAccount

Returns signatures for transactions involving a compressed account.getcompressionsignaturesforaccount.md

GetCompressionSignaturesForAddress

Retrieves signatures for transactions involving a specific address.getcompressionsignaturesforaddress.md

GetCompressionSignaturesForOwner

Returns signatures for transactions where an address is the owner.getcompressionsignaturesforowner.md

GetCompressionSignaturesForTokenOwner

Lists signatures for transactions involving tokens owned by an address.getcompressionsignaturesfortokenowner.md

GetIndexerHealth

Returns the health status of the compression indexer.getindexerhealth.md

GetIndexerSlot

Retrieves the current slot of the compression indexer.getindexerslot.md

GetLatestCompressionSignatures

Returns the most recent transaction signatures related to compression.getlatestcompressionsignatures.md

GetLatestNon-VotingSignatures

Retrieves recent non-voting transaction signatures.getlatestnonvotingsignatures.md

GetMultipleCompressedAccounts

Retrieves multiple compressed accounts in a single request.getmultiplecompressedaccounts.md

GetMultipleNewAddressProofs

Returns proofs that the new addresses are not taken already and can be created.getmultiplenewaddressproofs.md

GetTransactionWithCompressionInfo

Returns transaction details with additional compression-related information.gettransactionwithcompressioninfo.md

GetValidityProof

Retrieves a validity proof for compressed data.getvalidityproof.md
diff --git a/compression-docs/resources/privacy-policy.md b/compression-docs/resources/privacy-policy.md deleted file mode 100644 index 186d902..0000000 --- a/compression-docs/resources/privacy-policy.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -description: 'Last updated: September 14, 2024' ---- - -# Privacy Policy - -1\. PURPOSE OF THIS DOCUMENT - -1.1. This privacy policy ("Privacy Policy") describes how Light Protocol Labs ("Light Protocol," "we," "us," or "our") collects, uses, and shares information about you. This Privacy Policy applies whenever you use [https://www.zkcompression.com](https://www.zkcompression.com) (the "Website" or “Service”), contact our support team, interact with us on social media, or engage with us in any other manner. - -1.2. We may update this Privacy Policy from time to time and therefore encourage you to review this Privacy Policy frequently. If any changes are made, we will update the revision date at the top of this policy, and in certain cases, we may provide additional notice (such as posting a notice on our Website or sending a notification). - -1.3. For any matters, relating to data protection you may contact in writing by e-mail [friends@lightprotocol.com](mailto:friends@lightprotocol.com). - - - -2\. CONTENTS - -COLLECTING OF INFORMATION\ -USE OF INFORMATION\ -RETENTION OF INFORMATION\ -YOUR RIGHTS\ -DISCLOSURE OF DATA\ -TRANSFERS OF INFORMATION\ -DATA SECURITY AND LIABILITY\ -MINORS PRIVACY\ -LINKS TO OTHER WEBSITES\ -YOUR CALIFORNIA PRIVACY RIGHTS\ -ADDITIONAL DISCLOSURES FOR INDIVIDUALS IN EUROPE\ -CONTACT - - - -3\. COLLECTING OF INFORMATION - -3.1. We collect personal information you provide directly to us. - -3.2. We collect data about how you access and interact with our Website including information about the device and network you use, such as your hardware model, operating system version, browser type, IP address, mobile network, and unique device identifiers. Additionally, we track your activity on the Website, such as access times, pages viewed, links clicked, and the page that directed you to our Website. - -3.3. When accessing the Website via a mobile device, we may also gather information like your device ID, mobile browser type, and operating system. Usage data is collected automatically and includes diagnostic details such as the time spent on each page and other related metrics. - -3.4. With your device’s permissions, we may collect precise location data. You can disable the collection of this information at any time by adjusting your device settings. - -3.5. We use cookies and similar tracking technologies to gather information about your usage of our Website. Cookies are small data files stored on your device that help us enhance your experience, track popular features, and monitor visits. You may instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. If you refuse Cookies, you may not be able to use some parts of our Service. - - - -4\. USE OF INFORMATION - -4.1. We use the information collected to provide, maintain, and improve our products, services, and Website. Additionally, we use the data to personalize your experience, respond to customer inquiries, and provide technical notices, security alerts, and support messages. - -4.2. Our use of the information extends to: - -4.2.1. Monitoring and analyzing trends, usage, and activities related to our Website. - -4.2.2. Detecting, investigating, and preventing fraudulent, illegal, or malicious activity to protect the rights and property of our company and others. - -4.2.3. Debugging and identifying errors in our Website. - -4.2.4. Fulfilling legal and financial obligations. - -4.2.5. We may also use the information for additional purposes described to you at the time the data was collected or for purposes like data analysis, identifying trends, and improving our services. - - - -5\. RETENTION OF INFORMATION - -5.1. We will retain your information only as long as necessary for the purposes outlined in this Privacy Policy and ay retain Usage Data for security improvements or legal obligations. - -5.2. We expect to delete your information (at the latest) once there is no longer any legal or regulatory requirement or legitimate business purpose. - - - -6\. YOUR RIGHTS - -You have certain data protection rights, including the right to: - -6.1. request access to, update, or delete your personal data, and manage your information directly within the Service. - -6.2. to correct incomplete or inaccurate data and request the restriction of processing in certain situations. - -6.3. to obtain information on how and why your personal data is processed. - -6.4. request that we stop processing your personal data and withdraw consent to direct marketing at any time. - -6.5. the right to be notified of any data breach that may affect your rights and freedoms. - -6.6. request the deletion of your personal data in specific circumstances, such as when it's no longer necessary for the original purpose (right to be forgotten). - - - -7\. DISCLOSURE OF DATA - -7.1. We may disclose your Data: - -7.1.1. in connection with business transactions. - -7.1.2. to comply with legal obligations, if we believe that disclosure is in accordance with, or required by, any applicable law or legal process, including lawful requests by public authorities to meet national security or law enforcement requirements - -7.1.3. if we believe that your actions are inconsistent with our user agreements or policies, if we believe that you have violated the law, or if we believe it is necessary to protect the rights, property, and safety of Light Protocol, our users, the public, or others. - -7.1.4. to protect the rights, property, or safety of the Company, Service, or users. - - - -8\. TRANSFERS OF INFORMATION - -8.1. Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction - -8.2. Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer - -8.3. The Company will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information. - - - -9\. DATA SECURITY AND LIABILITY - -9.1. We implement robust technical and organizational measures to safeguard your personal data from unauthorized access, manipulation, loss, or exposure. These security protocols are regularly updated and enhanced in line with evolving technological standards. - -9.2. Please be aware that transmitting data over the internet (e.g., through email communications) is not always completely secure, and we cannot be held responsible for any data shared with us via these methods. While we strive to protect your information, absolute security cannot be guaranteed. - -9.3. This privacy policy does not extend to third-party websites or any external links provided on our platform. We disclaim any responsibility or liability for the content or data protection practices of third-party websites. - - - -10\. MINORS PRIVACY - -10.1. Our Service does not address anyone under 18, and we do not knowingly collect information from minors. - -10.2. If you are a parent and aware that your minors has provided Personal Data, please contact Us. - -10.3. If We become aware that We have collected Personal Data from anyone under the age of 18 without verification of parental consent, We take steps to remove that information from Our servers. - - - -11\. LINKS TO OTHER WEBSITES - -11.1. Our Service may contain links to third-party websites that are not operated by us. - -11.2. We advise you to review the Privacy Policy of every site you visit. We have no control over and no responsibility for the content, privacy policies or practices of any third party sites or services. - - - -12\. YOUR CALIFORNIA PRIVACY RIGHTS - -12.1. This section applies to you, if you are a California Resident. - -12.2. The California Consumer Privacy Act or "CCPA" (California Consumer Privacy Act of 2018) affords consumers residing in California additional rights with respect to their personal information. - -12.3. You have the right to inform you of (i) the categories of personal information we have collected about you over the past 12 months; (ii) the specific pieces of personal information we collect, use, and disclose about you; (iii) the categories of sources from which we collected your personal information; (iv) The categories of third parties to whom your personal information was disclosed; (v) the purposes for collecting your personal information; (vi) request deletion of your personal information, and (vii) not be discriminated against for exercising these rights. You may make these requests by contacting us at [friends@lightprotocol.com](mailto:friends@lightprotocol.com). Please note that these rights may be subject to limitations as outlined by applicable laws. In certain cases, we may need to deny your request in order to comply with our legal obligations or protect our rights. - -12.4. Light Protocol does not sell your personal information. - -12.5. We will not discriminate against you if you exercise your rights under the CCPA. - - - -13\. ADDITIONAL DISCLOSURES FOR INDIVIDUALS IN EUROPE - -13.1. If you are located in the European Economic Area ("EEA"), the United Kingdom, or Switzerland, you have certain rights and protections under the law regarding the processing of your personal data, and this section applies to you. - - - -13.2. Legal Basis for Processing - -13.2.1. When we process your personal data, we will do so in reliance on the following lawful bases: To perform our responsibilities under our contract with you (e.g., processing payments for and providing the products and services you requested). - -13.2.2. When we have a legitimate interest in processing your personal data to operate our business or protect our interests (e.g., to provide, maintain, and improve our products and services, conduct data analytics, and communicate with you). - -13.2.3. To comply with our legal obligations (e.g., to maintain a record of your consents and track those who have opted out of communications). - -13.2.4. When we have your consent to do so (e.g., when you opt in to receive communications from us). - -13.2.5. When consent is the legal basis for our processing your personal data, you may withdraw such consent at any time. - - - -13.3. DATA RETENTION - -We store other personal data for as long as necessary to carry out the purposes for which we originally collected it and for other legitimate business purposes, including to meet our legal, regulatory, or other compliance obligations. - - - -13.4. DATA SUBJECT REQUESTS - -Subject to certain limitations, you have the right to request access to the personal data we hold about you and to receive your data in a portable format, the right to ask that your personal data be corrected or erased, and the right to object to, or request that we restrict, certain processing. If you would like to exercise any of these rights, please contact us at [friends@lightprotocol.com](mailto:friends@lightprotocol.com). - - - -13.5. QUESTIONS OR COMPLAINTS - -Where we are not able to rectify a concern about our processing of personal data you have the right to lodge a complaint with the data protection authority of your place of residence. You can contact your Data Protection Authority using your respective link below: - -· EEA residents: [https://edpb.europa.eu/about-edpb/board/members\_en](https://edpb.europa.eu/about-edpb/board/members\_en) - -· UK residents: [https://ico.org.uk/global/contact-us/](https://ico.org.uk/global/contact-us/) - -· Switzerland residents: [https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html](https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html) - - - -14\. CONTACT - -If you have any questions about this Privacy Policy, You can contact us at email [friends@lightprotocol.com](mailto:friends@lightprotocol.com) referencing “Data Protection Enquiry". diff --git a/compression-docs/resources/sdks/client-development.md b/compression-docs/resources/sdks/client-development.md new file mode 100644 index 0000000..5556b83 --- /dev/null +++ b/compression-docs/resources/sdks/client-development.md @@ -0,0 +1,101 @@ + + +# Client Development + +Build client applications that interact with ZK Compression across web, Node.js, and native environments using our TypeScript and Rust SDKs. + +### Core Client SDKs + +
SDKLanguageDescription
@lightprotocol/stateless.jsTypeScriptCore compression SDK with RPC interface, compression/decompression operations, state tree management, and validity proof handling
@lightprotocol/compressed-tokenTypeScriptCompressed token operations including mint creation, transfers, SPL compression/decompression, and delegation handling
light-clientRustRPC client with Photon indexer API, and local test validator support.
+ +## Installation + +{% tabs %} +{% tab title="npm" %} +```sh +npm install --save \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @lightprotocol/zk-compression-cli +``` +{% endtab %} + +{% tab title="Yarn" %} +```sh +yarn add \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ + @solana/web3.js \ + @lightprotocol/zk-compression-cli +``` +{% endtab %} +{% endtabs %} + +{% code title="rust-client-sdks.toml" %} +```toml +[dependencies] +light-client = "0.13.1" +``` +{% endcode %} + +## Environments + +{% tabs %} +{% tab title="Localnet" %} +Start a local test-validator with the below command. It will start a single-node Solana cluster, an RPC node, and a prover node at ports 8899, 8784, and 3001. + +```bash +light test-validator +``` + +See the CLI Installation Guide for more. + +{% content-ref url="../cli-installation.md" %} +[cli-installation.md](../cli-installation.md) +{% endcontent-ref %} +{% endtab %} + +{% tab title="Devnet" %} +The `Rpc` connection is used to interact with the ZK Compression JSON RPC. It's a thin wrapper extending [Solana's web3.js `Connection` class](https://solana-labs.github.io/solana-web3.js/classes/Connection.html) with compression-related endpoints. + +Here's how you set up a connection: + +```typescript +import { createRpc } from '@lightprotocol/stateless.js'; + +// Local development +const connection = createRpc(); // Uses local test validator + +// DevNet +const connection = createRpc( + "https://devnet.helius-rpc.com?api-key=YOUR_KEY", +); + +// MainNet +const connection = createRpc( + "https://mainnet.helius-rpc.com?api-key=YOUR_KEY" +); +``` + +Find all ZK Compression related RPC methods below. + +{% content-ref url="../json-rpc-methods/" %} +[json-rpc-methods](../json-rpc-methods/) +{% endcontent-ref %} +{% endtab %} +{% endtabs %} + +## Examples + +{% embed url="https://github.com/Lightprotocol/example-web-client" %} + +{% embed url="https://github.com/Lightprotocol/example-nodejs-client" %} + +## Next Steps + +Get an overview of compressed tokens and dive right into the cookbook. + +{% content-ref url="broken-reference" %} +[Broken link](broken-reference) +{% endcontent-ref %} diff --git a/compression-docs/resources/sdks/program-development.md b/compression-docs/resources/sdks/program-development.md new file mode 100644 index 0000000..e4113c0 --- /dev/null +++ b/compression-docs/resources/sdks/program-development.md @@ -0,0 +1,50 @@ + + +# Program Development + +ZK Compression's Rust crates are published to [crates.io](https://docs.rs/releases/search?query=zk+compression) and can be found on [docs.rs](https://crates.io/search?q=zk%20compression) with the `light-` prefix. + +### Rust Crates + +* [`light-sdk`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk) — For Anchor and native programs. Includes CPI utilities, compressed account abstractions similar to anchor Account, and metadata structs for CPIs to the Light System program. +* [`light-sdk-pinocchio`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk-pinocchio) — For Pinocchio programs. Pinocchio-optimized SDK with compressed account abstractions and CPI utilities. + +For testing: + +* [`light-program-test`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/program-test) — Local test environment with indexer, prover, and LiteSVM. Use for fast test execution and unit/integration tests for your custom program. + +### Light Programs Overview + +{% hint style="info" %} +Your custom program invokes the _Light system program_ via Cross-Program Invocation (CPI) to leverage ZK Compression. +{% endhint %} + +
ProgramDescription
light-system-programEnforces compressed account layout, ownership checks, and validity proof verification. Used to create/write compressed accounts and PDAs
light-compressed-tokenSPL-compatible compressed token implementation with arbitrary compression/decompression support
account-compressionState and address tree implementation used by the Light System program
+ +### Version Requirements + +These are the required version for program development. + +* **Rust**: 1.86.0 or later +* **Solana CLI**: 2.2.15 +* **Anchor CLI**: 0.31.1 +* **Zk compression CLI**: 0.27.0 or later +* **Node.js**: 23.5.0 or later + +{% embed url="https://solana.com/developers/guides/getstarted/setup-local-development" %} + +{% content-ref url="../cli-installation.md" %} +[cli-installation.md](../cli-installation.md) +{% endcontent-ref %} + +### Examples + +{% embed url="https://github.com/Lightprotocol/program-examples?tab=readme-ov-file#examples" %} + +### Get Started + +Get an overview of compressed PDAs and create your custom program. + +{% content-ref url="../../compressed-pdas/create-a-program-with-compressed-pdas.md" %} +[create-a-program-with-compressed-pdas.md](../../compressed-pdas/create-a-program-with-compressed-pdas.md) +{% endcontent-ref %} diff --git a/compression-docs/resources/security.md b/compression-docs/resources/security.md deleted file mode 100644 index 60f6d0d..0000000 --- a/compression-docs/resources/security.md +++ /dev/null @@ -1,26 +0,0 @@ -# Security - -### Bug Bounty - -Light Protocol is hosting a [bug bounty program](https://immunefi.com/bug-bounty/light-protocol/information/). - -### Security Audits - -The Light protocol on-chain programs were audited by independent security firms Neodyme, OtterSec, Accretion, and Zellic. - -* [Accretion audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/accretion_v1_update_audit.pdf) (January '25) -* [Zellic audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/zellic_v1_audit.pdf) (September '24) -* [Neodyme audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/neodyme_v1_audit.pdf) (August '24) -* [OtterSec audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/ottersec_v1_audit.pdf) (July '24) - -A complete list of 3rd party audits can be found [here](https://github.com/Lightprotocol/light-protocol/tree/main/audits). - -### Groth16 Circuit Security - -The ZK Compression circuit was formally verified by Reilabs. See the report [here](https://github.com/Lightprotocol/light-protocol/blob/main/audits/reilabs_circuits_formal_verification_report.pdf). - -Information about the Trusted Setup Ceremony for the groth16 circuits is [here](https://github.com/Lightprotocol/gnark-mt-setup/blob/main/README.md). - -{% hint style="info" %} -**For additional information on Light Protocol's security policy, read** [**here**](https://github.com/Lightprotocol/light-protocol/blob/main/SECURITY.md)**.** -{% endhint %} diff --git a/compression-docs/whitepaper.md b/compression-docs/whitepaper.md new file mode 100644 index 0000000..06b8114 --- /dev/null +++ b/compression-docs/whitepaper.md @@ -0,0 +1,199 @@ +--- +description: Complete whitepaper introducing ZK Compression. +--- + +# Whitepaper + +*** + +### Scaling the Design Space for On-chain Applications with ZK Compression v0.1.0 + +**Light Protocol Team** + +#### Abstract + +This paper proposes Light Protocol, a set of smart contracts for Solana that introduces ZK Compression. + +Solana application developers can opt-in to compress their application’s on-chain state via the Light Protocol smart contracts. This reduces state cost by orders of magnitude while preserving the Solana L1's security, performance, and composability. + +For the Solana validator network, widespread adoption of ZK Compression could help solve the state growth problem by limiting the state held in active memory by validators relative to the total state produced. + +Finally, Light Protocol opens up a new design space for zero-knowledge-based protocols and applications on Solana; because ZK Compression stores data in zero-knowledge-friendly data structures, applications can efficiently prove custom off-chain computations over ZK-compressed state. + +### 1 The Problem: Expensive On-chain State + +For developers to scale their on-chain applications to large user bases, the marginal cost of data storage must be near zero. Solana has emerged as a leading Layer 1 blockchain, attracting application developers who aim to scale to large numbers of end users. + +However, storing data on the Solana L1 has become increasingly expensive for the network, and the cost trickles down to the application developer, limiting the design space for on-chain applications with low Lifetime Value (LTV) / Customer Acquisition Cost (CAC) ratios. + +### 2 ZK Compression + +When a Solana account gets compressed, its data is hashed and stored as a leaf in a sparse binary Merkle tree structure. The tree’s state root (i.e., a small fingerprint of all compressed accounts in the tree) is stored on the blockchain. + +The underlying compressed account data is stored off-chain, e.g., as call data in the cheaper Solana ledger space. To read or write compressed state, transactions provide the compressed off-chain data and a succinct zero-knowledge proof (validity proof). + +Light Protocol verifies the validity proof against the respective on-chain state root to ensure that the provided data was previously emitted via the protocol smart contracts. The succinctness property of the zero-knowledge proof (a Groth16 SNARK \[1]) ensures that the integrity of many compressed accounts can be verified on-chain with a constant proof size of 128 bytes, which is ideal for Solana’s highly constrained 1232-byte transaction size limit. + +### 3 Light Protocol System Architecture + +The transaction flow in Light Protocol consists of the following key components: + +1. **Off-chain state storage**: State is stored off-chain, e.g., as calldata in the Solana ledger. +2. **New Transactions specify state:** Transactions include the compressed data they read or write, the state tree accounts, a pointer to a recent on-chain state root, and a corresponding validity proof, all included in the transaction payload. +3. **Applications must invoke the Light Protocol system program to write compressed state.** + 1. The system program validates the state (verifies the validity proof, performing sum checks and ownership checks) + 2. It enforces an account schema resembling the layout of classic Solana accounts. + 3. The old compressed state is nullified (inserted into the nullifier queue). + 4. The new compressed state is appended to the state Merkle Tree and recorded as call data on the Solana ledger. + 5. Any newly created addresses are inserted into the address queue. +4. **Photon Indexer** nodes index and store events to make compressed account state available to clients. A new node can always sync with the latest compressed state by sequentially processing all historical transactions from Genesis. + +
A diagram illustrating the transaction flow in the Light Protocol system architecture. The off-chain client reads the compressed state from the indexer and constructs a transaction. The transaction, which includes the compressed state, state tree accounts, a pointer to a recent on-chain state root, and a validity proof, is sent to the caller program. The caller program invokes the Light System Program, which interacts with the Account Compression Program to validate the state, nullify the old compressed state, append the new compressed state to the state Merkle Tree, and record it as call data on the Solana ledger. The Account Compression Program also updates state roots and emits events. The indexer retrieves the compressed state and validity proof from the Solana ledger and makes this information available to the off-chain client, completing the transaction flow.
+ +A simplified compressed state transition can be expressed as: + +(𝑠𝑡𝑎𝑡𝑒, 𝑣𝑎𝑙𝑖𝑑𝑖𝑡𝑦𝑃𝑟𝑜𝑜𝑓) → 𝑠𝑡𝑎𝑡𝑒 𝑡𝑟𝑎𝑛𝑠𝑖𝑡𝑖𝑜𝑛 → 𝑠𝑡𝑎𝑡𝑒’ + +Here, _state’_ gets emitted onto the ledger. + +* In principle, new compressed account hashes (output state) get appended to specified state trees with each state transition. +* Old compressed account hashes (input state) get invalidated via insertion into a nullifier queue. +* Compressed state transitions are atomic and instantly final. + +
A diagram illustrating the compressed state transition process in the Light Protocol system. The process is initiated by atomic user interactions with the protocol. The user interacts with the Caller Program, which invokes the Light System Program. The Light System Program then interacts with the Account Compression Program to facilitate the state transition. The Account Compression Program performs three key actions: appending the output state to the state tree, invalidating the input state by inserting it into the nullifier queue, and adding new addresses to the address queue. The Forester node asynchronously empties the queues and updates the Merkle trees, maintaining the on-chain accounts, including the state tree and address space tree, which store the compressed state. The compressed state transition follows the sequence: (𝑠𝑡𝑎𝑡𝑒, 𝑣𝑎𝑙𝑖𝑑𝑖𝑡𝑦𝑃𝑟𝑜𝑜𝑓) → 𝑠𝑡𝑎𝑡𝑒 𝑡𝑟𝑎𝑛𝑠𝑖𝑡𝑖𝑜𝑛 → 𝑠𝑡𝑎𝑡𝑒', where state' gets emitted onto the ledger.
+ +**Foresters and Liveness** + +In an asynchronous process, _Forester_ nodes empty the nullifier queues. They achieve this by updating the leaf of the state Merkle tree, corresponding to the account hash previously inserted into the nullifier queue, with zeros (nullification). + +These queues enable instant finality of compressed state transitions but have a capped size. _Foresters_ are critical for protocol liveness and need to consistently empty queues. A full queue causes a liveness failure for all state stored in its associated state tree. A liveness failure is recovered by _Foresters_ emptying the queue again. Hosting a _Forester_ and foresting one’s trees is permissionless. + +#### 3.1 Compressed Account Model + +The Light System Program enforces an account layout that largely resembles Solana’s regular account model. Key differences include: + +* Each compressed account can be identified by its hash. +* Each write to a compressed account changes its hash. +* An address can optionally be set as a permanent unique ID of the compressed account. +* All compressed accounts are stored in sparse Merkle trees. Only the trees’ sparse state structure and roots (small fingerprints of all compressed accounts) are stored in the on-chain account space. + +These differences allow the protocol to store the underlying data off-chain (e.g., in the less expensive Solana ledger space) instead of in the more expensive on-chain account space. + +**Compressed PDA Accounts** + +Like regular accounts, each compressed account with a program-derived address (PDA) can be identified by its unique persistent address, represented as 32 bytes in the format of a PublicKey. + +Like PDAs, compressed account addresses don't belong to a private key; instead, they're derived from the program that owns them. + +
A diagram illustrating the structure of a compressed account in the Light Protocol system. The compressed account is owned by a program and is associated with a compressed PDA (Program Derived Address) account. The compressed account contains data bytes, lamports (account balance), an owner (the program that owns the account), and an address (represented as a public key). The owner of the compressed account has the authority to modify its data and transfer lamports from it. The compressed account is identified by its unique hash, which changes with each write operation. An address can be optionally set as a permanent unique identifier for the compressed account. Compressed accounts are stored in sparse Merkle trees, with only the trees' sparse state structure and roots stored in the on-chain account space, while the underlying data is stored off-chain.
+ +The compressed PDA account layout is similar to Solana's regular PDA account layout: Data, Lamports, Owner, and an address field. The data field stores program-owned state. In contrast to Solana’s account data field, Light Protocol enshrines a specific AccountData structure: Discriminator, Data, DataHash: + +
A diagram depicting the structure of a compressed PDA (Program Derived Address) account in the Light Protocol system. The compressed PDA account consists of a data field, which stores program-owned state, lamports (account balance), an owner field indicating the program that owns the account, and an address field represented as a public key. The AccountData structure within the compressed PDA account is composed of a discriminator (a unique identifier for the account type), the actual data (program state), and a DataHash (a hash of the account data). This AccountData structure is specific to the Light Protocol and differs from Solana's regular account data field.
+ +**Compressed PDA Account with AccountData** + +The Anchor framework reserves the first 8 bytes of a regular account's data field for the discriminator. This helps programs distinguish between different account types. The default compressed account layout is opinionated in this regard and enforces a discriminator in the Data field. + +The dataHash is what the Protocol uses to verify the integrity of program-owned data. This enables the protocol to be agnostic as to whether or how the data underlying the dataHash is stored or passed to the Light system program. The account owner program needs to ensure the correctness of the data hash. + +**Compressed Token Accounts** + +Light Protocol provides an implementation of a compressed token program built on top of ZK Compression. + +The Compressed Token program enforces a token layout that is compatible with the SPL Token standard. The program also supports SPL compression and decompression; existing SPL token accounts can be compressed and decompressed arbitrarily. + +**Fungible Compressed Accounts** + +Each compressed account can be identified by its hash, regardless of whether it has an address. By definition, whenever the data of a compressed account changes, its hash changes. + +In contrast to Solana’s regular account model, the address field is optional for compressed accounts because ensuring that a new account's address is unique incurs additional computational overhead. Addresses are not needed for fungible compressed accounts (i.e., tokens). + +#### 3.2 The Light Forest: Merkle Trees + +The protocol stores compressed state in a “forest” of multiple binary Merkle trees. + +**State Trees** + +A state tree is a binary concurrent Merkle tree \[2] that organizes data into a tree structure where each parent node is the hash of its two children nodes. This leads to a single unique root hash that allows for efficient cryptographic verification of the integrity of all the leaves in the tree. The hash of each compressed account is stored as a leaf in such a state tree: + +
 A diagram illustrating a binary Merkle tree with a depth of 2. The tree consists of a root node, two intermediate nodes (Node 0 and Node 1), and four leaf nodes (Leaf 0, Leaf 1, Leaf 2, and Leaf 3). The root node is the parent of Node 0 and Node 1, while Node 0 is the parent of Leaf 0 and Leaf 1, and Node 1 is the parent of Leaf 2 and Leaf 3. The tree represents a hierarchical structure where each parent node is derived from the hash of its two child nodes.

A small binary Merkle tree with depth 2.

+ +Each compressed account hash is a leaf in the state tree: + +
A diagram illustrating the composition of a compressed account hash, which is stored as a leaf in a state tree. The compressed account hash consists of the DataHash, Lamports (account balance), OwnerHash, Address, Discriminator, State tree hash, and Leaf Index. The DataHash represents the hash of the account data, Lamports store the account balance, OwnerHash is the hash of the account owner, Address is an optional unique identifier for the account, Discriminator helps distinguish between different account types, State tree hash identifies the specific state tree the account belongs to, and Leaf Index represents the position of the account within the state tree. The state tree hash and leaf index ensure that each compressed account hash is globally unique.

Compressed Account Hash Structure

+ +Compressed account hashes include the Public Key of the State tree's respective on-chain account (State tree hash) and the compressed account's position in the tree (leafIndex). This ensures that each account hash is globally unique. + +Each state tree has a corresponding on-chain State tree account that stores only the tree's final root hash and other metadata. Storing the final tree root hash on-chain allows the protocol to efficiently verify the validity of any leaf in the tree without needing to access the underlying compressed account state. The actual account data can thus be stored off-chain, e.g., in the much cheaper Solana ledger space, while preserving the security guarantees of the Solana L1. + +**Continuous Merkle Trees: Rollover and Rollover fees** + +For each new compressed account state, a new leaf is stored in the respective state Merkle tree. Given a tree depth of 26, the tree can support up to approximately 67 million leaves and, thus, 67 million compressed account state transitions. + +Compressed account hashes are added to the state tree until a threshold is reached. Once this threshold is met, a new state tree account and an associated nullifier queue account can be created to ensure continuous compressed transactions, a process known as a rollover. + +The costs for creating new on-chain accounts during a rollover are amortized across all transactions that add new leaves to the state tree. This marginal cost, which funds the next on-chain accounts, is usually very low and depends on the tree's depth and other factors affecting the size of the state tree and nullifier accounts. + +**Address Space Trees** + +Light Protocol supports the creation of provably unique addresses across a 254-bit address space. This is useful for applications requiring PDA-like uniqueness properties with compressed accounts, such as token-gating or creating unique identifiers. + +Transactions that create new addresses must provide a validity proof of exclusion from a given address space tree. Multiple independent address spaces can exist at the same time. Address space trees are indexed-concurrent binary Merkle tree data structures \[3]. These trees store exclusion ranges as linked lists in the tree leaves, enabling exclusion proofs across the 254-bit address space with trees of arbitrarily small depth. + +**Parallelism** + +State and address space trees can optionally be derived from and owned by custom Solana programs. This is useful for applications that want to control the write locks to their state trees. State tree accounts and nullifier accounts are separated to help elevate unnecessary write-locks. + +Suppose a tree _A_ with queue _A’_ and a tree _B_ with queue _B’._ Further, suppose a transaction _T_ nullifies a compressed account from tree _A_ but writes only to tree B; the transaction only write-locks _A’_ and _B_. _A_ and _B’_ do not get write-locked. + +**Limitations** + +ZK Compression reduces the data storage cost of all accounts to near zero, allowing developers to scale their application state to larger user bases on Solana. However, the following notable characteristics of ZK Compression can impact their utility for specific applications. ZK Compression transactions have: + +1. **Larger Transaction Size:** The transaction payload must include the compressed state to be read on-chain and a constant-size 128-byte validity proof. +2. **High Compute Unit Usage:** The protocol uses ZK primitives and on-chain hashing, which incur a relatively high base CU cost. If blocks are full, this can impact the inclusion rate of transactions. Future approaches to reducing CU cost can include optimizing the Merkle tree updates and hardware acceleration of cryptographic primitives and Syscalls used by the protocol. +3. **Per-transaction cost:** operating a _Forester_ node incurs additional hardware and transaction costs. The mechanism for efficiently nullifying multiple leaves in one Solana transaction can be improved significantly over time. + +### 4 A World with Light Protocol + +#### 4.1 Light Helps Developers Scale Their Applications + +The set of applications and protocols that benefit from ZK Compression is quite broad, including: + +1. Token-based applications and marketplaces, including applications for large-scale token distribution. +2. Applications that issue large numbers of digital assets, PDA accounts, and unique identifiers, such as decentralized social applications, name-service programs, and staking protocols. +3. Applications serving a user base with a low LTV/CAC ratio. +4. Payments infrastructure and applications. + +#### 4.2 Light Enables ZK-Applications + +Light Protocol is a shared bridge to merklelized, zk-friendly state. We believe that as more state becomes compressed via ZK Compression, Light Protocol will provide ZK-based applications and protocols with the option to bootstrap and scale on Solana across globally shared compressed state. + +We believe the design space for ZK-based applications is now wide open and will continue to expand. Some exciting technologies include: + +1. Identity Protocols, +2. ZK Coprocessors, +3. Based ZK Rollups + +### 5 Summary + +1. Light Protocol enables Solana developers to reduce their application state by orders of magnitude by introducing the ZK Compression primitive, which allows secure on-chain composability with off-chain state. +2. ZK Compression can contribute to solving Solana’s state growth problem. +3. Light seeks to enable a future with a thriving ZK ecosystem on Solana where new applications, marketplaces and computation designs can all interoperate, compose, and innovate permissionlessly over shared zk-compressed state. + +**Acknowledgments** + +We are grateful to Nicolas Pennie and Pedro Mantica for their extensive feedback, which has helped shape the design of ZK Compression, and to the team at Helius Labs as a whole for building the canonical ZK Compression Indexer implementation and RPC API. + +**Legal Disclaimer** + +Please read the entirety of this “Legal Disclaimer” section carefully. If you have any doubts about the action you should take, you should consult your own legal, financial, tax, or other professional advisor(s) before engaging in any activity herewith. This paper is for general information purposes only, to receive feedback and comments from the public. It does not constitute investment advice, a recommendation, or a solicitation to buy or sell any investment. It should not be used to evaluate the merits of making any investment decision. It should not be relied upon for accounting, legal, or tax advice or any investment recommendations. This paper reflects the current opinions of the Light Protocol Team. The opinions reflected herein outline current plans, which are subject to change at its discretion, without any guarantee of being updated. The success of outlined plans will depend on many factors outside the authors’ or Light Protocol Labs’ control, including but not limited to factors within the data and blockchain industries. Any statements about future events are based solely on the analysis of the issues described in this White Paper. That analysis might prove to be incorrect. + +**References** + +\[1] Groth, Jens. "On the size of pairing-based non-interactive arguments. " _Advances in Cryptology–EUROCRYPT 2016: 35th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Vienna, Austria, May 8-12, 2016, Proceedings, Part II 35_. Springer Berlin Heidelberg, 2016. + +\[2] _Concurrent Merkle Tree whitepaper.pdf_. (n.d.). Google Docs. [https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view) + +\[3] _Indexed Merkle Tree | Privacy-First ZKRollup | Aztec Documentation_. (n.d.). [https://docs.aztec.network/aztec/concepts/storage/trees/indexed\_merkle\_tree#indexed-merkle-tree-constructions](https://docs.aztec.network/aztec/concepts/storage/trees/indexed_merkle_tree#indexed-merkle-tree-constructions) diff --git a/docs/.gitbook/assets/Group 100.png b/docs/.gitbook/assets/Group 100.png deleted file mode 100644 index 74d6cce..0000000 Binary files a/docs/.gitbook/assets/Group 100.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 101.png b/docs/.gitbook/assets/Group 101.png deleted file mode 100644 index 1a321a4..0000000 Binary files a/docs/.gitbook/assets/Group 101.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 102.png b/docs/.gitbook/assets/Group 102.png deleted file mode 100644 index 035c577..0000000 Binary files a/docs/.gitbook/assets/Group 102.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 103 (1).png b/docs/.gitbook/assets/Group 103 (1).png deleted file mode 100644 index 6c7422d..0000000 Binary files a/docs/.gitbook/assets/Group 103 (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 103.png b/docs/.gitbook/assets/Group 103.png deleted file mode 100644 index 6c7422d..0000000 Binary files a/docs/.gitbook/assets/Group 103.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 49 (2).png b/docs/.gitbook/assets/Group 49 (2).png deleted file mode 100644 index 7960daf..0000000 Binary files a/docs/.gitbook/assets/Group 49 (2).png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 604.png b/docs/.gitbook/assets/Group 604.png deleted file mode 100644 index b62756d..0000000 Binary files a/docs/.gitbook/assets/Group 604.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 632697.png b/docs/.gitbook/assets/Group 632697.png deleted file mode 100644 index f744b74..0000000 Binary files a/docs/.gitbook/assets/Group 632697.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 632704.jpg b/docs/.gitbook/assets/Group 632704.jpg deleted file mode 100644 index 8786605..0000000 Binary files a/docs/.gitbook/assets/Group 632704.jpg and /dev/null differ diff --git a/docs/.gitbook/assets/Group 91.png b/docs/.gitbook/assets/Group 91.png deleted file mode 100644 index a8df7bb..0000000 Binary files a/docs/.gitbook/assets/Group 91.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 93.png b/docs/.gitbook/assets/Group 93.png deleted file mode 100644 index ab9879b..0000000 Binary files a/docs/.gitbook/assets/Group 93.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 96.png b/docs/.gitbook/assets/Group 96.png deleted file mode 100644 index 262190e..0000000 Binary files a/docs/.gitbook/assets/Group 96.png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 99 (1).png b/docs/.gitbook/assets/Group 99 (1).png deleted file mode 100644 index 60bdc18..0000000 Binary files a/docs/.gitbook/assets/Group 99 (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/Group 99.png b/docs/.gitbook/assets/Group 99.png deleted file mode 100644 index cc96bc4..0000000 Binary files a/docs/.gitbook/assets/Group 99.png and /dev/null differ diff --git a/docs/.gitbook/assets/Logo White on Blue.png b/docs/.gitbook/assets/Logo White on Blue.png deleted file mode 100644 index 6266fb1..0000000 Binary files a/docs/.gitbook/assets/Logo White on Blue.png and /dev/null differ diff --git a/docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png b/docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png deleted file mode 100644 index 8045247..0000000 Binary files a/docs/.gitbook/assets/Screenshot 2024-05-13 at 14.40.53.png and /dev/null differ diff --git a/docs/.gitbook/assets/image (1) (1) (1).png b/docs/.gitbook/assets/image (1) (1) (1).png deleted file mode 100644 index 11dfa5f..0000000 Binary files a/docs/.gitbook/assets/image (1) (1) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (1) (1).png b/docs/.gitbook/assets/image (1) (1).png deleted file mode 100644 index e4c30f1..0000000 Binary files a/docs/.gitbook/assets/image (1) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png deleted file mode 100644 index d7c5bc6..0000000 Binary files a/docs/.gitbook/assets/image (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (10).png b/docs/.gitbook/assets/image (10).png deleted file mode 100644 index d1ae1ed..0000000 Binary files a/docs/.gitbook/assets/image (10).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (11).png b/docs/.gitbook/assets/image (11).png deleted file mode 100644 index 2dc7c34..0000000 Binary files a/docs/.gitbook/assets/image (11).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (12).png b/docs/.gitbook/assets/image (12).png deleted file mode 100644 index ac15196..0000000 Binary files a/docs/.gitbook/assets/image (12).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (13).png b/docs/.gitbook/assets/image (13).png deleted file mode 100644 index 4edf535..0000000 Binary files a/docs/.gitbook/assets/image (13).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (14).png b/docs/.gitbook/assets/image (14).png deleted file mode 100644 index ddc68dd..0000000 Binary files a/docs/.gitbook/assets/image (14).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (15).png b/docs/.gitbook/assets/image (15).png deleted file mode 100644 index 605c189..0000000 Binary files a/docs/.gitbook/assets/image (15).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (16).png b/docs/.gitbook/assets/image (16).png deleted file mode 100644 index 8d139f5..0000000 Binary files a/docs/.gitbook/assets/image (16).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (17).png b/docs/.gitbook/assets/image (17).png deleted file mode 100644 index 1049820..0000000 Binary files a/docs/.gitbook/assets/image (17).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (18).png b/docs/.gitbook/assets/image (18).png deleted file mode 100644 index aab85ab..0000000 Binary files a/docs/.gitbook/assets/image (18).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (19).png b/docs/.gitbook/assets/image (19).png deleted file mode 100644 index b651a73..0000000 Binary files a/docs/.gitbook/assets/image (19).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (2) (1) (1).png b/docs/.gitbook/assets/image (2) (1) (1).png deleted file mode 100644 index dcffa1c..0000000 Binary files a/docs/.gitbook/assets/image (2) (1) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (2) (1).png b/docs/.gitbook/assets/image (2) (1).png deleted file mode 100644 index e4c30f1..0000000 Binary files a/docs/.gitbook/assets/image (2) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (2).png b/docs/.gitbook/assets/image (2).png deleted file mode 100644 index 6cf0af6..0000000 Binary files a/docs/.gitbook/assets/image (2).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (20).png b/docs/.gitbook/assets/image (20).png deleted file mode 100644 index f03ca82..0000000 Binary files a/docs/.gitbook/assets/image (20).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (21).png b/docs/.gitbook/assets/image (21).png deleted file mode 100644 index 727a4c9..0000000 Binary files a/docs/.gitbook/assets/image (21).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (22).png b/docs/.gitbook/assets/image (22).png deleted file mode 100644 index c8c0d86..0000000 Binary files a/docs/.gitbook/assets/image (22).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (23).png b/docs/.gitbook/assets/image (23).png deleted file mode 100644 index 3dead9a..0000000 Binary files a/docs/.gitbook/assets/image (23).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (24).png b/docs/.gitbook/assets/image (24).png deleted file mode 100644 index c34ff7f..0000000 Binary files a/docs/.gitbook/assets/image (24).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (25).png b/docs/.gitbook/assets/image (25).png deleted file mode 100644 index 9ab8366..0000000 Binary files a/docs/.gitbook/assets/image (25).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (26).png b/docs/.gitbook/assets/image (26).png deleted file mode 100644 index 8307cad..0000000 Binary files a/docs/.gitbook/assets/image (26).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (27).png b/docs/.gitbook/assets/image (27).png deleted file mode 100644 index 0c1d961..0000000 Binary files a/docs/.gitbook/assets/image (27).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (28).png b/docs/.gitbook/assets/image (28).png deleted file mode 100644 index 874b855..0000000 Binary files a/docs/.gitbook/assets/image (28).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (29).png b/docs/.gitbook/assets/image (29).png deleted file mode 100644 index ce98614..0000000 Binary files a/docs/.gitbook/assets/image (29).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (3) (1).png b/docs/.gitbook/assets/image (3) (1).png deleted file mode 100644 index ce0e647..0000000 Binary files a/docs/.gitbook/assets/image (3) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (3).png b/docs/.gitbook/assets/image (3).png deleted file mode 100644 index d9f386e..0000000 Binary files a/docs/.gitbook/assets/image (3).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (30).png b/docs/.gitbook/assets/image (30).png deleted file mode 100644 index 9322be2..0000000 Binary files a/docs/.gitbook/assets/image (30).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (31).png b/docs/.gitbook/assets/image (31).png deleted file mode 100644 index 431ff0b..0000000 Binary files a/docs/.gitbook/assets/image (31).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (32).png b/docs/.gitbook/assets/image (32).png deleted file mode 100644 index 46bfadf..0000000 Binary files a/docs/.gitbook/assets/image (32).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (33).png b/docs/.gitbook/assets/image (33).png deleted file mode 100644 index 304c7e3..0000000 Binary files a/docs/.gitbook/assets/image (33).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (34).png b/docs/.gitbook/assets/image (34).png deleted file mode 100644 index 72d4523..0000000 Binary files a/docs/.gitbook/assets/image (34).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (35).png b/docs/.gitbook/assets/image (35).png deleted file mode 100644 index 7003b26..0000000 Binary files a/docs/.gitbook/assets/image (35).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (36).png b/docs/.gitbook/assets/image (36).png deleted file mode 100644 index 074d3cf..0000000 Binary files a/docs/.gitbook/assets/image (36).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (37).png b/docs/.gitbook/assets/image (37).png deleted file mode 100644 index ed5751f..0000000 Binary files a/docs/.gitbook/assets/image (37).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (38).png b/docs/.gitbook/assets/image (38).png deleted file mode 100644 index 3df73f2..0000000 Binary files a/docs/.gitbook/assets/image (38).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (39).png b/docs/.gitbook/assets/image (39).png deleted file mode 100644 index 5eba656..0000000 Binary files a/docs/.gitbook/assets/image (39).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (4) (1).png b/docs/.gitbook/assets/image (4) (1).png deleted file mode 100644 index c776f54..0000000 Binary files a/docs/.gitbook/assets/image (4) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (4).png b/docs/.gitbook/assets/image (4).png deleted file mode 100644 index 2cef43a..0000000 Binary files a/docs/.gitbook/assets/image (4).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (40).png b/docs/.gitbook/assets/image (40).png deleted file mode 100644 index 6383adb..0000000 Binary files a/docs/.gitbook/assets/image (40).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (41).png b/docs/.gitbook/assets/image (41).png deleted file mode 100644 index d7c5bc6..0000000 Binary files a/docs/.gitbook/assets/image (41).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (42).png b/docs/.gitbook/assets/image (42).png deleted file mode 100644 index f5e7cba..0000000 Binary files a/docs/.gitbook/assets/image (42).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (5) (1).png b/docs/.gitbook/assets/image (5) (1).png deleted file mode 100644 index 605c189..0000000 Binary files a/docs/.gitbook/assets/image (5) (1).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (5).png b/docs/.gitbook/assets/image (5).png deleted file mode 100644 index 463216a..0000000 Binary files a/docs/.gitbook/assets/image (5).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (6).png b/docs/.gitbook/assets/image (6).png deleted file mode 100644 index 3659b25..0000000 Binary files a/docs/.gitbook/assets/image (6).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (7).png b/docs/.gitbook/assets/image (7).png deleted file mode 100644 index fc5b9b1..0000000 Binary files a/docs/.gitbook/assets/image (7).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (8).png b/docs/.gitbook/assets/image (8).png deleted file mode 100644 index 6318a01..0000000 Binary files a/docs/.gitbook/assets/image (8).png and /dev/null differ diff --git a/docs/.gitbook/assets/image (9).png b/docs/.gitbook/assets/image (9).png deleted file mode 100644 index 1c8b261..0000000 Binary files a/docs/.gitbook/assets/image (9).png and /dev/null differ diff --git a/docs/.gitbook/assets/image-2.png b/docs/.gitbook/assets/image-2.png deleted file mode 100644 index f5e7cba..0000000 Binary files a/docs/.gitbook/assets/image-2.png and /dev/null differ diff --git a/docs/.gitbook/assets/image.png b/docs/.gitbook/assets/image.png deleted file mode 100644 index 74dbc18..0000000 Binary files a/docs/.gitbook/assets/image.png and /dev/null differ diff --git a/docs/.gitbook/assets/light_architecture.png b/docs/.gitbook/assets/light_architecture.png deleted file mode 100644 index 490551f..0000000 Binary files a/docs/.gitbook/assets/light_architecture.png and /dev/null differ diff --git a/docs/README (1).md b/docs/README (1).md deleted file mode 100644 index 0a7762a..0000000 --- a/docs/README (1).md +++ /dev/null @@ -1,88 +0,0 @@ -# Terminology - -### Compressed Solana Program (CSP) - -A smart contract that interacts with ZK-compressed state via the LightLayer. This includes regular Anchor programs that execute fully on-chain and programs that run custom off-chain computation over compressed state. - - - -### Compressed State / Light State - -State that is orders of magnitude cheaper than regular "uncompressed" state while preserving the security, performance, and composability of the Solana L1. - -**Regular Solana state** lives in on-chain accounts. On-chain accounts are always loaded into the RAM of the Solana network and therefore incur a cost for the user. Users must pay for rent exemption at account creation by locking SOL based on the account size. This rent cost is prohibitive for applications and DePin networks where the incremental Lifetime Value of an individual user's on-chain state is lower than the cost of the state. - -With **compressed state**, only a much smaller root hash of the state (a unique fingerprint) is stored on-chain, while the underlying data is stored off-chain. - -Whenever a program or dApp interacts with compressed state, the Light smart contracts verify the integrity of the state against its root hash. - -By default, the underlying "raw" state gets permanently stored on the Solana ledger, thereby leveraging the security of the Solana blockchain for Data Availability (DA). - -To achieve this and to inherit Solana's parallelism, compressed state via the LightLayer is stored across _`n`_ **state trees** (concurrent Merkle trees). Each piece of data that is created or consumed in a transaction represents a single leaf of a state tree. All tree leaves get recursively hashed together so that only the tree's final 32-byte root hash needs to be stored on-chain. - -To verify the validity of many pieces of state (CompressedAccounts) inside a single Solana transaction, Light uses Zero-knowledge cryptography, enabling the client to compress all state proofs into one small validity proof with a constant size of 128 bytes. - - - -### Compressed Account - -Compressed accounts are the base primitive that developers interact with. They closely resemble the layout and utility of regular Solana accounts. Compressed accounts can be program-owned and can optionally have a permanent unique `address` (PDA). - -One difference to regular Solana accounts is that the `address` field is optional. By default, compressed accounts can be identified by their hash, with the hash inputs being the underlying account data plus the account's unique location (leafIndex) in its corresponding state tree. - -The optional unique `address` allows for verifiable uniqueness guarantees of compressed state, which can be useful for applications that require non-fungible state. - -Every transaction specifies which state trees it reads from (input) and writes to (output). - -When writing to a compressed account, the protocol consumes the current state and creates the new state. - -`input state (current) -> state transition -> output state (new)` - -A single transaction can read from _`n`_ (currently n≤8) compressed account inputs, and write to _`m`_ compressed account outputs, inheriting Solana's parallelism. - - - -### Merkle tree / State tree - -Merkle trees are the underlying data structure that allows for efficient verification of the integrity of the state. - -Light consists of a 'forest' of state trees. Each state tree has a corresponding on-chain account storing a sparse Merkle tree, the tree's metadata, and _`n`_ recent root hashes (a root hash is the final 32-byte hash resulting from hashing together all current leaves of the tree). One state tree can have many leaves, for example, a tree of height 32 has a total capacity of 2\*\*32 (\~4B) leaves. Each leaf represents the hash of the state of a compressed account as emitted in a previous transaction. - - - -### Forester node - -The protocol must invalidate the consumed state whenever a transaction consumes compressed account inputs. This is achieved by storing the consumed compressed account identifier hashes in an on-chain nullifier queue account. Each state tree has an associated nullifier queue. These queues have a specified length (default: n = 4800). When the queue is full, transactions involving input state from the respective state tree will fail. To ensure the liveness of the LightLayer, off-chain keepers (called Foresters) must periodically empty these queues. - -To do so, Foresters asynchronously update the respective leaves in the state tree with zero values and remove the leaves hashes from the nullifier queue. - -The nullification process is trustless and permissionless. - - - -### Photon node - -Photon is the canonical indexer implementation for the ZK Compression API, built and maintained by Helius Labs. You can access their open-source implementation here: [https://github.com/helius-labs/photon](https://github.com/helius-labs/photon). - - - -### Validity proof / ZKP - -At the system level, validity proofs mathematically prove the correctness of the input state that is read by a transaction. One of the most important properties of the proofs used within Light is their succinctness (read: short. 256 bytes / 128 bytes compressed). - - - -### Groth16 - -Groth16 is a Zero-Knowledge Proof (ZKP) system used within Light for efficient proof generation and verification. Groth16 proofs belong to the family of SNARKs. (**S**uccinct **N**on-interactive **Ar**guments of **K**nowledge). - - - - - - - - - - - diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 98f4255..0000000 --- a/docs/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -
- -
- -
- -## What is Light Protocol? - -Light is a protocol built on Solana that introduces **ZK compression,** a primitive that enables secure scaling directly on the L1. - -## **Why build with Light?** - -**Hyperscale state:** Developers and users can opt to compress their on-chain state, reducing state costs **by orders of magnitude** while preserving the security, performance, and composability of the Solana L1. - -**A new design space for computation**: In addition to classic on-chain compute, compressed state via Light natively supports custom ZK compute, enabling developers to build previously impossible computation designs on Solana. - -{% hint style="info" %} -In depth documentation for developers is provided at [https://www.zkcompression.com](https://www.zkcompression.com). -{% endhint %} - -### State cost reduction - -| Creation Cost | Regular Account | Compressed Account | -| -------------------- | --------------- | ------------------------------ | -| 100-byte PDA Account | \~ 0.0016 SOL | \~ 0.00001 SOL (160x cheaper) | -| 100 Token Accounts | \~ 0.2 SOL | \~ 0.00004 SOL (5000x cheaper) | - -## Core Features - -| **Minimal state cost** | Securely stores state on cheaper ledger space instead of the more expensive account space, allowing apps to scale to millions of users. | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **L1 security & performance** | Execution and data availability on Solana, preserving the performance and security guarantees of the L1. | -| **Composable** | Solana programs can mix and match between compressed and regular on-chain state, allowing atomic interaction with multiple programs, accounts, and compressed accounts. | -| **Custom ZK compute** | Leverage Light's on-chain contracts and plumbing, and tap into global, unified ZK-friendly state. | - -## ZK and Compression in a Nutshell: - -_**Compression**_**:** Only the [state roots](learn/core-concepts/state-trees.md) (small fingerprints of all [compressed accounts](learn/core-concepts/compressed-account-model.md)) are stored in on-chain accounts. The underlying data is stored on the cheaper Solana ledger. - -_**ZK**_**:** The protocol uses small zero-knowledge proofs (validity proofs) to ensure the integrity of the compressed state. This is all done under the hood. You can fetch validity proofs from [RPC providers](https://www.zkcompression.com/introduction/intro-to-development#rpc-connection) that support ZK Compression. - -## How to use this documentation - -This documentation provides a high-level introduction to Light Protocol and is a directory of resources for building with it. The in-depth developer documentation can be found at [https://www.zkcompression.com](https://www.zkcompression.com). - -{% hint style="info" %} -Light Protocol is being built in the [open](https://github.com/Lightprotocol/light-protocol), and Mainnet-Beta is live! Stay up-to-date on X with the teams contributing to ZK compression and its RPC implementation: [Light](https://twitter.com/LightProtocol) and [Helius](https://twitter.com/heliuslabs). -{% endhint %} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index 446dba8..0000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,37 +0,0 @@ -# Table of contents - -## Introduction - -* [Overview](README.md) -* [ZK Compression](introduction/zk-compression.md) -* [🗓️ Event: Solana Zero Hackathon](introduction/event-solana-zero-hackathon.md) - -## Learn - -* [Core concepts](learn/core-concepts/README.md) - * [Compressed Account Model](learn/core-concepts/compressed-account-model.md) - * [State Trees](learn/core-concepts/state-trees.md) - * [Validity Proofs](learn/core-concepts/validity-proofs.md) - * [Lifecycle of a Transaction](learn/core-concepts/lifecycle-of-a-transaction.md) - * [Limitations](learn/core-concepts/limitations.md) -* [In a Nutshell](learn/in-a-nutshell.md) - -## Developers - -* [Advanced: Computing over Light State with custom ZKPs](developers/advanced-computing-over-light-state-with-custom-zkps.md) -* [Devnet Addresses](developers/devnet-addresses.md) - -## Release Notes - -* [1.0.0 Mainnet-Beta](release-notes/1.0.0-mainnet-beta.md) -* [0.6.0](developers/release-notes/0.6.0.md) -* [0.5.0](developers/release-notes/0.5.0.md) -* [0.4.0](developers/release-notes/0.4.0.md) -* [0.3.0](developers/release-notes/0.3.0.md) - -## resources - -* [Security](resources/security.md) -* [Privacy Policy](resources/privacy-policy.md) -* [Terms of Use](resources/terms-of-use.md) -* [Terms of Use - Website](resources/terms-of-use-website.md) diff --git a/docs/developers/advanced-computing-over-light-state-with-custom-zkps.md b/docs/developers/advanced-computing-over-light-state-with-custom-zkps.md deleted file mode 100644 index a0d4f5b..0000000 --- a/docs/developers/advanced-computing-over-light-state-with-custom-zkps.md +++ /dev/null @@ -1,11 +0,0 @@ -# Advanced: Computing over Light State with custom ZKPs - -{% hint style="info" %} -This is an advanced section; if you don't need custom off-chain compute for your application, you can safely disregard this section. -{% endhint %} - -If you want to use custom off-chain compute with compressed accounts, you need to verify a custom ZKP in your on-chain program. - -All state that is compressed via Light Protocol natively enables efficient computation via ZKPs. - -If you can describe or translate your computation into a groth16 circuit, you can run that computation over all Solana L1 state that was compressed via Light. This prevents state fragmentation while enabling previously impossible types of applications and computation designs on Solana. diff --git a/docs/developers/devnet-addresses.md b/docs/developers/devnet-addresses.md deleted file mode 100644 index 35df83e..0000000 --- a/docs/developers/devnet-addresses.md +++ /dev/null @@ -1,31 +0,0 @@ -# Devnet Addresses - -### Devnet URLs - -
Network Address (RPC)https://devnet.helius-rpc.com?api-key=<api_key>
Photon RPC APIhttps://devnet.helius-rpc.com?api-key=<api_key>
- -### zkTestnet URLs (deprecated) - -{% hint style="info" %} -Note: zkTestnet has been deprecated in favor of our **Solana Devnet** deployment. Please use the public Solana Devnet Cluster for further development -{% endhint %} - -
Network address (RPC)https://zk-testnet.helius.dev:8899 (deprecated)
Photon RPC APIhttps://zk-testnet.helius.dev:8784 (deprecated)
Proverhttps://zk-testnet.helius.dev:3001 (deprecated)
- -### Program IDs and Accounts (from 27th Aug 2024 onward) - -
Light System ProgramSySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7
Compressed Token ProgramcTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m
Account Compression Programcompr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq
Shared Public State Treesmt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT
Shared Public Nullifier Queuenfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148
Shared Public Address Treeamt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2
Shared Public Address Queueaq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F
- -### Program IDs and Accounts (deprecated as of 27th Aug 2024) - -
Light System ProgramH5sFv8VwWmjxHYS2GB4fTDsK7uTtnRT4WiixtHrET3bN
Compressed Token ProgramHXVfQ44ATEi9WBKLSCCwM54KokdkzqXci9xCQ7ST9SYN
Account Compression ProgramCbjvJc1SNx1aav8tU49dJGHu8EUdzQJSMtkjDmV8miqK
Shared Public State Tree5bdFnXU47QjzGpzHfXnxcEi5WXyxzEAZzd1vrE39bf1W
Shared Public Nullifier Queue44J4oDXpjPAbzHCSc24q7NEiPekss4sAbLd8ka4gd9CZ
Shared Public Address TreeC83cpRN6oaafjNgMQJvaYgAz592EP5wunKvbokeTKPLn
Shared Public Address QueueHNjtNrjt6irUPYEgxhx2Vcs42koK9fxzm3aFLHVaaRWz
- -### Lookup Tables - -{% hint style="info" %} -[Lookup tables](https://solana.com/docs/advanced/lookup-tables) can be used with compressed accounts to reduce your transaction size. We provide a pre-initialized lookup table on Devnet that covers the Light's program IDs and accounts: -{% endhint %} - -
Default Lookup Table #1qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V
- -You can also create your own lookup tables. We provide a helper function that initializes your table with Light's default program IDs and accounts. We also have [the following guide on creating lookup tables](https://www.zkcompression.com/developers/typescript-client#creating-lookup-tables) in the TypeScript Client section. diff --git a/docs/developers/release-notes/0.4.0.md b/docs/developers/release-notes/0.4.0.md deleted file mode 100644 index 02ff9c9..0000000 --- a/docs/developers/release-notes/0.4.0.md +++ /dev/null @@ -1,26 +0,0 @@ -# 0.4.0 - -## ZK Testnet is Here! - -{% hint style="info" %} -_Note: It’s an alpha release, so bugs & breaking changes can occur. If you have questions, bugs, or feedback, check out the_ [_Helius_](https://discord.gg/Uzzf6a7zKr) _and_ [_Light_](https://discord.gg/CYvjBgzRFP) _Developer Discord for help. Or message us directly via_ [_Telegram_](https://t.me/swen\_light) -{% endhint %} - -Testnet details**, while currently deprecated,** are still visible in the [**Devnet Addresses section**](https://www.zkcompression.com/developers/devnet-addresses#zktestnet-urls-deprecated) - -**You can find a list of all crates here:** - -{% embed url="https://github.com/Lightprotocol/light-protocol/releases/tag/v0.4.0" %} - -### Tooling - -* [Local Development + Token CLI](https://github.com/Lightprotocol/light-protocol/tree/main/cli) -* [ZK Compression RPC](https://github.com/helius-labs/photon) - -### Active Feature Set - -* **Compressed accounts** (no upfront rent) -* **Compressed-token program**. Create + mint (tested for up to 25x token mint batch, \~5000x cheaper than SPL-token mints) -* **Compress/decompress** SOL and tokens -* **Ts-sdk**[\ - ](https://www.zkcompression.com/introduction/intro-to-development) diff --git a/docs/developers/release-notes/0.5.0.md b/docs/developers/release-notes/0.5.0.md deleted file mode 100644 index be53015..0000000 --- a/docs/developers/release-notes/0.5.0.md +++ /dev/null @@ -1,33 +0,0 @@ -# 0.5.0 - -## Solana Devnet is here! - -{% hint style="info" %} -_Note: It’s an alpha release, so bugs and breaking changes can occur. If you find bugs, have questions, or have feedback, check out the Helius and Light Developer Discord for help. You can also message us directly via_ [_Telegram_](https://t.me/swen\_light) -{% endhint %} - -This release includes various quality-of-life improvements, including for robustness and performance. - -You can find a detailed list of all changes here: - -{% embed url="https://github.com/Lightprotocol/light-protocol/releases/tag/v.0.5.0" %} - -## Migrating from zkTestnet - -1. The same `programIds` and account addresses as zkTestnet are used. The full details are listed in the [Devnet Addresses](../devnet-addresses.md) section -2. Airdrop yourself some Devnet SOL via the Solana CLI and deploy your program to the public Solana Devnet cluster -3. :tada: -4. If you have any issues migrating or deploying your program on Devnet, just [let us know](https://t.me/swen\_light)! - -### Tooling - -* [Development + Token CLI](https://github.com/Lightprotocol/light-protocol/tree/main/cli) -* [ZK Compression RPC](https://github.com/helius-labs/photon) (this is supported out-of-the-box via the CLI) - -### Active Feature set - -* **Compressed accounts** (no upfront rent) -* **Compressed-token program**. Create + mint (tested for up to 25x token mint batch, \~5000x cheaper than SPL-token mints) -* **Compress/decompress** PDA accounts and tokens -* **Ts-sdk** - diff --git a/docs/developers/release-notes/0.6.0.md b/docs/developers/release-notes/0.6.0.md deleted file mode 100644 index 5dc5ce1..0000000 --- a/docs/developers/release-notes/0.6.0.md +++ /dev/null @@ -1,26 +0,0 @@ -# 0.6.0 - -## A new release for Localnet and Devnet is here! - -{% hint style="info" %} -_Note: This is a beta release. If you find bugs, have questions, or have feedback, check out the Helius and Light Developer Discord for help. You can also message us directly via_ [_Telegram_](https://t.me/swen\_light)_._ -{% endhint %} - -This release includes various quality-of-life improvements, including for robustness and performance. - -### Breaking changes - -* **New program IDs**. The up-to-date list can be found [here](https://www.zkcompression.com/developers/devnet-addresses). -* **JS RPC client**: compressed account endpoints now get returned with a cursor that allows for proper pagination. therefore accounts must be accessed via `response.items.accounts` instead of `response.accounts`. - -You can find a detailed list of all changes here: - -{% embed url="https://github.com/Lightprotocol/light-protocol/releases/tag/v.0.6.0" %} - -### Upgrading from v0.5.0 - -* Redeploy your programs on devnet pointing to the [**new program IDs**](../devnet-addresses.md#program-ids-and-accounts-from-27th-aug-2024-onward). You may also need to update your JS client's use of `getCompressedAccounts` according to the breaking changes highlighted above. - -### Examples - -* We added a new simple name service program reference implementation! It uses the latest releases [**here**](https://github.com/Lightprotocol/light-protocol/tree/main/examples/name-service). diff --git a/docs/introduction/event-solana-zero-hackathon.md b/docs/introduction/event-solana-zero-hackathon.md deleted file mode 100644 index 7b2a3a7..0000000 --- a/docs/introduction/event-solana-zero-hackathon.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -hidden: true ---- - -# 🗓️ Event: Solana Zero Hackathon - -
- -{% hint style="info" %} -Solana Zero is a dedicated side track for the [Solana Radar hackathon](https://www.colosseum.org/radar). -{% endhint %} - -## Submissions are now closed! - -1. Use ZK Compression in your hackathon project. -2. If you did submit your project here or via the link below by September 8th, you can score in Solana Zero! 🎉 - -{% embed url="https://earn.superteam.fun/listings/hackathon/solana-zero-hackathon/" %} - -### Schedule - -| Text | Start | End | -| ------------------------------------------------------------------------------------------------- | ----------- | ----------- | -| Global Hackathon | 02 Sep 2024 | 02 Oct 2024 | -| Submissions ([submit here](https://earn.superteam.fun/listings/hackathon/solana-zero-hackathon/)) | 02 Oct 2024 | 08 Oct 2024 | -| Judging | 08 Oct 2024 | 15 Oct 2024 | - -## Resources - -* [ZK Compression documentation](https://www.zkcompression.com/) -* [Example token-escrow program](https://github.com/Lightprotocol/light-protocol/tree/main/examples/token-escrow/programs/token-escrow/src/escrow\_with\_pda) -* [Discord chat for the hackathon](https://discord.com/invite/qCv4Y7uYmh) -* [Project ideas](https://github.com/Lightprotocol/zk-compression-summer-hackathon/blob/main/ideas.md) - -## Judging - -The Radar Hackathon's [submission rules](https://www.colosseum.org/\_app/immutable/assets/Solana%20Radar%20Hackathon%20Official%20Rules%202024.8c044e21.pdf) apply. To be eligible for the side track, participants must register in the project registration form shared above and submit their project to the Radar Hackathon before October 8, 2024. Once all submissions are collected, we will distribute a list to the side-track judges for evaluation. - -Teams and individuals are evaluated on the following criteria: - -* Functionality -* Potential impact -* Novelty -* Design -* Extensibility - -After the judges complete individual evaluations, they will discuss and choose the winners based on the project's weighted scores. - -## Side Track Prizes - -| Place | Prize | -| ---------------------------------------------------------------------------- | -------------------------- | -| First Place | 1 winner up to $20k + Swag | -| Second Place | 1 team of $10k + Swag | -| Third Place | 2 teams of $5k each + Swag | -| All participants who submit a project that fulfills the eligibility criteria | $100 | - -{% hint style="info" %} -All winners will also receive personal help with VC intros and Go-to-Market strategy from [Mert](https://x.com/0xMert\_)! -{% endhint %} - -## FAQ - -
- -How do I submit? - -1. Submit your project [here](https://earn.superteam.fun/listings/hackathon/solana-zero-hackathon/) by October 08. - -
- -
- -What are the rules? - -The [Solana Foundation Rules](https://www.colosseum.org/\_app/immutable/assets/Solana%20Radar%20Hackathon%20Official%20Rules%202024.8c044e21.pdf) apply. - -Additionally, to be eligible to score in Solana Zero, your project must: - -* Use compressed tokens or compressed accounts in some capacity. -* Be submitted by October 8, 2024. -* Each participant can have a maximum of 1 project submission count towards scoring in the Solana Zero side track. -* Please note that the $100 participation bounty is awarded at our sole discretion, particularly as a measure to prevent Sybil attacks. - -
- -
- -Where can i access developer resources? - -* [ZK Compression documentation](zk-compression.md) -* [Light Protocol Monorepo](https://github.com/lightprotocol/light-protocol) -* [Example programs](https://github.com/Lightprotocol/light-protocol/tree/main/examples) -* Example clients ([web](https://github.com/Lightprotocol/example-web-client), [node](https://github.com/Lightprotocol/example-nodejs-client)) -* Introductory [Blog](https://www.helius.dev/blog/solana-builders-zk-compression) posts - -
- -
- -Where can I find ideas? - -We encourage you to build things that you're excited about building. - -For inspiration, we have compiled a list of interesting ideas [here](https://github.com/Lightprotocol/zk-compression-summer-hackathon/tree/main?tab=readme-ov-file). - -
- -
- -Where can I ask technical questions? - -1. Check out the [Light](https://discord.gg/CYvjBgzRFP) and [Helius](https://discord.gg/Uzzf6a7zKr) Developer Discord servers! - -
diff --git a/docs/introduction/zk-compression.md b/docs/introduction/zk-compression.md deleted file mode 100644 index e15a683..0000000 --- a/docs/introduction/zk-compression.md +++ /dev/null @@ -1,45 +0,0 @@ -# ZK Compression - -Light's core primitive is **ZK Compression**. This section is a directory guiding you to the relevant codebases, examples, and advanced guides. - -{% hint style="info" %} -We're hosting in-depth documentation and guides at [zkcompression.com](https://www.zkcompression.com) with the Helius team. -{% endhint %} - -### 1. Learn - -Explore our core concepts and get an overview of the protocol to understand the fundamentals of ZK Compression. - -{% embed url="https://www.zkcompression.com/learn/in-a-nutshell" %} - -### 2. Intro to development - -This guide has everything you need to know to start developing with ZK Compression on Solana. - -{% embed url="https://www.zkcompression.com/introduction/intro-to-development" %} - -### 3. TypeScript Client - -Our TypeScript library provides the tools you need to interact with ZK Compression using the JSON RPC API. - -{% embed url="https://www.zkcompression.com/developers/typescript-client" %} - -### 4. Wallet Integration for Compressed Tokens - -This section walks you through how to add compressed token support to your browser extension wallet. - -{% embed url="https://www.zkcompression.com/developers/add-compressed-token-support-to-your-wallet" %} - -### 5. JSON RPC Methods - -These pages outline all ZK Compression methods extending Solana's [default JSON RPC API](https://solana.com/docs/rpc). Helius Labs maintains the [canonical RPC API and indexer implementation](https://github.com/helius-labs/photon). - -{% embed url="https://www.zkcompression.com/developers/json-rpc-methods" %} - -### 6. Run a Node for ZK Compression - -This section covers setting up a node for development on public Networks. For public networks (i.e., Devnet, Mainnet-Beta), you can either work with an RPC infrastructure provider that supports ZK Compression, such as [Helius Labs](https://helius.xyz/), or run your own nodes (Photon RPC, Prover, or Forester). - -{% embed url="https://www.zkcompression.com/node-operators/run-a-node" %} -_Becoming an operator for any node type is permissionless._ -{% endembed %} diff --git a/docs/learn/core-concepts/README.md b/docs/learn/core-concepts/README.md deleted file mode 100644 index b13709d..0000000 --- a/docs/learn/core-concepts/README.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -hidden: true ---- - -# Core concepts - -Light Protocol's core primitive ZK Compression drastically reduces on-chain state costs while maintaining security, composability, and performance. ZK Compression allows developers to efficiently scale their applications to millions of users with the help of zero-knowledge proofs. - -In the following sections, we'll dive into the core concepts that make ZK Compression possible: - -* **Compressed Account Model**: Understand how compressed accounts differ from regular Solana accounts (not much!) and how they enhance efficiency. -* **State Trees**: Learn about the Merkle tree structure used to store compressed accounts and how it minimizes on-chain storage. -* **Validity Proofs**: Explore how the protocol uses zero-knowledge proofs to verify data validity while keeping proof sizes small. -* **Lifecycle of a Transaction**: Follow the journey of a ZK-compressed transaction from creation to execution. -* **Limitations and Considerations**: Discover the trade-offs and scenarios where ZK Compression may or may not be the best solution. - diff --git a/docs/learn/core-concepts/compressed-account-model.md b/docs/learn/core-concepts/compressed-account-model.md deleted file mode 100644 index 01dae51..0000000 --- a/docs/learn/core-concepts/compressed-account-model.md +++ /dev/null @@ -1,48 +0,0 @@ -# Compressed Account Model - -{% hint style="info" %} -This guide assumes that you're familiar with [Solana's account model](https://solana.com/docs/core/accounts). -{% endhint %} - -## Regular versus Compressed Accounts - -ZK compressed state is stored in compressed accounts. Compressed accounts are similar to regular Solana accounts but with four main differences: - -* Each compressed account can be identified by its hash -* Each write to a compressed account changes its hash -* An `address` can optionally be set as a permanent unique ID of the compressed account -* All compressed accounts are stored in [sparse state trees](state-trees.md). Only the tree's state root (i.e., a small fingerprint of all compressed accounts) is stored in the on-chain account space - -These differences allow the protocol to store states as calldata in the less expensive Solana ledger space instead of costly on-chain account space - -To understand the similarities and differences between Solana's regular account model and compressed accounts, let's first look at compressed accounts with Program-Derived Addresses (PDAs) - -> If you don't know what PDAs are, read [this explainer](https://solana.com/docs/core/pda) first - -## Compressed PDA Accounts - -Like regular accounts, each compressed PDA account can be identified by its unique persistent address, represented as 32 bytes in the format of a `PublicKey`. Like PDAs, compressed account addresses don't belong to a private key; rather, they're derived from the program that owns them - -

Compressed PDA Accounts

- -The compressed PDA account layout is similar to Solana's regular PDA account layout — it has the **Data**, **Lamports**, **Owner**, and **Address** fields. The **Data** field stores the program state. Notice the enshrined **AccountData** structure: **Discriminator**, **Data**, **DataHash**: - -

Compressed PDA Account with AccountData

- -The [Anchor](https://www.anchor-lang.com/) framework reserves the first 8 bytes of a regular account's data field for the discriminator. This helps programs distinguish between different program-owned accounts. The default compressed account layout is opinionated in this regard and enforces a discriminator in the Data field. You can ignore the **DataHash** field for now; we cover its importance for ZK Compression later. - -### Address & Hash - -The `address` field is optional for compressed accounts because ensuring that the address of a new account is unique incurs additional computational overhead, and not all use cases need the uniqueness property of addresses - -Instead, each compressed account can be identified by its hash, regardless of whether it has an address - -{% hint style="info" %} -* Use the address field wherever the state must be unique (such as for NFTs or certain PDAs) -* You don't need the address for any fungible state (e.g., fungible tokens) -{% endhint %} - -By definition, whenever the data of a compressed account changes, its hash changes. This impacts how developers interact with fungible state: - -* Check out the [examples](https://www.zkcompression.com/introduction/intro-to-development#build-by-example) section in the ZK Compression documentation to see what using hashes instead of addresses looks like in practice -* Visit the [State Trees](state-trees.md) section to understand why using the account's hash as its ID makes sense for the compression protocol diff --git a/docs/learn/core-concepts/lifecycle-of-a-transaction.md b/docs/learn/core-concepts/lifecycle-of-a-transaction.md deleted file mode 100644 index 6559e67..0000000 --- a/docs/learn/core-concepts/lifecycle-of-a-transaction.md +++ /dev/null @@ -1,35 +0,0 @@ -# Lifecycle of a Transaction - -ZK Compression transactions are fully compatible with Solana's Transaction and VersionedTransaction. There are three key nuances in building transactions with compressed accounts as compared to regular accounts: - -* Instructions must specify the list of all compressed accounts being read or written to. To read or write to a compressed account, the instruction must send the current account state on-chain and prove its validity. -* Each unique state tree that gets read or written to (via any compressed account) needs to be specified as per Solana's regular on-chain [account access lists](https://solana.com/docs/core/transactions#array-of-account-addresses). -* To read any compressed account state on-chain, the client must send a validity proof alongside the instruction data to the chain. Depending on your program logic, the validity proof can prove A) the validity of all specified read accounts and B) the non-existence of a specified PDA within the compressed address space, e.g., for creating a new compressed PDA account. - -We can express a transaction more generally as: - -`(state, validityProof) -> state transition -> state'` - -Here's what this looks like when updating a single compressed PDA account: - -

Simplified: Read and Write compressed accounts

- -In this example, we assume that the client previously created said compressed account and thereafter fetched its compressed account info from an [RPC node](broken-reference). - -The custom Solana program executing the state transition Data -> Data' should require its client to pack the instructions efficiently. In the above scenario, the total data that's sent to the chain is: `address (same)`, `owner program (same)`, `data`, `data'-data`, `validity proof.` - -The compressed account after its update looks like this: - -

Full representation of a compressed account with PDA

- -## On-chain Protocol Execution - -To write compressed state, a custom caller program must invoke the Light System Program via CPI. The system program then does the following: - -1. Runs relevant checks ([sum check](https://github.com/Lightprotocol/light-protocol/blob/main/programs/system/src/invoke/processor.rs#L64-L70), etc.) -2. [Verifies the validity proof](https://github.com/Lightprotocol/light-protocol/blob/main/programs/system/src/invoke/verify\_state\_proof.rs#L184-L190) -3. [Nullifies](https://github.com/Lightprotocol/light-protocol/blob/main/programs/system/src/invoke/processor.rs#L203-L208) the "old" leaf of the compressed account that is being written to -4. [Appends](https://github.com/Lightprotocol/light-protocol/blob/main/programs/system/src/invoke/processor.rs#L240-L249) the new compressed account hash to the state tree and advances the tree's state root -5. [Emits](https://github.com/Lightprotocol/light-protocol/blob/main/programs/system/src/invoke/processor.rs#L269-L276) the new "raw" compressed account state onto the Solana ledger - -An[ RPC node](https://www.zkcompression.com/node-operators/run-a-node#photon-indexer-node) then parses the transaction and compressed state and provides the read state to clients via the [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-methods) diff --git a/docs/learn/core-concepts/limitations.md b/docs/learn/core-concepts/limitations.md deleted file mode 100644 index 95a6c56..0000000 --- a/docs/learn/core-concepts/limitations.md +++ /dev/null @@ -1,62 +0,0 @@ -# Limitations - -Before using ZK Compression to scale your application state, consider the following limitations of compressed accounts: - -* [Larger Transaction Size](limitations.md#larger-transaction-size) -* [High Compute Unit Usage](limitations.md#high-compute-unit-usage) -* [Per-Transaction State Cost](limitations.md#state-cost-per-transaction) - -## General Recommendation - -{% hint style="info" %} -Consider which accounts in your application benefit from ZK Compression and which don't. You can use both types for different parts of your application! -{% endhint %} - -It may be preferred for an account _not_ to be permanently compressed if: - -* The account gets updated very frequently within a single block (e.g., shared liquidity pools in a DeFi protocol). -* You expect the lifetime number of writes to the same account to be very large (>>1000x). -* The account stores large amounts of data, and you need to access a large part of it (>1kb) inside one on-chain transaction. - -## **Larger Transaction Size** - -Solana's transaction size limit is 1232 Bytes. Transactions exceeding this limit will fail. ZK Compression increases your transaction size in two ways: - -* 128 bytes must be reserved for the validity proof, which is a constant size per transaction, assuming the transaction reads from at least one compressed account. -* You must send the account data you want to read/write on-chain. - -## **High Compute Unit Usage** - -{% hint style="info" %} -System CU usage: - -* \~100,000 CU for validity proof verification, which is a constant size per transaction, assuming the transaction reads from at least one compressed account -* \~100,000 CU system use (state tree [Poseidon](https://eprint.iacr.org/2019/458.pdf) hashing et al.) -* \~6,000 CU per compressed account read/write - -**Example**: a typical compressed token transfer uses around 292,000 CU. -{% endhint %} - -Higher CU usage can: - -* **Lead to usage limits:** The total CU limit per transaction is 1,400,000 CU, and the per-block write lock limit per State tree is 12,000,000 CU. -* **Require your users to increase their** [**priority fee**](https://solana.com/developers/guides/advanced/how-to-use-priority-fees) **during congestion:** Whenever Solana's global per-block CU limit (48,000,000 CU) is reached, validator clients may prioritize transactions with higher per-CU priority fees. - -## State Cost Per Transaction - -Each write operation incurs a small additional network cost. If you expect a single compressed account to amass a large amount of state updates, the lifetime cost of the compressed account may be higher than its uncompressed equivalent, which currently has a fixed per-byte rent cost at creation. - -{% hint style="info" %} -Whenever a [transaction](lifecycle-of-a-transaction.md) writes to a compressed account, it nullifies the previous compressed account state and appends the new compressed account as a leaf to the state tree. Both of these actions incur costs that add to Solana's base fee. -{% endhint %} - -
TypeLamportsNotes
Solana base fee5000 per signatureCompensates validators for processing transactions
Write new compressed account state ~300 per leaf (default)Depends on tree depth:
\left( 2^{\text{tree\_depth}} \times \text{tree\_account\_rent\_cost} \times \text{rollover\_threshold} \right)
~300 for the default depth of 26
Nullify old compressed account state5000 per transactionReimburses the cost of running a Forester transaction. The current default Forester node implementation can be found here
Create addresses5000 per transactionSame as nullify
- -## Next Steps - -Now you're familiar with the core concepts of ZK Compression, you're ready to take the next step! Dive into [building a program](https://www.zkcompression.com/introduction/intro-to-development#on-chain-program-development) or [application](https://www.zkcompression.com/introduction/intro-to-development#client-side-development) with ZK Compression, or learn how to [set up and run your own node](https://www.zkcompression.com/node-operators/run-a-node).\ -\ -For those interested in learning more about the fundamentals of ZK and its applications on Solana, we recommend reading the following: - -* [Zero-Knowledge Proofs: An Introduction to the Fundamentals](https://www.helius.dev/blog/zero-knowledge-proofs-an-introduction-to-the-fundamentals) -* [Zero-Knowledge Proofs: Its Applications on Solana](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana) diff --git a/docs/learn/core-concepts/state-trees.md b/docs/learn/core-concepts/state-trees.md deleted file mode 100644 index 8ba4124..0000000 --- a/docs/learn/core-concepts/state-trees.md +++ /dev/null @@ -1,17 +0,0 @@ -# State Trees - -The protocol stores compressed state in multiple state trees. - -{% hint style="info" %} -A state tree is a binary [Merkle tree](https://brilliant.org/wiki/merkle-tree/) that organizes data into a tree structure where each parent node is the hash of its two children nodes. This leads to a single unique root hash that allows for efficient cryptographic verification of the integrity of all the nodes, also referred to as leaves, in the tree. -{% endhint %} - -

A small binary Merkle tree (depth 2)

- -The hash of each [compressed account](compressed-account-model.md) is stored as a leaf in such a state tree: - -

Each compressed account hash is a leaf in the state tree

- -Note that each compressed account hash includes the public key of the state tree's respective on-chain account (i.e., `state tree hash`) and the compressed account's position in the tree (i.e., `leafIndex`). This ensures that each account hash is globally unique - -Each state tree has a corresponding on-chain state tree account that stores only the tree's final root hash and other metadata. Storing the final tree root hash on-chain allows the protocol to efficiently verify the validity of any leaf (compressed account) in the tree. The raw state can thus be stored as calldata in the much cheaper Solana ledger space while preserving the security guarantees of Solana. diff --git a/docs/learn/core-concepts/validity-proofs.md b/docs/learn/core-concepts/validity-proofs.md deleted file mode 100644 index 7738390..0000000 --- a/docs/learn/core-concepts/validity-proofs.md +++ /dev/null @@ -1,7 +0,0 @@ -# Validity Proofs - -Validity proofs are succinct zero-knowledge proofs (ZKPs) that can prove the existence of compressed accounts as leaves within state trees while maintaining a constant 128-byte size. These proofs are generated off-chain and verified on-chain, reducing Solana's overall computational burden. Note that ZK Compression uses [Groth16](https://docs.rs/groth16-solana/latest/groth16\_solana/), a renowned [pairing-based](https://en.wikipedia.org/wiki/Pairing-based\_cryptography) [zk-SNARK](https://www.helius.dev/blog/zero-knowledge-proofs-its-applications-on-solana#-zk-snarks-and-circuits), for its prover system - -

A Merkle proof path (purple nodes) consists of all sibling node hashes required to calculate the final root node.

- -The protocol leverages ZKP generation and verification under the hood, so you don't have to learn about ZK directly to use ZK Compression diff --git a/docs/learn/in-a-nutshell.md b/docs/learn/in-a-nutshell.md deleted file mode 100644 index 9aaea97..0000000 --- a/docs/learn/in-a-nutshell.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -description: This page provides a high-level overview of the Light Protocol architecture. ---- - -# In a Nutshell - -## High-level System Overview - -1. **State is stored as call data in** [**Solana's ledger**](https://www.helius.dev/blog/all-you-need-to-know-about-compression-on-solana#state-vs-ledger)**,** resulting in no rent cost -2. **Transaction Specify State:** Transactions define the state they access (read/write) and include it in the transaction payload -3. **State Validation:** - 1. Solana Programs [invoke](https://solana.com/docs/core/cpi) the [Light Protocol System Program](../developers/devnet-addresses.md#program-ids-and-accounts-from-27th-aug-2024-onward) to update compressed state - 1. **The protocol** [**validates**](core-concepts/validity-proofs.md) **the state** (validity of read state, sum checks, ownership checks) - 2. **The protocol enforces a schema:** Classic Accounts → Compressed Accounts. [Compressed accounts](core-concepts/compressed-account-model.md) have a layout similar to classic accounts -4. **State Updates:** At the end of a transaction, the new state is recorded as a log on the ledger -5. [**Photon RPC Nodes**](https://www.zkcompression.com/node-operators/run-a-node#photon-rpc-node)**:** These nodes index and persist the logs, making the compressed account state available to clients via the [ZK Compression RPC API](https://www.zkcompression.com/developers/json-rpc-methods) -6. **Forester nodes (cranks)**: Interact with the [Account Compression Program](../developers/devnet-addresses.md#program-ids-and-accounts-from-27th-aug-2024-onward) to empty queues, and roll-over trees, where compressed state is stored. diff --git a/docs/overview/core-concepts.md b/docs/overview/core-concepts.md deleted file mode 100644 index 17c5cd1..0000000 --- a/docs/overview/core-concepts.md +++ /dev/null @@ -1,2 +0,0 @@ -# Core concepts - diff --git a/docs/release-notes/1.0.0-mainnet-beta.md b/docs/release-notes/1.0.0-mainnet-beta.md deleted file mode 100644 index eb72a91..0000000 --- a/docs/release-notes/1.0.0-mainnet-beta.md +++ /dev/null @@ -1,9 +0,0 @@ -# 1.0.0 Mainnet-Beta - -This is a program and program-dependencies release. You can expect a stable API for those and deploy custom programs using ZK Compression in production. - -_Please note: Our SDK implementations (JS client SDKs and rust-SDK) continue to be v0 and are unaudited._ - -You can find a detailed list of all changes here: - -{% embed url="https://github.com/Lightprotocol/light-protocol/releases/tag/v.1.0.0" %} diff --git a/docs/resources/privacy-policy.md b/docs/resources/privacy-policy.md deleted file mode 100644 index 0697c36..0000000 --- a/docs/resources/privacy-policy.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -description: 'Last updated: September 13, 2024.' -noRobotsIndex: true ---- - -# Privacy Policy - -1\. PURPOSE OF THIS DOCUMENT - -1.1. This privacy policy ("Privacy Policy") describes how Light Protocol Labs ("Light Protocol," "we," "us," or "our") collects, uses, and shares information about you. This Privacy Policy applies whenever you use [https://docs.lightprotocol.com](https://docs.lightprotocol.com/) (the "Website" or “Service”), contact our support team, interact with us on social media, or engage with us in any other manner. - -1.2. We do not collect, use, and share information about you when using [https://lightprotocol.com](https://lightprotocol.com). - -1.3. We may update this Privacy Policy from time to time and therefore encourage you to review this Privacy Policy frequently. If any changes are made, we will update the revision date at the top of this policy, and in certain cases, we may provide additional notice (such as posting a notice on our Website or sending a notification). - -1.4. For any matters, relating to data protection you may contact in writing by e-mail [friends@lightprotocol.com](mailto:friends@lightprotocol.com). - - - -2\. CONTENTS - -COLLECTING OF INFORMATION\ -USE OF INFORMATION\ -RETENTION OF INFORMATION\ -YOUR RIGHTS\ -DISCLOSURE OF DATA\ -TRANSFERS OF INFORMATION\ -DATA SECURITY AND LIABILITY\ -MINORS PRIVACY\ -LINKS TO OTHER WEBSITES\ -YOUR CALIFORNIA PRIVACY RIGHTS\ -ADDITIONAL DISCLOSURES FOR INDIVIDUALS IN EUROPE\ -CONTACT - - - -3\. COLLECTING OF INFORMATION - -3.1. We collect personal information you provide directly to us. - -3.2. We collect data about how you access and interact with our Website including information about the device and network you use, such as your hardware model, operating system version, browser type, IP address, mobile network, and unique device identifiers. Additionally, we track your activity on the Website, such as access times, pages viewed, links clicked, and the page that directed you to our Website. - -3.3. When accessing the Website via a mobile device, we may also gather information like your device ID, mobile browser type, and operating system. Usage data is collected automatically and includes diagnostic details such as the time spent on each page and other related metrics. - -3.4. With your device’s permissions, we may collect precise location data. You can disable the collection of this information at any time by adjusting your device settings. - -3.5. We use cookies and similar tracking technologies to gather information about your usage of our Website. Cookies are small data files stored on your device that help us enhance your experience, track popular features, and monitor visits. You may instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. If you refuse Cookies, you may not be able to use some parts of our Service. - - - -4\. USE OF INFORMATION - -4.1. We use the information collected to provide, maintain, and improve our products, services, and Website. Additionally, we use the data to personalize your experience, respond to customer inquiries, and provide technical notices, security alerts, and support messages. - -4.2. Our use of the information extends to: - -4.2.1. Monitoring and analyzing trends, usage, and activities related to our Website. - -4.2.2. Detecting, investigating, and preventing fraudulent, illegal, or malicious activity to protect the rights and property of our company and others. - -4.2.3. Debugging and identifying errors in our Website. - -4.2.4. Fulfilling legal and financial obligations. - -4.2.5. We may also use the information for additional purposes described to you at the time the data was collected or for purposes like data analysis, identifying trends, and improving our services. - - - -5\. RETENTION OF INFORMATION - -5.1. We will retain your information only as long as necessary for the purposes outlined in this Privacy Policy and ay retain Usage Data for security improvements or legal obligations. - -5.2. We expect to delete your information (at the latest) once there is no longer any legal or regulatory requirement or legitimate business purpose. - - - -6\. YOUR RIGHTS - -You have certain data protection rights, including the right to: - -6.1. request access to, update, or delete your personal data, and manage your information directly within the Service. - -6.2. to correct incomplete or inaccurate data and request the restriction of processing in certain situations. - -6.3. to obtain information on how and why your personal data is processed. - -6.4. request that we stop processing your personal data and withdraw consent to direct marketing at any time. - -6.5. **the** right to be notified of any data breach that may affect your rights and freedoms. - -6.6. request the deletion of your personal data in specific circumstances, such as when it's no longer necessary for the original purpose (right to be forgotten). - - - -7\. DISCLOSURE OF DATA - -7.1. We may disclose your Data: - -7.1.1. in connection with business transactions. - -7.1.2. to comply with legal obligations, if we believe that disclosure is in accordance with, or required by, any applicable law or legal process, including lawful requests by public authorities to meet national security or law enforcement requirements - -7.1.3. if we believe that your actions are inconsistent with our user agreements or policies, if we believe that you have violated the law, or if we believe it is necessary to protect the rights, property, and safety of Light Protocol, our users, the public, or others. - -7.1.4. to protect the rights, property, or safety of the Company, Service, or users. - - - -8\. TRANSFERS OF INFORMATION - -8.1. Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction - -8.2. Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer - -8.3. The Company will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information. - - - -9\. DATA SECURITY AND LIABILITY - -9.1. We implement robust technical and organizational measures to safeguard your personal data from unauthorized access, manipulation, loss, or exposure. These security protocols are regularly updated and enhanced in line with evolving technological standards. - -9.2. Please be aware that transmitting data over the internet (e.g., through email communications) is not always completely secure, and we cannot be held responsible for any data shared with us via these methods. While we strive to protect your information, absolute security cannot be guaranteed. - -9.3. This privacy policy does not extend to third-party websites or any external links provided on our platform. We disclaim any responsibility or liability for the content or data protection practices of third-party websites. - - - -10\. MINORS PRIVACY - -10.1. Our Service does not address anyone under 18, and we do not knowingly collect information from minors. - -10.2. If you are a parent and aware that your minors has provided Personal Data, please contact Us. - -10.3. If We become aware that We have collected Personal Data from anyone under the age of 18 without verification of parental consent, We take steps to remove that information from Our servers. - - - -11\. LINKS TO OTHER WEBSITES - -11.1. Our Service may contain links to third-party websites that are not operated by us. - -11.2. We advise you to review the Privacy Policy of every site you visit. We have no control over and no responsibility for the content, privacy policies or practices of any third party sites or services. - - - -12\. YOUR CALIFORNIA PRIVACY RIGHTS - -12.1. This section applies to you, if you are a California Resident. - -12.2. The California Consumer Privacy Act or "CCPA" (California Consumer Privacy Act of 2018) affords consumers residing in California additional rights with respect to their personal information. - -12.3. You have the right to inform you of (i) the categories of personal information we have collected about you over the past 12 months; (ii) the specific pieces of personal information we collect, use, and disclose about you; (iii) the categories of sources from which we collected your personal information; (iv) The categories of third parties to whom your personal information was disclosed; (v) the purposes for collecting your personal information; (vi) request deletion of your personal information, and (vii) not be discriminated against for exercising these rights. You may make these requests by contacting us at [friends@lightprotocol.com](mailto:friends@lightprotocol.com). Please note that these rights may be subject to limitations as outlined by applicable laws. In certain cases, we may need to deny your request in order to comply with our legal obligations or protect our rights. - -12.4. Light Protocol does not sell your personal information. - -12.5. We will not discriminate against you if you exercise your rights under the CCPA. - - - -13\. ADDITIONAL DISCLOSURES FOR INDIVIDUALS IN EUROPE - -13.1. If you are located in the European Economic Area ("EEA"), the United Kingdom, or Switzerland, you have certain rights and protections under the law regarding the processing of your personal data, and this section applies to you. - - - -13.2. Legal Basis for Processing - -13.2.1. When we process your personal data, we will do so in reliance on the following lawful bases: To perform our responsibilities under our contract with you (e.g., processing payments for and providing the products and services you requested). - -13.2.2. When we have a legitimate interest in processing your personal data to operate our business or protect our interests (e.g., to provide, maintain, and improve our products and services, conduct data analytics, and communicate with you). - -13.2.3. To comply with our legal obligations (e.g., to maintain a record of your consents and track those who have opted out of communications). - -13.2.4. When we have your consent to do so (e.g., when you opt in to receive communications from us). - -13.2.5. When consent is the legal basis for our processing your personal data, you may withdraw such consent at any time. - - - -13.3. DATA RETENTION - -We store other personal data for as long as necessary to carry out the purposes for which we originally collected it and for other legitimate business purposes, including to meet our legal, regulatory, or other compliance obligations. - - - -13.4. DATA SUBJECT REQUESTS - -Subject to certain limitations, you have the right to request access to the personal data we hold about you and to receive your data in a portable format, the right to ask that your personal data be corrected or erased, and the right to object to, or request that we restrict, certain processing. If you would like to exercise any of these rights, please contact us at [friends@lightprotocol.com](mailto:friends@lightprotocol.com). - - - -13.5. QUESTIONS OR COMPLAINTS - -Where we are not able to rectify a concern about our processing of personal data you have the right to lodge a complaint with the data protection authority of your place of residence. You can contact your Data Protection Authority using your respective link below: - -· EEA residents: [https://edpb.europa.eu/about-edpb/board/members\_en](https://edpb.europa.eu/about-edpb/board/members\_en) - -· UK residents: [https://ico.org.uk/global/contact-us/](https://ico.org.uk/global/contact-us/) - -· Switzerland residents: [https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html](https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact.html) - - - -14\. CONTACT - -If you have any questions about this Privacy Policy, You can contact us at email [friends@lightprotocol.com](mailto:friends@lightprotocol.com) referencing “Data Protection Enquiry". diff --git a/docs/resources/security.md b/docs/resources/security.md deleted file mode 100644 index 02fdd5c..0000000 --- a/docs/resources/security.md +++ /dev/null @@ -1,24 +0,0 @@ -# Security - -### Bug Bounty - -Light Protocol is hosting a [bug bounty program](https://immunefi.com/bug-bounty/light-protocol/information/) in cooperation with Immunefi. - -### Security Audits - -The Light protocol programs were audited by independent security firms Neodyme, OtterSec, and Zellic. - -* Zellic audit report (September '24, to be published) -* [Neodyme audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/neodyme\_v1\_audit.pdf) (August '24) -* [OtterSec audit report](https://github.com/Lightprotocol/light-protocol/blob/main/audits/ottersec\_v1\_audit.pdf) (July '24) - -### Groth16 Circuit Security - -The ZK Compression circuit was formally verified by Reilabs. See our report [here](https://github.com/Lightprotocol/light-protocol/blob/main/audits/reilabs\_circuits\_formal\_verification\_report.pdf). - -You can find all information about the Trusted Setup Ceremony for the groth16 circuit [here](https://github.com/Lightprotocol/gnark-mt-setup/blob/main/README.md). - -{% hint style="info" %} -**For additional information on Light Protocol's security policy, read** [**here**](https://github.com/Lightprotocol/light-protocol/blob/main/SECURITY.md)**.** -{% endhint %} - diff --git a/docs/resources/terms-of-use-website.md b/docs/resources/terms-of-use-website.md deleted file mode 100644 index 831b90c..0000000 --- a/docs/resources/terms-of-use-website.md +++ /dev/null @@ -1,441 +0,0 @@ ---- -description: 'Last updated: September 13, 2024' -hidden: true ---- - -# Terms of Use - Website - - - -1\. SCOPE - -1.1. These Terms govern the use of Light Protocol Websites, and, any other related Agreement or legal relationship with the Owner in a legally binding way. The terms of use for the Protocol are set forth here. Capitalized words are defined in the relevant dedicated section of this document. - -1.2. The User must read this document carefully and keep a copy of them for reference. Some provisions in these Terms may only apply to certain categories of Users. - -1.3. BY ACCESSING OR USING THE SERVICE, YOU AGREE THAT YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE BOUND BY THE EULA. IF YOU DO NOT AGREE, PLEASE DO NOT USE THE SERVICE OR SITE. - -1.4. “Light Protocol Websites” is provided by Light Protocol Labs, which can be contacted via Owner contact email: [friends@lightprotocol.com](mailto:friends@lightprotocol.com). - -1.5. "Light Protocol Websites" refers to this website, including its subdomains and any other website through which the Owner makes its Service available; the Application Program Interfaces (API); the Service; any applications, sample and content files, source code, scripts, instruction sets or software included as part of the Service, as well as any related documentation. - -1.6. Unless otherwise specified, the terms of use detailed in this section apply generally when using Light Protocol Websites. - -1.7. Single or additional conditions of use or access may apply in specific scenarios and in such cases are additionally indicated within this document. - -1.8. By using Light Protocol Websites, Users confirm to meet the following requirements: - -1.8.1. There are no restrictions for Users in terms of being Consumers or Business Users; - -1.8.2. Users must be recognized as adult by applicable law; - -1.8.3. Minors may only use Light Protocol Websites under parental or adult supervision; - -1.8.4. Users aren’t located in a country that is subject to a U.S. government embargo, or that has been designated by the U.S. government as a “terrorist-supporting” country; - -1.8.5. Users aren’t listed on any U.S. government list of prohibited or restricted parties; - - - -2\. What the User should know at a glance - -2.1. We grant you permission to download the protocol and use it for your own purposes. However, your use of the Protocol is entirely at your own risk. You are solely responsible for deciding how to use and implement the Protocol and we have no control over and assume no responsibility for your use. We accept no liability for any losses that may arise in connection with your use of the Protocol. - -2.2. Please note that In particular, certain provisions may only apply to Consumers or to those Users that do not qualify as Consumers. Such limitations are always explicitly mentioned within each affected clause. In the absence of any such mention, clauses apply to all Users. - -2.3. Usage of Light Protocol Websites and the Service is age restricted: to access and use Light Protocol Websites and its - -2.4. Service the User must be an adult under applicable law. - -2.5. Minors may access Light Protocol Websites and use its Service only under parental or adult supervision. - - - -TERMS OF USE - -3\. Content on [Light Protocol Websites](http://www.lightprotocol.com/) - -3.1. Unless where otherwise specified or clearly recognizable, all content available on Light Protocol Websites is owned or provided by the Owner or its licensors. - -3.2. The Owner undertakes its utmost effort to ensure that the content provided on Light Protocol Websites infringes no applicable legal provisions or third-party rights. However, it may not always be possible to achieve such a result. In such cases, without prejudice to any legal prerogatives of Users to enforce their rights, Users are kindly asked to preferably report related complaints using the contact details provided in this document. - - - -4\. Rights regarding content on Light Protocol Websites - All rights reserved - -4.1. The Owner holds and reserves all intellectual property rights for any such content. - -4.2. Users may not, therefore, use such content in any way that is not necessary or implicit in the proper use of the Service. - -4.3. In particular, but without limitation, Users may not copy, download, share (beyond the limits set forth in these Terms), modify, translate, transform, publish, transmit, sell, sublicense, edit, transfer/assign to third parties or create derivative works from the content available on Light Protocol Websites, nor allow any third party to do so through the User or their device, even without the User's knowledge. - -4.4. Where explicitly stated on Light Protocol Websites, the User may download, copy and/or share some content available through Light Protocol Websites for its sole use and provided that the copyright attributions and all the other attributions requested by the Owner are correctly implemented. - -4.5. We grant you permission to download the protocol and use it for your own purposes. However, your use of the Protocol is entirely at your own risk. You are solely responsible for deciding how to use and implement the Protocol and we have no control over and assume no responsibility for your use. We accept no liability for any losses that may arise in connection with your use of the Protocol. - -4.6. Any applicable statutory limitation or exception to copyright shall stay unaffected. - - - -5\. Access to external resources - -5.1. Through Light Protocol Websites Users may have access to external resources provided by third parties. Users acknowledge and accept that the Owner has no control over such resources and is therefore not responsible for their content and availability. - -5.2. Conditions applicable to any resources provided by third parties, including those applicable to any possible grant of rights in content, result from each such third parties’ terms and conditions or, in the absence of those, applicable statutory law. - - - -6\. Acceptable use - -6.1. Light Protocol Websites and the Service may only be used within the scope of what they are provided for, under these Terms and applicable law. - -6.2. Users are solely responsible for making sure that their use of Light Protocol Websites and/or the Service violates no applicable law, regulations or third-party rights. - -6.3. Therefore, the Owner reserves the right to take any appropriate measure to protect its legitimate interests including by denying Users access to Light Protocol Websites or the Service, terminating contracts, reporting any misconduct performed through Light Protocol Websites or the Service to the competent authorities – such as judicial or administrative authorities - whenever Users engage or are suspected to engage in any of the following activities: violate laws, regulations and/or these Terms; infringe any third-party rights; considerably impair the Owner’s legitimate interests; - -6.4. offend the Owner or any third party. - - - -7\. Software license - -7.1. The software embedded in or related to Light Protocol Websites is provided under a some-rights-reserved license. - -7.2. This means that Users are granted broad rights, including but not limited to the rights to use, execute, copy or distribute the software, to the extent determined by such license. - -7.3. The terms of such license shall always prevail upon conflicting, divergent or inconsistent provisions of these Terms. - -7.4. Users may find further information regarding the license terms on [https://github.com/Lightprotocol](https://github.com/Lightprotocol). - - - -8\. API usage terms - -8.1. Users may access their data relating to Light Protocol Websites via the Application Program Interface (API). Any use of the API, including use of the API through a third-party product/service that accesses Light Protocol Websites, is bound by these Terms and, in addition, by the following specific terms:the User expressly understands and agrees that the Owner bears no responsibility and shall not be held liable for any damages or losses resulting from the User’s use of the API or their use of any third-party products/services that access data through the API. - - - -COMMON LIABILITY AND INDEMNIFICATION - -Unless otherwise explicitly stated or agreed with Users, the Owner’s liability for damages in connection with the execution of the Agreement shall be excluded, limited and/or reduced to the maximum extent permitted by applicable law. - - - -9\. Indemnification - -9.1. The User agrees to indemnify and hold the Owner and its subsidiaries, affiliates, officers, directors, agents, co-branders, partners and employees harmless from and against any claim or demand ⁠ — including but not limited to lawyer's fees and costs ⁠ — made by any third party due to or in relation with any culpable violation of these Terms, third-party rights or statutory provisions connected to the use of the Service by the User or its affiliates, officers, directors, agents, co-branders, partners and employees to the extent allowed by applicable law. - -9.2. The above also applies to any claims exercised by third parties (including but not limited to the Owner’s clients or customers) against the Owner related to Digital Products provided by the User such as, for instance, conformity claims. - - - -10\. Limitation of liability - -10.1. Unless otherwise explicitly stated and without prejudice to applicable law, Users shall have no right to claim damages against the Owner (or any natural or legal person acting on its behalf). - -10.2. This does not apply to damages to life, health or physical integrity, damages resulting from the breach of material contractual obligations such as any obligation strictly necessary to achieve the purpose of the contract, and/or damages resulting from intent or gross negligence, as long as Light Protocol Websites has been appropriately and correctly used by the User. - -10.3. Unless damages have been caused by way of intent or gross negligence, or they affect life, health or physical integrity, the Owner shall only be liable to the extent of typical and foreseeable damages at the moment the contract was entered into. - - - -AUSTRALIAN USERS LIABILITY AND INDEMNIFICATION - -11\. Nothing in these Terms excludes, restricts or modifies any guarantee, condition, warranty, right or remedy which the User may have under the Competition and Consumer Act 2010 (Cth) or any similar state and territory legislation and which cannot be excluded, restricted or modified (non-excludable right). To the fullest extent permitted by law, our liability to the User, includingliability for a breach of a non-excludable right and liability which is not otherwise excluded under these Terms, is limited, at the Owner’s sole discretion, to the re-performance of the services or the payment of the cost of having the services supplied again. - - - -US USERS LIABILITY AND INDEMNIFICATION - -12\. Disclaimer of Warranties - -12.1. Light Protocol Websites is provided strictly on an “as is” and “as available” basis. Use of the Service is at Users’ own risk. - -12.2. To the maximum extent permitted by applicable law, the Owner expressly disclaims all conditions, representations, and warranties — whether express, implied, statutory or otherwise, including, but not limited to, any implied warranty of merchantability, fitness for a particular purpose, or non-infringement of third-party rights. No advice or information, whether oral or written, obtained by the User from the Owner or through the Service will create any warranty not expressly stated herein. - -12.3. Without limiting the foregoing, the Owner, its subsidiaries, affiliates, licensors, officers, directors, agents, co-branders, partners, suppliers and employees do not warrant that the content is accurate, reliable or correct; that the Service will meet Users’ requirements; that the Service will be available at any particular time or location, uninterrupted or secure; that any defects or errors will be corrected; or that the Service is free of viruses or other harmful components. Any content downloaded or otherwise obtained through the use of the Service is downloaded at Users' own risk and Users shall be solely responsible for any damage to Users’ computer system or mobile device or loss of data that results from such download or Users’ use of the Service. - -12.4. The Owner does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the Service or any hyperlinked website or service, and the Owner shall not be a party to or in any way monitor any transaction between Users and third-party providers of products or services.The Service may become inaccessible or it may not function properly with Users’ web browser, mobile device, and/or operating system. The owner cannot be held liable for any perceived or actual damages arising from Service content, operation, or use of this Service. - -12.5. Federal law, some states, and other jurisdictions, do not allow the exclusion and limitations of certain implied warranties. The above exclusions may not apply to Users. This Agreement gives Users specific legal rights, and Users may also have other rights which vary from state to state. The disclaimers and exclusions under this agreement shall not apply to the extent prohibited by applicable law. - - - -13\. Limitations of liability - -13.1. To the maximum extent permitted by applicable law, in no event shall the Owner, and its subsidiaries, affiliates, officers, directors, agents, co-branders, partners, suppliers and employees be liable for: any indirect, punitive, incidental, special, consequential or exemplary damages, including without limitation damages for loss of profits, goodwill, use, data or other intangible losses, arising out of or relating to the use of, or inability to use, the Service; and any damage, loss or injury resulting from hacking, tampering or other unauthorized access or use of the Service or - -13.2. User account or the information contained therein; any errors, mistakes, or inaccuracies of content; personal injury or property damage, of any nature whatsoever, resulting from User access to or use of the Service; any unauthorized access to or use of the Owner’s secure servers and/or any and all personal information stored therein; any interruption or cessation of transmission to or from the Service; any bugs, viruses, trojan horses, or the like that may be transmitted to or through the Service; any errors or omissions in any content or for any loss or damage incurred as a result of the use of any content posted, emailed, transmitted, or otherwise made available through the Service; and/or the defamatory, offensive, or illegal conduct of any User or third party. In no event shall the Owner, and its subsidiaries, affiliates, officers, directors, agents, co-branders, partners, suppliers and employees be liable for any claims, proceedings, liabilities, obligations, damages, losses or costs in an amount exceeding the amount paid by User to the Owner hereunder in the preceding 12 months, or the period of duration of this agreement between the Owner and User, whichever is shorter. - -13.3. This limitation of liability section shall apply to the fullest extent permitted by law in the applicable jurisdiction whether the alleged liability is based on contract, tort, negligence, strict liability, or any other basis, even if the User has been advised of the possibility of such damage. - -13.4. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, therefore the above limitations or exclusions may not apply to the User. The terms give User specific legal rights, and User may also have other rights which vary from jurisdiction to jurisdiction. The disclaimers, exclusions, and limitations of liability under the terms shall not apply to the extent prohibited by applicable law. - - - -14\. Indemnification - -The User agrees to defend, indemnify and hold the Owner and its subsidiaries, affiliates, officers, directors, agents, co-branders, partners, suppliers and employees harmless from and against any and all claims or demands, damages, obligations, losses, liabilities, costs or debt, and expenses, including, but not limited to, legal fees and expenses, arising from User’s use of and access to the Service, including any data or content transmitted or received by User; User’s violation of these terms, including, but not limited to, User’s breach of any of the representations and warranties set forth in these terms; User’s violation of any third-party rights, including, but not limited to, any right of privacy or intellectual property rights; User’s violation of any statutory law, rule, or regulation; any content that is submitted from User’s account, including third party access with User’s unique username, password or other security measure, if applicable, including, but not limited to, misleading, false, or inaccurate information; User’s wilful misconduct; or statutory provision by User or its affiliates, officers, directors, agents, co-branders, partners, suppliers and employees to the extent allowed by applicable law. - - - -COMMON PROVISIONS - -15\. No Waiver - -15.1. The Owner’s failure to assert any right or provision under these Terms shall not constitute a waiver of any such right or provision. - -15.2. No waiver shall be considered a further or continuing waiver of such term or any other term. - -16\. Service interruption - -16.1. To ensure the best possible service level, the Owner reserves the right to interrupt the Service for maintenance, system updates or any other changes, informing the Users appropriately. - -16.2. Within the limits of law, the Owner may also decide to suspend or discontinue the Service altogether. If the Service is discontinued, the Owner will cooperate with Users to enable them to withdraw personal data or information and will respect Users' rights relating to continued product use and/or compensation, as provided for by applicable law. - -16.3. Additionally, the Service might not be available due to reasons outside the Owner’s reasonable control, such as “force majeure” events (infrastructural breakdowns or blackouts etc.). - - - -17\. Service reselling - -Users may not reproduce, duplicate, copy, sell, resell or exploit any portion of Light Protocol Websites and of its Service without the Owner’s express prior written permission, granted either directly or through a legitimate reselling programme. - - - -18\. Privacy policy - -For information about the use of their personal data, Users must refer to the privacy policy of Light Protocol Websites/privacy-policy which is hereby declared to be part of these Terms. - - - -19\. Intellectual property rights - -19.1. Without prejudice to any more specific provision of these Terms, any intellectual property rights, such as copyrights, trademark rights, patent rights and design rights related to Light Protocol Websites are the exclusive property of the Owner or its licensors and are subject to the protection granted by applicable laws or international treaties relating to intellectual property. - -19.2. All trademarks — nominal or figurative — and all other marks, trade names, service marks, word marks, illustrations, images, or logos appearing in connection with Light Protocol Websites are, and remain, the exclusive property of the Owner or its licensors and are subject to the protection granted by applicable laws or international treaties related to intellectual property. - - - -20\. Changes to these Terms - -20.1. The Owner reserves the right to amend or otherwise modify these Terms at any time. In such cases, the Owner will appropriately inform the User of these changes. - -20.2. Such changes will only affect the relationship with the User from the date communicated to Users onwards. - -20.3. The continued use of the Service will signify the User’s acceptance of the revised Terms. - -20.4. If Users do not wish to be bound by the changes, they must stop using the Service and may terminate the Agreement. - -20.5. The applicable previous version will govern the relationship prior to the User's acceptance. The User can obtain any previous version from the Owner. - -20.6. If legally required, the Owner will notify Users in advance of when the modified Terms will take effect. - - - -21\. Assignment of contract - -21.1. The Owner reserves the right to transfer, assign, dispose of by novation, or subcontract any or all rights or obligations under these Terms, taking the User’s legitimate interests into account. Provisions regarding changes of these Terms will apply accordingly. - -21.2. Users may not assign or transfer their rights or obligations under these Terms in any way, without the written permission of the Owner. - - - -22\. Contacts - -All communications relating to the use of Light Protocol Websites must be sent using [friends@lightprotocol.com](mailto:friends@lightprotocol.com) referencing “Terms of Use Enquiry”. - - - -23\. Severability - -Should any provision of these Terms be deemed or become invalid or unenforceable under applicable law, the invalidity or unenforceability of such provision shall not affect the validity of the remaining provisions, which shall remain in full force andeffect. - - - -US USER PROVISIONS - -24\. Any such invalid or unenforceable provision will be interpreted, construed and reformed to the extent reasonably required to render it valid, enforceable and consistent with its original intent. - - - -25\. These Terms constitute the entire Agreement between Users and the Owner with respect to the subject matter hereof, and supersede all other communications, including but not limited to all prior agreements, between the parties with respect to such subject matter. - - - -26\. These Terms will be enforced to the fullest extent permitted by law. - - - -EU USER PROVISIONS - -27\. Should any provision of these Terms be or be deemed void, invalid or unenforceable, the parties shall do their best to find, in an amicable way, an agreement on valid and enforceable provisions thereby substituting the void, invalid or unenforceable parts. - - - -28\. In case of failure to do so, the void, invalid or unenforceable provisions shall be replaced by the applicable statutory provisions, if so permitted or stated under the applicable law. - - - -29\. Without prejudice to the above, the nullity, invalidity or impossibility to enforce a particular provision of these Terms shall not nullify the entire Agreement, unless the severed provisions are essential to the Agreement, or of such importance that the parties would not have entered into the contract if they had known that the provision would not be valid, or in cases where the remaining provisions would translate into an unacceptable hardship on any of the parties. - - - -30\. These Terms are drawn up and revised in English. Other language versions of these Terms are provided for information purposes only. In the event of any inconsistency between different linguistic versions, the original version shall always prevail. - - - -31\. These Terms are governed by the law of the place where the Owner is based, as disclosed in the relevant section of this document, without regard to conflict of laws principles. - - - -32\. However, regardless of the above, if the law of the country that the User is located in provides for higher applicable consumer protection standards, such higher standards shall prevail. - - - -33\. Exception for Consumers in Switzerland: If the User qualifies as a Consumer in Switzerland, Swiss law will apply. - - - -34\. Exception for Consumers in Brazil: If the User qualifies as a Consumer in Brazil and the product and/or service is commercialized in Brazil, Brazilian law will apply. - - - -VENUE OF JURISDICTION - -35\. The exclusive competence to decide on any controversy resulting from or connected to these Terms lies with the courts of the place where the Owner is based, as displayed in the relevant section of this document. - - - -36\. Exception for Consumers in Europe - -The above does not apply to any Users that qualify as European Consumers, nor to Consumers based in the United Kingdom, Switzerland, Norway or Iceland. - - - -37\. Exception for Consumers in Brazil - -The above does not apply to Users in Brazil that qualify as Consumers. - - - -38\. UK Consumers - -38.1. Consumers based in England and Wales may bring legal proceedings in connection with these Terms in the English and Welsh courts. - -38.2. Consumers based in Scotland may bring legal proceedings in connection with these Terms in either the Scottish or the English courts. - -38.3. Consumers based in Northern Ireland may bring legal proceedings in connection with these Terms in either the Northern Irish or the English courts. - - - -39\. US Users - -39.1. Each party specifically waives any right to trial by jury in any court in connection with any action or litigation. - -39.2. Any claims under these terms shall proceed individually and no party shall join in a class action or other proceeding with or on behalf of others. - - - -US USERS - -40\. Surviving provisions - -40.1. This Agreement shall continue in effect until it is terminated by either Light Protocol Websitess or the User. - -40.2. Upon termination, the provisions contained in these Terms that by their context are intended to survive termination or expiration will survive, including but not limited to the following: - -40.2.1. the User’s grant of licenses under these Terms shall survive indefinitely; - -40.2.2. the User’s indemnification obligations shall survive for a period of five years from the date of termination; - -40.2.3. the disclaimer of warranties and representations, and the stipulations under the section containing indemnity and limitation of liability provisions, shall survive indefinitely. - - - -ACCESSIBILITY - -41\. The Owner is committed to making the content accessible to Users with disabilities. If Users have a disability and are unable to access any portion of Light Protocol Websitess due to their disability, they should give a notice including a detailed description of the issue encountered. - - - -42\. If the issue is readily identifiable and resolvable in accordance with industry-standard information technology tools and techniques, the Owner commits to promptly address it. - - - -\ - - - - -DEFINITIONS AND LEGAL REFERENCES - -Light Protocol Websitess - -The property that enables the provision of the Service on or [https://docs.lightprotocol.com](https://docs.lightprotocol.com/) or [www.lightprotocol.com](http://www.lightprotocol.com/). - - - -Agreement - -Any legally binding or contractual relationship between the Owner and the User, governed by these Terms. - - - -Brazilian (or Brazil) - -Applies where a User, regardless of nationality, is in Brazil. - - - -Business User - -Any User that does not qualify as a Consumer. - - - -Digital Product - -Is a Product that consists of: content produced and supplied in digital form; and/or - -a service that allows for the creation, processing, storing or accessing data in a digital form or the sharing or any other form of interaction with digital data uploaded or created by the User or any other user of [www.lightprotocol.com](http://www.lightprotocol.com/) or [https://docs.lightprotocol.com](https://docs.lightprotocol.com/). - - - -European (or Europe) - -Applies where a User, regardless of nationality, is in the EU. - - - -Owner (or We) - -Indicates Light Protocol Labs that provides [www.lightprotocol.com](http://www.lightprotocol.com/), [https://docs.lightprotocol.com](https://docs.lightprotocol.com/) and/or the Service to Users. - - - -Service - -The service provided by www.lightprotocol.com as described in these Terms and on [www.lightprotocol.com](http://www.lightprotocol.com/)or [https://docs.lightprotocol.com](https://docs.lightprotocol.com/). - - - -Terms - -All provisions applicable to the use of www.lightprotocol.com and/or the Service as described in this document, including any other related documents or agreements, and as updated from time to time. - - - -User (or You) - -Indicates any natural person or legal entity using [www.lightprotocol.com](http://www.lightprotocol.com/) or [https://docs.lightprotocol.com](https://docs.lightprotocol.com/). - - - -Consumer - -Consumer is any User qualifying as such under applicable law. - - - - diff --git a/docs/resources/terms-of-use.md b/docs/resources/terms-of-use.md deleted file mode 100644 index c6f7956..0000000 --- a/docs/resources/terms-of-use.md +++ /dev/null @@ -1,287 +0,0 @@ ---- -description: 'Last updated: September 14, 2024.' -hidden: true -noRobotsIndex: true ---- - -# Terms of Use - -Heads up: We grant you permission to download the protocol, use it for your own purposes. - -Please read these Terms of Use carefully before you start to use the Protocol. By using the Protocol, you accept and agree to be bound and abide by these Terms of Use. If you do not want to agree to these Terms of Use, you must not use the Protocol. - -Please send all communications relating to these Terms of Use to [friends@lightprotocol.com](mailto:friends@lightprotocol.com) referencing “Terms of Use Enquiry”. - -*** - -1\. ACCEPTANCE OF THE TERMS OF USE - -1.1. These terms of use are entered into by and between you and Light Protocol Labs (“Light Protocol”, “we” or “us”). The following terms of use, together with any documents namely incorporated by reference (collectively, these “Terms of Use”), govern the use by you (“you”, and in its variants including “your”) of Light Protocol’s Solana based protocol and its associated software and documentation, including ZK Compression (collectively, the “Protocol”) made available on GitHub. - -1.2. These Terms of Use apply exclusively to the Protocol and does not cover any other protocols or software available from Light Protocol or any other parties, whether accessible through GitHub or elsewhere. We do not own, control, or bear any responsibility for these third parties materials and tools, such as protocols or smart contracts, namely the Solana Protocol, which is controlled, maintained, and/or operated by third parties. Please consult the relevant third parties’ user agreements for information regarding your rights and the associated risks with your use of and access to the respective protocols and related materials. - -1.3. When the user uses the Protocol under the authority of another person or entity, such as an employer, then “you” (and any related terms like “your”) refers to the party on whose behalf the Protocol is being used. Additionally, if someone else is using the Protocol on your behalf, you accept full responsibility for their actions or inactions, as though they were your own. - - - -2\. AMENDMENTS TO THE TERMS OF USE - -2.1. We may update these Terms of Use at any time. The changes will take effect immediately upon posting and apply for all use of the Protocol thereafter. - -2.2. Changes to the Governing Law and dispute resolution section will not apply to any disputes known before the date of posting. - -2.3. By continuing to use the Protocol after updates, you agree to the revised terms. It is your responsibility to check for updates each time you use the Protocol, as they are binding on you. - - - -3\. PROTOCOL FUNCTIONALITY, SECURITY AND RISKS - -3.1. The Protocol is an open-source solution to network congestion and data storage costs on Solana in a way, that it enables compressed transactions and accounts. This minimises cost by securely storing specific kinds of data on cheaper ledger space instead of the more expensive account space. - -3.1.1. The Protocol includes the system program and compressed token program, which are made available publicly, and runs on open-source self executing smart contracts. - -3.1.2. The Protocol requires keeper nodes called "foresters" (“Forester” or “Foresters”) to function. The Foresters constantly empties queues of transactions in an asynchronous process to ensure network liveness. Foresters have no influence on compressed transactions or accounts and do not interact with users. - -3.1.3. The Protocol is not centrally operated due to the use of a multi-signature system, with its addresses made publicly available here \ - -3.2. We prioritize the security of the Protocol and have implemented measures to mitigate potential risks, as detailed in our [security section](broken-reference). - -3.2.1. However, since the Protocol is built on open-source software, it may contain bugs or vulnerabilities that could adversely affect its functionality. In rare but severe cases, these issues could lead to the loss of your digital assets or your ability to access your digital wallet. - -3.2.2. You recognize that hackers or other malicious individuals may attempt to disrupt the Protocol or your use of it through various means, including but not limited to deploying malicious software, conducting denial-of-service attacks, performing Sybil attacks, or engaging in spoofing activities. - -3.2.3. By using the Protocol, you acknowledge and accept these inherent risks. You agree to release us from any liability and hold us harmless for any losses you may incur as a result of such vulnerabilities or malicious activities. - -3.2.4. You acknowledge that the Protocol may utilize, incorporate, or provide links to certain open-source components. Your use of the Protocol is subject to any applicable open-source licenses governing these components, and you agree to comply with all such license terms. - -3.3. Light Protocol does not hold your digital assets, nor does it take custody of them. - -3.3.1. We have no access to your assets or funds. Neither the Protocol or Light Protocol has control over the private keys of any assets belonging to users. It is a user’s responsibility to ensure to maintain control of her digital assets and she have sole responsibility for exchanging them. - -3.3.2. You understand and agree that you are solely responsible for any losses arising from your digital asset transactions and exchanges. You commit to indemnify, defend, and hold us harmless from any claims or losses that you or others may incur as a result of your digital asset transactions, even if you executed these transactions by following documenation or instructions available in the Protocol. - -3.3.3. We are not your financial advisors and do not offer any investment advice concerning your use or exchange of digital assets. - -3.4. Light Protocol is not a financial institution, and we have no fiduciary duty to you. - - - -4\. INTELLECTUAL PROPERTY RIGHTS OF THE PROTOCOL - -4.1. The Protocol and its entire contents, features, and functionality are owned by Light Protocol, its licensors, or other providers of such material and are protected by EU and international copyright, trademark, patent, trade secret, and other intellectual property or proprietary rights laws. - -4.2. The Protocol is licensed under the terms of the Apache License, Version 2.0, available at http://www.apache.org/licenses/ and GNU General Public License 3.0, available at https://www.gnu.org/licenses/gpl-3.0.en.html (together “Licenses”), incorporated herein by reference and, and together with these Terms of Use. In the event of any conflict between the Licenses’ terms and these Terms of Use, the terms of the Licenses will prevail. - -4.3. You are afforded broad rights under these Terms of Use and the Licenses, including the ability to use, execute, copy, and distribute the Protocol as specified by the Licenses and these Terms of Use: - -4.3.1. We authorize you to download and utilize the Protocol for your own purposes. Please be aware that you use the Protocol entirely at your own risk. You are solely responsible for determining how you employ and implement the Protocol. We do not control or assume responsibility for your use and disclaim any liability for losses that may result from it. - -4.3.2. If you print, copy, modify, download, or otherwise use any part of the Protocol in violation of these Terms of Use, your permission to use the Protocol will terminate immediately. You must cease all use and destroy any copies or derivatives of the software you have obtained or created. Unauthorized use not expressly permitted by the Licenses’ terms or these Terms of Use constitutes a breach and may violate copyright, trademark, and other applicable laws. - -4.3.3. We reserve the exclusive right to modify or withdraw the Protocol at our sole discretion without prior notice. We will not be liable if the Protocol, in whole or in part, is unavailable at any time or for any period. From time to time, we may also restrict access to certain parts or the entire Protocol. - -4.3.4. You are responsible for making all necessary arrangements to access, download, and use the Protocol. - - - -5\. USE OF THE PROTOCOL - -5.1. The Protocol may only be used within the scope of what lawful purposes it is provided for, under these Terms and applicable law. - -5.2. We make no claims that the Protocol or any of its content is accessible or appropriate in your country or region, is accurate, complete, or useful. If you choose to rely on it, you do so entirely at your own risk. We are not responsible or liable for any consequences resulting from your or others' reliance on the Protocol. - -5.3. We reserve the exclusive right to modify or discontinue the Protocol at our sole discretion and without prior notice. We shall not be held liable if any part or the entirety of the Protocol becomes unavailable at any time or for any duration, regardless of the reason. - -5.4. You are responsible for making all necessary arrangements to access, download, and use the Protocol. - -5.5. By using the Protocol, you confirm and warrant you meet the following requirements, and if you do not fulfill these requirements, you must refrain from using the Protocol: - -5.5.1. you are 18 years of age or older, except when using the Protocol under the supervision of a parent or authorised adult guardian; - -5.5.2. you affirm that you are legally competent to enter into a binding agreement with Light Protocol and that you meet all the eligibility criteria mentioned above; - -5.5.3. you are solely responsible for making sure that your use of the Protocol violates no applicable law and your local law, regulations or third-party rights; - -5.5.4. you are not citizen, resident, or located in a country that is subject to a United States, European Union or its Member States, United Kingdom, United Nations embargo, or that has been designated as a “terrorist-supporting” country or region (collectively “Sanctioned Countries”), or any other country or region where your use of or access to the Protocol would be illegal or otherwise violate any applicable or local law; - -5.5.5. you aren’t listed, represent, or operate for anyone listed on any a United States, European Union or its Member States, United Kingdom, United Nations list of prohibited or restricted parties (collectively “Sanctioned Persons”). - -5.6. You agree to use not to use the Protocol only for the lawful purposes it is provided for, under these Terms and applicable law. You agree not to use the protocol for - -5.6.1. employ any devices, introduce any harmful software, or routines that interfere with the Protocol's normal functioning; - -5.6.2. circumvent or disable any security features designed to protect the Protocol; - -5.6.3. attempt to access, interfere with, or disrupt any part of the Protocol or its connected servers and databases without authorization; - -5.6.4. engage in any activity that hinders or disrupts the proper operation of the Protocol; - -5.6.5. participate in or facilitate any illegal or fraudulent actions, including handling or transferring funds obtained through criminal means; - -5.6.6. fabricate transactions, manipulate market conditions, engage in deceptive trading practices, or encourage others to engage in such unethical behavior; - -5.6.7. exploit, harm, or attempt to harm minors by exposing them to inappropriate content, soliciting personally identifiable information, or any other means of exploitation; - -5.6.8. send, receive, upload, download, or utilize any materials that are in violation of these Terms of Use; - -5.6.9. impersonate or attempt to impersonate Light Protocol, its team members, other users, or any other individual or entity, including through the misuse of digital wallets, email addresses, or usernames associated with them; and - -5.6.10. engage in any behavior that restricts or inhibits any other person's ability to use or enjoy the Protocol, or that may harm Light Protocol or its users, or expose them to legal liability. - - - -6\. ENFORCEMENT AND TERMINATION - -6.1. We reserve the right to take any adequate actions to protect the Protocol and the legitimate interests of its users upon breach of these Terms of Use or applicable law. This includes: - -6.1.1. initiating legal proceedings or reporting to law enforcement authorities any illegal or unauthorized use of the Protocol; - -6.1.2. terminating or suspending access to all or part of the Protocol, with or without notice, for adequate reason, including violations of these Terms of Use for the respective user; and - -6.1.3. implementing appropriate measures, such as denying access or reporting misconduct to relevant authorities, if a user engages in or are reasonably suspected of engaging in prohibited activities. - -6.2. Please note that we do not monitor or control any content within the Protocol, nor do we oversee on-chain transactions. As a result, we disclaim all liability for the actions or omissions of other users of the Protocol. - - - -7\. GENERAL RISKS ASSOCIATED WITH BLOCKCHAIN - -7.1. By accessing or interacting with the Protocol in any manner, you represent and warrant that you fully comprehend the inherent risks associated with: - -7.1.1. cryptographic systems and blockchain based-networks, including the fundamental principles and vulnerabilities of cryptography and distributed ledger technologies; - -7.1.2. digital assets; - -7.1.3. smart contract-based tokens, in particular recognizing the functionalities and potential risks of tokens governed by smart contracts; and - -7.1.4. systems interacting with blockchain networks. - -7.2. You acknowledge and accept that cryptography is a rapidly advancing field. Breakthroughs in code decryption or other technological innovations—such as the development of quantum computers—may introduce new risks to digital assets and smart contracts accessible via the Protocol. These advancements could potentially lead to the theft or loss of your digital assets. - - - -8\. DISCLAIMER - -8.1. By accessing and using the Protocol, you acknowledge that we do not offer investment advice, and no advisory relationship is established between you and us. We do not provide financial, investment, tax, legal, or securities advice through the Protocol. Any information or content available on the Protocol, whether from us, third parties, or other users, should not be interpreted as investment advice. We disclaim any responsibility or liability for decisions you make or actions you take based on your use of the Protocol. - -8.1.1. Nothing presented within the Protocol should be construed as a solicitation, recommendation, endorsement, or offer by us or any third party to engage in any transaction involving digital assets, securities, or other financial instruments. We and our affiliates do not endorse or sponsor any digital assets that may be transacted through the Protocol. - -8.1.2. You understand and agree that using the Protocol may require you to pay fees associated with interacting with blockchain technologies. You are solely responsible for paying these fees and costs. Light Protocol is not responsible for covering any such expenses. - -8.2. We value security, as outlined above in 6.2., however you acknowledge and agree that we cannot guarantee that the Protocol or any files available for download are free of viruses, malware, or other harmful components. - -8.2.1. You are responsible for implementing appropriate security measures to protect your systems and data, including maintaining backups and antivirus protections. - -8.2.2. To the maximum extent allowed by applicable law, we are not liable for any loss or damage caused by viruses or other technologically harmful material that may infect your devices, software, data, or other proprietary material due to your use of the Protocol or any services or items obtained through the Protocol. - - - -9\. WARRANTIES - -9.1. You acknowledge and agree that the Protocol is provided to you on an "as is" and "as available" basis, without any warranties of any kind, whether express, implied, statutory, or otherwise. Your use of the Protocol is entirely at your own risk. - -9.2. To the fullest extent permitted by applicable law, Light Protocol and its subsidiaries, affiliates, officers, directors, agents, co-branders, partners, suppliers, employees, and service providers disclaim all warranties, express or implied. This includes, but is not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising from course of dealing or usage of trade. - -9.3. We make no representations or warranties regarding the completeness, accuracy, reliability, security, quality, or availability of the Protocol. No advice or information, whether oral or written, obtained by you from us or through the Protocol shall create any warranty not expressly stated herein. - -9.4. Specifically, we do not warrant that: - -· The Protocol will meet your requirements or expectations. - -· The Protocol will be uninterrupted, timely, secure, or error-free. - -· Any defects or errors will be corrected. - -· The Protocol is free from viruses or other harmful components. - -· Any content or information obtained through the Protocol will be accurate or reliable. - -9.5. Nothing in these Terms affects any warranties that cannot be excluded or limited under applicable law. - - - -10\. LIMITATION OF LIABILITY - -10.1. To the maximum extent permitted by applicable law, in no event shall Light Protocol, along with its subsidiaries, affiliates, officers, directors, agents, co-branders, partners, suppliers, employees, or service providers, be liable for any indirect, incidental, special, consequential, or exemplary damages. - -10.1.1. This includes, without limitation, damages for loss of profits, goodwill, use, data, or other intangible losses arising out of or relating to your use of, or inability to use, the Protocol. - -10.1.2. This limitation applies even if such damages were foreseeable or if we have been advised of the possibility of such damages. - -10.2. Nothing in these Terms affects any warranties that cannot be excluded or limited under applicable law. - - - -11\. INDEMNIFICATION - -11.1. You agree to defend, indemnify, and hold harmless Light Protocol, its affiliates, licensors, service providers, and their respective officers, directors, employees, contractors, agents, shareholders, members, suppliers, successors, and assigns from and against any and all claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees—including reasonable attorneys' fees and legal expenses—that arise out of or relate to: - -11.1.1. your breach or violation of these Terms of Use; - -11.1.2. your use or misuse of the Protocol, including any data or content you transmit or receive; - -11.1.3. any use of the Protocol's content that is not expressly authorized in these Terms of Use; - -11.1.4. your violation of any applicable laws, statutes, regulations, or rules. - -11.2. This indemnification obligation applies to the maximum extent permitted by applicable law. - - - - - -12\. GOVERNING LAW AND JURISDICTION - -12.1. All issues arising from or related to the Protocol and these Terms of Use, including any disputes or claims—whether contractual or non-contractual—shall be governed by and interpreted in accordance with the laws of the Cayman Islands. This applies without regard to any choice of law or conflict of law rules, whether from the Cayman Islands or any other jurisdiction. - -12.2. Subject to the arbitration provisions below, any legal actions, suits, or proceedings related to the Protocol or these Terms must be brought exclusively in the courts of the Cayman Islands. However, we reserve the right to initiate legal proceedings against you in your country or state of residence or any other applicable jurisdiction if you breach these Terms of Use. - -12.3. By agreeing to these Terms, you waive any objections to the jurisdiction of such courts and to the venue in which they are located. - - - -13\. ARBITRATION - -13.1. Any disputes arising from these Terms of Use or related to your use of the Protocol—including disputes over interpretation, violation, invalidity, non-performance, or termination—may be required to be submitted to final and binding arbitration, at the sole discretion of Light Protocol. Such arbitration will be administered by the Cayman International Mediation and Arbitration Centre (CI-MAC) under the Arbitration Act of the Cayman Islands and governed by Cayman Islands law. - -13.2. The arbitration will be conducted in English, in George Town, Cayman Islands, with a single arbitrator presiding. The arbitrator shall have the authority to rule on any challenges to its jurisdiction or to the validity and enforceability of these arbitration provisions. - -13.3. The decision rendered by the sole arbitrator will be final and binding on both parties, and each party waives the right to challenge the jurisdiction of the arbitration or object to the venue, including under the doctrine of forum non conveniens. - -13.4. All disputes will be resolved on an individual basis. Class arbitration and any claims brought as a plaintiff or class member in any representative or collective proceeding are expressly prohibited. The arbitrator is not permitted to consolidate claims or preside over any form of class or representative arbitration. - -13.5. By agreeing to these Terms of Use, you consent to this arbitration provision, which affects how disputes between you and Light Protocol will be resolved. Please read this section carefully, as it requires you to resolve disputes through individual arbitration and prohibits class actions. - - - -14\. LIMITATION ON TIME TO FILE CLAIMS - -14.1. Any claim or legal action arising out of or relating to these Terms of Use or your use of the Protocol must be initiated within one year from the date the cause of action occurs. - -14.2. Failure to bring such a claim within this timeframe will result in the claim being permanently barred. - - - -15\. WAIVER, SEVERABILITY - -15.1. No waiver by Light Protocol of any term or condition in these Terms of Use shall be considered a continuing waiver or a waiver of any other term or condition. - -15.2. If Light Protocol does not enforce a right or provision under these Terms at any time, this shall not be considered a waiver of that right or provision. - -15.3. Should any provision of these Terms be deemed or become invalid or unenforceable under applicable law, the invalidity or unenforceability of such provision shall not affect the validity of the remaining provisions, which shall remain in full force and effect. - - - -16\. AGREEMENT AND SURVIVING PROVISIONS - -16.1. These Terms of Use and the Licenses constitute the entire Agreement between you and the Light Protocol with respect to the Protocol, and supersede all other communications, including but not limited to all prior agreements, whether in writing or oral, between the parties with respect to the Protocol. - -16.2. This Agreement shall continue in effect until it is terminated by either Light Protocol or you. Upon termination, the provisions contained in these Terms of Use that by their context are intended to survive termination or expiration will survive, including but not limited to the following: - -16.2.1. your grant of licenses under these Terms shall survive indefinitely; - -16.2.2. your indemnification obligations shall survive for a period of five years from the date of termination; - -16.2.3. the disclaimer of warranties and representations, and the stipulations under the section containing indemnity and limitation of liability provisions, shall survive indefinitely. - -16.3. Please send all communications relating to these Terms of Use to [friends@lightprotocol.com](mailto:friends@lightprotocol.com) referencing “Terms of Use Enquiry”. diff --git a/package.json b/package.json new file mode 100644 index 0000000..142a859 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "light-mcp", + "version": "1.2.4", + "description": "MCP server providing ZK Compression documentation search for AI-powered IDEs", + "main": "dist/index.js", + "type": "module", + "bin": { + "light-mcp": "dist/cli.js" + }, + "files": [ + "dist/**/*", + "compression-docs/**/*", + "README.md" + ], + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "tsx src/index.ts", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "glob": "^10.3.10", + "gray-matter": "^4.0.3", + "fuse.js": "^7.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "tsx": "^4.0.0" + }, + "keywords": ["mcp", "solana", "zk-compression"], + "author": "Your Name", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/sweriko/developer-content.git" + }, + "homepage": "https://github.com/sweriko/developer-content", + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..d2d11aa --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,110 @@ +#!/usr/bin/env node +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; +import { join, dirname } from 'path'; +import { homedir } from 'os'; + +const CURSOR_CONFIG_PATH = join(homedir(), '.cursor', 'mcp.json'); +const CURSOR_DIR = dirname(CURSOR_CONFIG_PATH); + +function ensureCursorDir() { + if (!existsSync(CURSOR_DIR)) { + mkdirSync(CURSOR_DIR, { recursive: true }); + console.error('Created .cursor directory'); + } +} + +function readCursorConfig() { + if (!existsSync(CURSOR_CONFIG_PATH)) { + return { mcpServers: {} }; + } + + try { + const content = readFileSync(CURSOR_CONFIG_PATH, 'utf-8'); + return JSON.parse(content); + } catch (error) { + console.error('Invalid mcp.json, creating new one'); + return { mcpServers: {} }; + } +} + +function writeCursorConfig(config: any) { + writeFileSync(CURSOR_CONFIG_PATH, JSON.stringify(config, null, 2)); +} + +function installMCP() { + console.error('Installing Light MCP for ZK Compression documentation...'); + + ensureCursorDir(); + + const config = readCursorConfig(); + + // Add or update the light-mcp server + config.mcpServers = config.mcpServers || {}; + config.mcpServers['light-mcp'] = { + command: 'npx', + args: ['-y', 'light-mcp'] + }; + + writeCursorConfig(config); + + console.error('Light MCP installed successfully.'); + console.error(`Configuration saved to: ${CURSOR_CONFIG_PATH}`); + console.error('Please restart Cursor to load the MCP server.'); +} + +function uninstallMCP() { + console.error('Uninstalling Light MCP...'); + + if (!existsSync(CURSOR_CONFIG_PATH)) { + console.error('No Cursor MCP configuration found.'); + return; + } + + const config = readCursorConfig(); + + if (config.mcpServers && config.mcpServers['light-mcp']) { + delete config.mcpServers['light-mcp']; + writeCursorConfig(config); + console.error('Light MCP removed from Cursor configuration.'); + console.error('Please restart Cursor to apply changes.'); + } else { + console.error('Light MCP was not found in configuration.'); + } +} + +function showHelp() { + console.error('Light MCP - ZK Compression Documentation Search'); + console.error(''); + console.error('Usage:'); + console.error(' npx light-mcp install Install Light MCP in Cursor'); + console.error(' npx light-mcp uninstall Remove Light MCP from Cursor'); + console.error(' npx light-mcp Run MCP server (used by Cursor)'); + console.error(' npx light-mcp --help Show this help'); +} + +async function startMCPServer() { + // Import and start the MCP server + const { default: ZKCompressionDocsServer } = await import('./index.js'); + const server = new ZKCompressionDocsServer(); + await server.run(); +} + +function main() { + const args = process.argv.slice(2); + + if (args.includes('install')) { + installMCP(); + } else if (args.includes('uninstall')) { + uninstallMCP(); + } else if (args.includes('--help') || args.includes('-h')) { + showHelp(); + } else if (args.length === 0) { + // No args = run MCP server (this is what Cursor calls) + startMCPServer(); + } else { + console.error('Unknown command. Use --help for usage information.'); + process.exit(1); + } +} + +main(); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5e39ee8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,549 @@ +#!/usr/bin/env node +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + Tool +} from '@modelcontextprotocol/sdk/types.js'; +import { glob } from 'glob'; +import { readFileSync, existsSync } from 'fs'; +import { join, dirname, relative } from 'path'; +import { fileURLToPath } from 'url'; +import matter from 'gray-matter'; +import Fuse from 'fuse.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const PROJECT_ROOT = join(__dirname, '..'); + +interface DocumentEntry { + path: string; + relativePath: string; + title: string; + content: string; + frontmatter: any; + section: string; + methodName?: string; + keywords: string[]; + isRpcMethod: boolean; + isComprehensiveDoc: boolean; +} + +class ZKCompressionDocsServer { + private server: Server; + private documents: DocumentEntry[] = []; + private searchIndex: Fuse | null = null; + + constructor() { + this.server = new Server( + { + name: 'light-mcp', + version: '1.1.1', + }, + { + capabilities: { + tools: {}, + }, + } + ); + + this.setupHandlers(); + this.initializeDocuments(); + } + + private extractMethodName(filePath: string, content: string, title: string): string | undefined { + const fileName = filePath.split('/').pop()?.replace('.md', ''); + if (fileName && fileName.startsWith('get')) { + return fileName; + } + + // Extract from content (look for method definitions) + const methodMatch = content.match(/^#\s*(\w+)$/m) || content.match(/`(\w+)`.*method/i); + if (methodMatch) { + return methodMatch[1]; + } + + return undefined; + } + + private extractKeywords(content: string, title: string, methodName?: string): string[] { + const keywords = new Set(); + + // Add title words + title.toLowerCase().split(/\s+/).forEach(word => { + if (word.length > 2) keywords.add(word); + }); + + // Add method name variations + if (methodName) { + keywords.add(methodName.toLowerCase()); + // Add camelCase splits + const camelSplit = methodName.replace(/([A-Z])/g, ' $1').toLowerCase().trim(); + camelSplit.split(/\s+/).forEach(word => { + if (word.length > 2) keywords.add(word); + }); + } + + // Extract key terms from content + const keyTerms = content.match(/`[a-zA-Z][a-zA-Z0-9_]*`/g) || []; + keyTerms.forEach(term => { + const clean = term.replace(/`/g, '').toLowerCase(); + if (clean.length > 2) keywords.add(clean); + }); + + // Add common API/RPC terms + const commonTerms = ['rpc', 'method', 'api', 'endpoint', 'compressed', 'account', 'token', 'balance', 'signature']; + const contentLower = content.toLowerCase(); + commonTerms.forEach(term => { + if (contentLower.includes(term)) keywords.add(term); + }); + + return Array.from(keywords); + } + + private async initializeDocuments() { + try { + const markdownFiles = await glob('compression-docs/**/*.md', { + cwd: PROJECT_ROOT, + absolute: true, + }); + + console.error(`Found ${markdownFiles.length} markdown files`); + + for (const filePath of markdownFiles) { + try { + const content = readFileSync(filePath, 'utf-8'); + const parsed = matter(content); + const relativePath = relative(PROJECT_ROOT, filePath); + + const pathParts = relativePath.split('/'); + let section = 'General'; + if (pathParts.length > 1) { + section = pathParts[1].replace(/-/g, ' '); + } + + const fileName = pathParts[pathParts.length - 1].replace('.md', ''); + const title = parsed.data.title || fileName.replace(/-/g, ' '); + const methodName = this.extractMethodName(filePath, parsed.content, title); + const keywords = this.extractKeywords(parsed.content, title, methodName); + + // Detect if this is an RPC method file + const isRpcMethod = filePath.includes('json-rpc-methods') && + !fileName.includes('readme') && + !fileName.includes('rpcmethods'); + + // Detect comprehensive documentation files + const isComprehensiveDoc = fileName.includes('rpcmethods') || + parsed.content.includes('## Mainnet ZK Compression API endpoints') || + title.toLowerCase().includes('overview') || + title.toLowerCase().includes('all'); + + const doc: DocumentEntry = { + path: filePath, + relativePath, + title: title.charAt(0).toUpperCase() + title.slice(1), + content: parsed.content, + frontmatter: parsed.data, + section, + methodName, + keywords, + isRpcMethod, + isComprehensiveDoc, + }; + + this.documents.push(doc); + } catch (error) { + console.error(`Error processing ${filePath}:`, error); + } + } + + const fuseOptions = { + keys: [ + { name: 'title', weight: 3 }, + { name: 'methodName', weight: 2.5 }, + { name: 'keywords', weight: 2 }, + { name: 'content', weight: 1 }, + { name: 'section', weight: 0.5 }, + ], + threshold: 0.6, // More permissive + includeScore: true, + includeMatches: true, + minMatchCharLength: 2, + ignoreLocation: true, + useExtendedSearch: true, + }; + + this.searchIndex = new Fuse(this.documents, fuseOptions); + console.error(`Initialized search index with ${this.documents.length} documents`); + } catch (error) { + console.error('Error initializing documents:', error); + } + } + + private setupHandlers() { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: 'search_docs', + description: 'Advanced search across ZK Compression documentation with semantic understanding, context analysis, and smart ranking. Finds API methods, concepts, examples, and implementation details with high precision.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'Search query supporting natural language, technical terms, and concepts (e.g., "how to create compressed tokens", "validity proof verification", "RPC methods for token accounts")', + }, + limit: { + type: 'number', + description: 'Maximum number of results to return (default: 5, max: 20)', + default: 5, + }, + section: { + type: 'string', + description: 'Filter by documentation section (e.g., "compressed-tokens", "compressed-pdas", "learn", "resources", "json-rpc-methods")', + }, + mode: { + type: 'string', + description: 'Search mode: fuzzy (flexible matching), exact (precise terms), semantic (meaning-based), comprehensive (multi-layered analysis)', + enum: ['fuzzy', 'exact', 'semantic', 'comprehensive'], + default: 'semantic', + }, + content_filter: { + type: 'string', + description: 'Filter by content type to focus results', + enum: ['all', 'guides', 'reference', 'examples', 'concepts'], + default: 'all', + }, + expand_context: { + type: 'boolean', + description: 'Provide expanded context and related sections', + default: true, + }, + include_code: { + type: 'boolean', + description: 'Include code examples and snippets in results', + default: true, + }, + }, + required: ['query'], + }, + } as Tool, + ], + }; + }); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name !== 'search_docs') { + throw new Error(`Unknown tool: ${request.params.name}`); + } + + const { + query, + limit = 5, + section, + mode = 'semantic', + content_filter = 'all', + expand_context = true, + include_code = true + } = request.params.arguments as { + query: string; + limit?: number; + section?: string; + mode?: string; + content_filter?: string; + expand_context?: boolean; + include_code?: boolean; + }; + + if (!query) { + throw new Error('Query parameter is required'); + } + + return await this.searchDocs(query, Math.min(limit, 20), section, mode, content_filter, expand_context, include_code); + }); + } + + private detectQueryIntent(query: string): { isComprehensive: boolean; category?: string; searchTerms: string[] } { + const queryLower = query.toLowerCase(); + const comprehensiveIndicators = ['all', 'list', 'complete', 'every', 'entire', 'full list']; + const isComprehensive = comprehensiveIndicators.some(indicator => queryLower.includes(indicator)); + + let category; + if (queryLower.includes('rpc') || queryLower.includes('method') || queryLower.includes('api')) { + category = 'rpc-methods'; + } else if (queryLower.includes('token')) { + category = 'compressed-tokens'; + } else if (queryLower.includes('pda') || queryLower.includes('account')) { + category = 'compressed-pdas'; + } + + // Extract meaningful search terms + const searchTerms = query.toLowerCase() + .replace(/\b(all|list|complete|every|entire|full)\b/g, '') + .split(/\s+/) + .filter(term => term.length > 2); + + return { isComprehensive, category, searchTerms }; + } + + private performComprehensiveSearch(category: string, section?: string): DocumentEntry[] { + if (category === 'rpc-methods') { + // Return all RPC method documents + comprehensive overview + let docs = this.documents.filter(doc => + doc.isRpcMethod || doc.isComprehensiveDoc && doc.path.includes('json-rpc-methods') + ); + + // Prioritize the comprehensive overview document + docs.sort((a, b) => { + if (a.isComprehensiveDoc && !b.isComprehensiveDoc) return -1; + if (!a.isComprehensiveDoc && b.isComprehensiveDoc) return 1; + return a.title.localeCompare(b.title); + }); + + return docs; + } + + // Default comprehensive search for other categories + return this.documents.filter(doc => + section ? doc.section.toLowerCase().includes(section.toLowerCase()) : true + ); + } + + private async searchDocs( + query: string, + limit: number, + section?: string, + mode: string = 'semantic', + content_filter: string = 'all', + expand_context: boolean = true, + include_code: boolean = true + ) { + if (!this.searchIndex) { + return { + content: [ + { + type: 'text', + text: 'Search index not initialized. Please wait for the server to finish loading.', + }, + ], + }; + } + + const { isComprehensive, category, searchTerms } = this.detectQueryIntent(query); + let searchResults: any[] = []; + let usedComprehensiveSearch = false; + + // Handle comprehensive queries + if (isComprehensive && category) { + const comprehensiveDocs = this.performComprehensiveSearch(category, section); + searchResults = comprehensiveDocs.map(doc => ({ item: doc, score: 0 })); + usedComprehensiveSearch = true; + + // For RPC methods, ensure we get all 21 + overview + if (category === 'rpc-methods') { + limit = Math.max(limit, 25); // Ensure we show all RPC methods + } + } else { + // Normal fuzzy search + searchResults = this.searchIndex.search(query); + + // Apply section filter + if (section) { + searchResults = searchResults.filter(result => + result.item.section.toLowerCase().includes(section.toLowerCase()) + ); + } + + // Apply content filter + if (content_filter !== 'all') { + searchResults = searchResults.filter(result => { + const doc = result.item; + switch (content_filter) { + case 'guides': + return doc.section.includes('guides') || doc.title.toLowerCase().includes('guide'); + case 'reference': + return doc.isRpcMethod || doc.section.includes('reference'); + case 'examples': + return doc.content.includes('example') || doc.content.includes('```'); + case 'concepts': + return doc.section.includes('learn') || doc.section.includes('concepts'); + default: + return true; + } + }); + } + } + + const results = searchResults.slice(0, limit); + + if (results.length === 0) { + return { + content: [ + { + type: 'text', + text: `No results found for "${query}"${section ? ` in section "${section}"` : ''}`, + }, + ], + }; + } + + let response = `# ZK Compression Documentation Search Results\n\n`; + response += `**Query:** "${query}"\n`; + response += `**Found:** ${results.length} result${results.length === 1 ? '' : 's'}`; + if (usedComprehensiveSearch) { + response += ` (comprehensive search)\n`; + } else { + response += ` (${mode} search)\n`; + } + response += `**Mode:** ${mode} | **Filter:** ${content_filter}\n\n`; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const doc = result.item; + const score = Math.round((1 - (result.score || 0)) * 100); + + response += `## ${i + 1}. ${doc.title}\n`; + response += `**Path:** \`${doc.relativePath}\`\n`; + response += `**Section:** ${doc.section}\n`; + if (doc.methodName) { + response += `**Method:** \`${doc.methodName}\`\n`; + } + response += `**Relevance:** ${score}%\n\n`; + + // Enhanced content extraction + let relevantContent = this.extractRelevantContent( + doc, + usedComprehensiveSearch ? searchTerms : query.toLowerCase().split(' '), + include_code, + expand_context + ); + + if (relevantContent.length > 2000) { + relevantContent = relevantContent.substring(0, 2000) + '\n\n...'; + } + + response += '```markdown\n' + relevantContent.trim() + '\n```\n\n'; + response += '---\n\n'; + } + + return { + content: [ + { + type: 'text', + text: response, + }, + ], + }; + } + + private extractRelevantContent( + doc: DocumentEntry, + queryWords: string[], + includeCode: boolean, + expandContext: boolean + ): string { + const contentLines = doc.content.split('\n'); + let relevantContent = ''; + + // For comprehensive docs or RPC methods, include more structure + if (doc.isComprehensiveDoc || doc.isRpcMethod) { + // Include title and first few sections + let sectionCount = 0; + let inCodeBlock = false; + + for (let i = 0; i < contentLines.length && sectionCount < 3; i++) { + const line = contentLines[i]; + + // Track code blocks + if (line.startsWith('```')) { + inCodeBlock = !inCodeBlock; + if (includeCode) { + relevantContent += line + '\n'; + } + continue; + } + + // Skip code content if not including code + if (inCodeBlock && !includeCode) { + continue; + } + + // Include headers + if (line.startsWith('#')) { + sectionCount++; + relevantContent += line + '\n'; + } + // Include lines with query terms + else if (queryWords.some(word => line.toLowerCase().includes(word))) { + relevantContent += line + '\n'; + } + // Include important structural lines + else if (line.includes('|') || line.startsWith('*') || line.startsWith('-')) { + relevantContent += line + '\n'; + } + // Add context around matches + else if (expandContext && i > 0 && i < contentLines.length - 1) { + const prevLine = contentLines[i - 1]; + const nextLine = contentLines[i + 1]; + if (queryWords.some(word => + prevLine.toLowerCase().includes(word) || nextLine.toLowerCase().includes(word) + )) { + relevantContent += line + '\n'; + } + } + } + } else { + // Standard content extraction for other docs + let currentParagraph = ''; + let foundRelevantContent = false; + + for (const line of contentLines) { + if (line.trim() === '') { + if (currentParagraph.trim() && queryWords.some(word => + currentParagraph.toLowerCase().includes(word) + )) { + relevantContent += currentParagraph + '\n\n'; + foundRelevantContent = true; + } + currentParagraph = ''; + } else { + currentParagraph += line + '\n'; + } + } + + if (currentParagraph.trim() && queryWords.some(word => + currentParagraph.toLowerCase().includes(word) + )) { + relevantContent += currentParagraph + '\n\n'; + foundRelevantContent = true; + } + + if (!foundRelevantContent) { + const preview = contentLines.slice(0, 15).join('\n'); + relevantContent = preview + (contentLines.length > 15 ? '\n\n...' : ''); + } + } + + return relevantContent || 'No relevant content found.'; + } + + async run() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('Light MCP server running'); + } +} + +const isMainModule = process.argv[1] && process.argv[1].endsWith('index.js'); +if (isMainModule) { + const server = new ZKCompressionDocsServer(); + server.run().catch(console.error); +} + +export { ZKCompressionDocsServer }; +export default ZKCompressionDocsServer; + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1407b35 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}