├── .github ├── .env ├── file-filters.yml └── workflows │ ├── build-programs.yml │ ├── build-rust-client.yml │ ├── create-release.yml │ ├── create-solana-release.yml │ ├── create-svm-release.yml │ ├── deploy-js-client.yml │ ├── deploy-program.yml │ ├── deploy-rust-client.yml │ ├── main.yml │ ├── test-js-client.yml │ ├── test-programs.yml │ └── test-rust-client.yml ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── clients ├── js-solita │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.js │ ├── .solitarc.js │ ├── LICENSE │ ├── README.md │ ├── idl │ │ └── bubblegum.json │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── errors.ts │ │ ├── generated │ │ │ ├── accounts │ │ │ │ ├── TreeConfig.ts │ │ │ │ ├── Voucher.ts │ │ │ │ └── index.ts │ │ │ ├── errors │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── instructions │ │ │ │ ├── burn.ts │ │ │ │ ├── cancelRedeem.ts │ │ │ │ ├── compress.ts │ │ │ │ ├── createTree.ts │ │ │ │ ├── decompressV1.ts │ │ │ │ ├── delegate.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mintToCollectionV1.ts │ │ │ │ ├── mintV1.ts │ │ │ │ ├── redeem.ts │ │ │ │ ├── setAndVerifyCollection.ts │ │ │ │ ├── setDecompressibleState.ts │ │ │ │ ├── setTreeDelegate.ts │ │ │ │ ├── transfer.ts │ │ │ │ ├── unverifyCollection.ts │ │ │ │ ├── unverifyCreator.ts │ │ │ │ ├── updateMetadata.ts │ │ │ │ ├── verifyCollection.ts │ │ │ │ └── verifyCreator.ts │ │ │ └── types │ │ │ │ ├── BubblegumEventType.ts │ │ │ │ ├── Collection.ts │ │ │ │ ├── Creator.ts │ │ │ │ ├── DecompressibleState.ts │ │ │ │ ├── LeafSchema.ts │ │ │ │ ├── MetadataArgs.ts │ │ │ │ ├── TokenProgramVersion.ts │ │ │ │ ├── TokenStandard.ts │ │ │ │ ├── UpdateArgs.ts │ │ │ │ ├── UseMethod.ts │ │ │ │ ├── Uses.ts │ │ │ │ ├── Version.ts │ │ │ │ └── index.ts │ │ ├── index.ts │ │ └── mpl-bubblegum.ts │ ├── tests │ │ └── main.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── typedoc.json │ └── yarn.lock ├── js │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.json │ ├── CONTRIBUTING.md │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── canTransfer.ts │ │ ├── createTree.ts │ │ ├── errors.ts │ │ ├── flags.ts │ │ ├── generated │ │ │ ├── accounts │ │ │ │ ├── index.ts │ │ │ │ ├── treeConfig.ts │ │ │ │ └── voucher.ts │ │ │ ├── errors │ │ │ │ ├── index.ts │ │ │ │ └── mplBubblegum.ts │ │ │ ├── index.ts │ │ │ ├── instructions │ │ │ │ ├── burn.ts │ │ │ │ ├── burnV2.ts │ │ │ │ ├── cancelRedeem.ts │ │ │ │ ├── collectV2.ts │ │ │ │ ├── createTreeConfig.ts │ │ │ │ ├── createTreeConfigV2.ts │ │ │ │ ├── decompressV1.ts │ │ │ │ ├── delegate.ts │ │ │ │ ├── delegateAndFreezeV2.ts │ │ │ │ ├── delegateV2.ts │ │ │ │ ├── freezeV2.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mintToCollectionV1.ts │ │ │ │ ├── mintV1.ts │ │ │ │ ├── mintV2.ts │ │ │ │ ├── redeem.ts │ │ │ │ ├── setAndVerifyCollection.ts │ │ │ │ ├── setCollectionV2.ts │ │ │ │ ├── setDecompressibleState.ts │ │ │ │ ├── setNonTransferableV2.ts │ │ │ │ ├── setTreeDelegate.ts │ │ │ │ ├── thawAndRevokeV2.ts │ │ │ │ ├── thawV2.ts │ │ │ │ ├── transfer.ts │ │ │ │ ├── transferV2.ts │ │ │ │ ├── unverifyCollection.ts │ │ │ │ ├── unverifyCreator.ts │ │ │ │ ├── unverifyCreatorV2.ts │ │ │ │ ├── updateAssetDataV2.ts │ │ │ │ ├── updateMetadata.ts │ │ │ │ ├── updateMetadataV2.ts │ │ │ │ ├── verifyCollection.ts │ │ │ │ ├── verifyCreator.ts │ │ │ │ └── verifyCreatorV2.ts │ │ │ ├── programs │ │ │ │ ├── index.ts │ │ │ │ └── mplBubblegum.ts │ │ │ ├── shared │ │ │ │ └── index.ts │ │ │ └── types │ │ │ │ ├── assetDataSchema.ts │ │ │ │ ├── bubblegumEventType.ts │ │ │ │ ├── collection.ts │ │ │ │ ├── creator.ts │ │ │ │ ├── decompressibleState.ts │ │ │ │ ├── index.ts │ │ │ │ ├── leafSchema.ts │ │ │ │ ├── metadataArgs.ts │ │ │ │ ├── metadataArgsV2.ts │ │ │ │ ├── tokenProgramVersion.ts │ │ │ │ ├── tokenStandard.ts │ │ │ │ ├── updateArgs.ts │ │ │ │ ├── useMethod.ts │ │ │ │ ├── uses.ts │ │ │ │ └── version.ts │ │ ├── getAssetWithProof.ts │ │ ├── getCompressionProgramsForV1Ixs.ts │ │ ├── hash.ts │ │ ├── hooked │ │ │ ├── index.ts │ │ │ ├── mintAuthority.ts │ │ │ └── resolvers.ts │ │ ├── index.ts │ │ ├── leafAssetId.ts │ │ ├── merkle.ts │ │ └── plugin.ts │ ├── test │ │ ├── _setup.ts │ │ ├── burn.test.ts │ │ ├── burnV2.test.ts │ │ ├── cancelRedeem.test.ts │ │ ├── collectV2.test.ts │ │ ├── createTree.test.ts │ │ ├── createTreeV2.test.ts │ │ ├── dasApi.test.ts │ │ ├── decompressV1.test.ts │ │ ├── delegate.test.ts │ │ ├── delegateAndFreezeV2.test.ts │ │ ├── delegateV2.test.ts │ │ ├── freezeV2.test.ts │ │ ├── getProgram.test.ts │ │ ├── mintToCollectionV1.test.ts │ │ ├── mintToCollectionV1AndTransfer.test.ts │ │ ├── mintV1.test.ts │ │ ├── mintV2ToCollectionAndTransfer.test.ts │ │ ├── mintV2_only.test.ts │ │ ├── parseMintV1.test.ts │ │ ├── parseMintV2.test.ts │ │ ├── permanentBurnDelegate.test.ts │ │ ├── permanentFreezeDelegate.test.ts │ │ ├── permanentTransferDelegate.test.ts │ │ ├── redeem.test.ts │ │ ├── royalties.test.ts │ │ ├── setAndVerifyCollection.test.ts │ │ ├── setCollectionV2.test.ts │ │ ├── setNonTransferableV2.test.ts │ │ ├── setTreeDelegate.test.ts │ │ ├── thawAndRevokeV2.test.ts │ │ ├── thawV2.test.ts │ │ ├── transfer.test.ts │ │ ├── transferV2.test.ts │ │ ├── unverifyCollection.test.ts │ │ ├── unverifyCreator.test.ts │ │ ├── unverifyCreatorV2.test.ts │ │ ├── updateAssetDataV2.test.ts │ │ ├── updateMetadata.test.ts │ │ ├── updateMetadataV2.test.ts │ │ ├── verifyCollection.test.ts │ │ ├── verifyCreator.test.ts │ │ ├── verifyCreatorV2.test.ts │ │ ├── verifyLeaf.test.ts │ │ └── verifyLeafonV2Tree.test.ts │ ├── tsconfig.json │ └── typedoc.json ├── mpl-ac-js │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.json │ ├── CONTRIBUTING.md │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── generated │ │ │ ├── accounts │ │ │ │ ├── index.ts │ │ │ │ └── merkleTree.ts │ │ │ ├── errors │ │ │ │ ├── index.ts │ │ │ │ ├── mplAccountCompression.ts │ │ │ │ └── mplNoop.ts │ │ │ ├── index.ts │ │ │ ├── instructions │ │ │ │ ├── append.ts │ │ │ │ ├── appendCanopyNodes.ts │ │ │ │ ├── closeEmptyTree.ts │ │ │ │ ├── index.ts │ │ │ │ ├── initEmptyMerkleTree.ts │ │ │ │ ├── initPreparedTreeWithRoot.ts │ │ │ │ ├── insertOrAppend.ts │ │ │ │ ├── noopInstruction.ts │ │ │ │ ├── prepareBatchMerkleTree.ts │ │ │ │ ├── replaceLeaf.ts │ │ │ │ ├── transferAuthority.ts │ │ │ │ └── verifyLeaf.ts │ │ │ ├── programs │ │ │ │ ├── index.ts │ │ │ │ ├── mplAccountCompression.ts │ │ │ │ └── mplNoop.ts │ │ │ ├── shared │ │ │ │ └── index.ts │ │ │ └── types │ │ │ │ ├── accountCompressionEvent.ts │ │ │ │ ├── applicationDataEvent.ts │ │ │ │ ├── applicationDataEventV1.ts │ │ │ │ ├── changeLogEvent.ts │ │ │ │ ├── changeLogEventV1.ts │ │ │ │ ├── compressionAccountType.ts │ │ │ │ ├── concurrentMerkleTreeHeader.ts │ │ │ │ ├── concurrentMerkleTreeHeaderData.ts │ │ │ │ ├── index.ts │ │ │ │ └── pathNode.ts │ │ ├── hooked │ │ │ ├── changeLog.ts │ │ │ ├── concurrentMerkleTree.ts │ │ │ ├── index.ts │ │ │ ├── merkleTreeAccountData.ts │ │ │ └── path.ts │ │ ├── index.ts │ │ └── plugin.ts │ ├── test │ │ ├── _setup.ts │ │ └── getProgram.test.ts │ ├── tsconfig.json │ └── typedoc.json ├── rust │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── generated │ │ │ ├── accounts │ │ │ │ ├── mod.rs │ │ │ │ ├── tree_config.rs │ │ │ │ └── voucher.rs │ │ │ ├── errors │ │ │ │ ├── mod.rs │ │ │ │ └── mpl_bubblegum.rs │ │ │ ├── instructions │ │ │ │ ├── burn.rs │ │ │ │ ├── burn_v2.rs │ │ │ │ ├── cancel_redeem.rs │ │ │ │ ├── collect_v2.rs │ │ │ │ ├── create_tree_config.rs │ │ │ │ ├── create_tree_config_v2.rs │ │ │ │ ├── decompress_v1.rs │ │ │ │ ├── delegate.rs │ │ │ │ ├── delegate_and_freeze_v2.rs │ │ │ │ ├── delegate_v2.rs │ │ │ │ ├── freeze_v2.rs │ │ │ │ ├── mint_to_collection_v1.rs │ │ │ │ ├── mint_v1.rs │ │ │ │ ├── mint_v2.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── redeem.rs │ │ │ │ ├── set_and_verify_collection.rs │ │ │ │ ├── set_collection_v2.rs │ │ │ │ ├── set_decompressible_state.rs │ │ │ │ ├── set_non_transferable_v2.rs │ │ │ │ ├── set_tree_delegate.rs │ │ │ │ ├── thaw_and_revoke_v2.rs │ │ │ │ ├── thaw_v2.rs │ │ │ │ ├── transfer.rs │ │ │ │ ├── transfer_v2.rs │ │ │ │ ├── unverify_collection.rs │ │ │ │ ├── unverify_creator.rs │ │ │ │ ├── unverify_creator_v2.rs │ │ │ │ ├── update_asset_data_v2.rs │ │ │ │ ├── update_metadata.rs │ │ │ │ ├── update_metadata_v2.rs │ │ │ │ ├── verify_collection.rs │ │ │ │ ├── verify_creator.rs │ │ │ │ └── verify_creator_v2.rs │ │ │ ├── mod.rs │ │ │ ├── programs.rs │ │ │ └── types │ │ │ │ ├── asset_data_schema.rs │ │ │ │ ├── bubblegum_event_type.rs │ │ │ │ ├── collection.rs │ │ │ │ ├── creator.rs │ │ │ │ ├── decompressible_state.rs │ │ │ │ ├── leaf_schema.rs │ │ │ │ ├── metadata_args.rs │ │ │ │ ├── metadata_args_v2.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── token_program_version.rs │ │ │ │ ├── token_standard.rs │ │ │ │ ├── update_args.rs │ │ │ │ ├── use_method.rs │ │ │ │ ├── uses.rs │ │ │ │ └── version.rs │ │ ├── hash.rs │ │ ├── lib.rs │ │ ├── traits.rs │ │ └── utils.rs │ └── tests │ │ ├── burn.rs │ │ ├── mint.rs │ │ ├── setup │ │ ├── mod.rs │ │ └── tree_manager.rs │ │ └── transfer.rs └── spl-ac-js │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.json │ ├── CONTRIBUTING.md │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── generated │ │ ├── accounts │ │ │ ├── index.ts │ │ │ └── merkleTree.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ ├── splAccountCompression.ts │ │ │ └── splNoop.ts │ │ ├── index.ts │ │ ├── instructions │ │ │ ├── append.ts │ │ │ ├── closeEmptyTree.ts │ │ │ ├── index.ts │ │ │ ├── initEmptyMerkleTree.ts │ │ │ ├── insertOrAppend.ts │ │ │ ├── noopInstruction.ts │ │ │ ├── replaceLeaf.ts │ │ │ ├── transferAuthority.ts │ │ │ └── verifyLeaf.ts │ │ ├── programs │ │ │ ├── index.ts │ │ │ ├── splAccountCompression.ts │ │ │ └── splNoop.ts │ │ ├── shared │ │ │ └── index.ts │ │ └── types │ │ │ ├── accountCompressionEvent.ts │ │ │ ├── applicationDataEvent.ts │ │ │ ├── applicationDataEventV1.ts │ │ │ ├── changeLogEvent.ts │ │ │ ├── changeLogEventV1.ts │ │ │ ├── compressionAccountType.ts │ │ │ ├── concurrentMerkleTreeHeader.ts │ │ │ ├── concurrentMerkleTreeHeaderData.ts │ │ │ ├── index.ts │ │ │ └── pathNode.ts │ ├── hooked │ │ ├── changeLog.ts │ │ ├── concurrentMerkleTree.ts │ │ ├── index.ts │ │ ├── merkleTreeAccountData.ts │ │ └── path.ts │ ├── index.ts │ └── plugin.ts │ ├── test │ ├── _setup.ts │ └── getProgram.test.ts │ ├── tsconfig.json │ └── typedoc.json ├── configs ├── kinobi.cjs ├── mpl-ac-kinobi.cjs ├── scripts │ ├── client │ │ ├── test-js.sh │ │ └── test-rust.sh │ └── program │ │ ├── build.sh │ │ ├── clean.sh │ │ ├── dump.sh │ │ ├── dump_devnet.sh │ │ └── test.sh ├── shank.cjs ├── spl-ac-kinobi.cjs └── validator.cjs ├── idls ├── bubblegum.json ├── mpl_account_compression.json ├── mpl_noop.json ├── spl_account_compression.json └── spl_noop.json ├── package.json ├── pnpm-lock.yaml └── programs └── bubblegum ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── program ├── Cargo.toml ├── Xargo.toml ├── src │ ├── asserts.rs │ ├── error.rs │ ├── lib.rs │ ├── processor │ │ ├── burn.rs │ │ ├── cancel_redeem.rs │ │ ├── collect.rs │ │ ├── compress.rs │ │ ├── create_tree.rs │ │ ├── decompress.rs │ │ ├── delegate.rs │ │ ├── delegate_and_freeze.rs │ │ ├── freeze.rs │ │ ├── mint.rs │ │ ├── mint_to_collection.rs │ │ ├── mod.rs │ │ ├── redeem.rs │ │ ├── set_and_verify_collection.rs │ │ ├── set_collection.rs │ │ ├── set_decompressible_state.rs │ │ ├── set_non_transferable.rs │ │ ├── set_tree_delegate.rs │ │ ├── thaw.rs │ │ ├── thaw_and_revoke.rs │ │ ├── transfer.rs │ │ ├── unverify_collection.rs │ │ ├── unverify_creator.rs │ │ ├── update_asset_data.rs │ │ ├── update_metadata.rs │ │ ├── verify_collection.rs │ │ └── verify_creator.rs │ ├── state │ │ ├── collect.rs │ │ ├── leaf_schema.rs │ │ ├── metaplex_adapter.rs │ │ ├── metaplex_anchor.rs │ │ └── mod.rs │ ├── traits.rs │ └── utils.rs └── tests │ ├── collection.rs │ ├── creators.rs │ ├── decompression.rs │ ├── simple.rs │ └── utils │ ├── context.rs │ ├── digital_asset.rs │ ├── mod.rs │ ├── tree.rs │ └── tx_builder.rs └── rustfmt.toml /.github/.env: -------------------------------------------------------------------------------- 1 | CARGO_TERM_COLOR=always 2 | NODE_VERSION=18.x 3 | PROGRAMS=["bubblegum"] 4 | RUST_VERSION=1.79.0 5 | SOLANA_VERSION=1.18.19 6 | ANCHOR_VERSION=0.29.0 7 | COMMIT_USER_NAME="github-actions" 8 | COMMIT_USER_EMAIL="github-actions@github.com" 9 | DEPLOY_SOLANA_VERSION=1.18.19 10 | RUST_CLIENT_RUST_VERSION=1.82.0 11 | RUST_CLIENT_SOLANA_VERSION=2.1.21 12 | -------------------------------------------------------------------------------- /.github/file-filters.yml: -------------------------------------------------------------------------------- 1 | # This file is used by the dorny/paths-filter action to figure out if a program or 2 | # client has changed and thus if it should be built or tested. Any changes in the 3 | # files listed below will trigger the appropriate workflow for that program or client. 4 | 5 | # Programs. 6 | 7 | program_common: &program_common 8 | - ".github/workflows/build-programs.yml" 9 | - ".github/workflows/test-programs.yml" 10 | - ".github/workflows/main.yml" 11 | - ".github/file-filters.yml" 12 | - ".github/.env" 13 | 14 | bubblegum_program: &bubblegum_program 15 | - *program_common 16 | - "programs/bubblegum/**" 17 | 18 | programs: &programs 19 | - *bubblegum_program 20 | 21 | # Clients. 22 | 23 | client_common: &client_common 24 | - *programs 25 | - ".github/workflows/test-js-client.yml" 26 | - ".github/workflows/test-rust-client.yml" 27 | - ".github/workflows/build-rust-client.yml" 28 | - ".github/workflows/main.yml" 29 | - ".github/file-filters.yml" 30 | - ".github/.env" 31 | - "configs/shank.cjs" 32 | - "configs/kinobi.cjs" 33 | 34 | js_client: &js_client 35 | - *client_common 36 | - "clients/js/**" 37 | 38 | rust_client: &rust_client 39 | - *client_common 40 | - "clients/rust/**" 41 | 42 | clients: &clients 43 | - *js_client 44 | - *rust_client 45 | 46 | # Any. 47 | 48 | any: &any 49 | - *programs 50 | - *clients 51 | -------------------------------------------------------------------------------- /.github/workflows/build-programs.yml: -------------------------------------------------------------------------------- 1 | name: Build Programs 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | git_ref: 7 | type: string 8 | 9 | env: 10 | CACHE: true 11 | 12 | jobs: 13 | build_programs: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Git checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ inputs.git_ref }} 21 | 22 | - name: Load environment variables 23 | run: cat .github/.env >> $GITHUB_ENV 24 | 25 | - name: Install Rust 26 | uses: metaplex-foundation/actions/install-rust@v1 27 | with: 28 | toolchain: ${{ env.RUST_VERSION }} 29 | 30 | - name: Install Solana 31 | uses: metaplex-foundation/actions/install-solana@v1 32 | with: 33 | version: ${{ env.SOLANA_VERSION }} 34 | cache: ${{ env.CACHE }} 35 | 36 | - name: Cache program dependencies 37 | if: env.CACHE == 'true' 38 | uses: metaplex-foundation/actions/cache-programs@v1 39 | 40 | - name: Build programs 41 | shell: bash 42 | working-directory: configs/scripts/program 43 | run: ./build.sh 44 | env: 45 | PROGRAMS: ${{ env.PROGRAMS }} 46 | - name: Sanitize Ref 47 | id: sanitize 48 | shell: bash 49 | run: | 50 | REF="${{ inputs.git_ref }}" 51 | if [ -z "$REF" ]; then 52 | REF="default" 53 | fi 54 | SANITIZED=${REF//\//-} 55 | echo "sanitized=$SANITIZED" >> "$GITHUB_OUTPUT" 56 | - name: Upload program builds 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: program-builds-${{ steps.sanitize.outputs.sanitized }} 60 | # First wildcard ensures exported paths are consistently under the programs folder. 61 | path: ./program*/.bin/*.so 62 | include-hidden-files: true 63 | if-no-files-found: error 64 | -------------------------------------------------------------------------------- /.github/workflows/build-rust-client.yml: -------------------------------------------------------------------------------- 1 | name: Build Rust Client 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | git_ref: 7 | type: string 8 | 9 | env: 10 | CACHE: true 11 | 12 | jobs: 13 | build_sdk: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Git checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ inputs.git_ref }} 21 | 22 | - name: Load environment variables 23 | run: cat .github/.env >> $GITHUB_ENV 24 | 25 | - name: Install Rust 26 | uses: metaplex-foundation/actions/install-rust@v1 27 | with: 28 | toolchain: ${{ env.RUST_CLIENT_RUST_VERSION }} 29 | 30 | - name: Install Solana 31 | uses: metaplex-foundation/actions/install-solana@v1 32 | with: 33 | version: ${{ env.RUST_CLIENT_SOLANA_VERSION }} 34 | cache: ${{ env.CACHE }} 35 | 36 | - name: Run cargo clippy 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: clippy 40 | args: --all-targets --all-features --no-deps --manifest-path ./clients/rust/Cargo.toml 41 | 42 | - name: Build Rust client 43 | shell: bash 44 | working-directory: clients/rust 45 | run: cargo build --all-features --release 46 | 47 | - name: Upload Rust client builds 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: rust-client-builds 51 | # First wildcard ensures exported paths are consistently under the clients folder. 52 | path: ./client*/rust/target/release/*mpl_bubblegum* 53 | if-no-files-found: error 54 | -------------------------------------------------------------------------------- /.github/workflows/create-solana-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Solana release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bump: 7 | description: Version bump 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | git_ref: 16 | description: Commit hash or branch to create release 17 | required: false 18 | type: string 19 | default: main 20 | 21 | env: 22 | CACHE: true 23 | 24 | jobs: 25 | create_release: 26 | name: Solana 27 | uses: ./.github/workflows/create-release.yml 28 | secrets: inherit 29 | with: 30 | git_ref: ${{ inputs.git_ref }} 31 | bump: ${{ inputs.bump }} 32 | program: bubblegum 33 | type: solana 34 | -------------------------------------------------------------------------------- /.github/workflows/create-svm-release.yml: -------------------------------------------------------------------------------- 1 | name: Create SVM release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bump: 7 | description: Version bump 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | git_ref: 16 | description: Commit hash or branch to create release 17 | required: false 18 | type: string 19 | default: svm 20 | 21 | env: 22 | CACHE: true 23 | 24 | jobs: 25 | create_release: 26 | name: SVM 27 | uses: ./.github/workflows/create-release.yml 28 | secrets: inherit 29 | with: 30 | git_ref: ${{ inputs.git_ref }} 31 | bump: ${{ inputs.bump }} 32 | program: bubblegum 33 | type: svm 34 | -------------------------------------------------------------------------------- /.github/workflows/test-programs.yml: -------------------------------------------------------------------------------- 1 | name: Test Programs 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | program_matrix: 7 | type: string 8 | git_ref: 9 | type: string 10 | 11 | env: 12 | CACHE: true 13 | 14 | jobs: 15 | test_programs: 16 | name: Test 17 | runs-on: ubuntu-latest-16-cores 18 | strategy: 19 | matrix: 20 | program: ${{ fromJson(inputs.program_matrix) }} 21 | steps: 22 | - name: Git checkout 23 | uses: actions/checkout@v4 24 | with: 25 | ref: ${{ inputs.git_ref }} 26 | 27 | - name: Load environment variables 28 | run: cat .github/.env >> $GITHUB_ENV 29 | 30 | - name: Install Rust 31 | uses: metaplex-foundation/actions/install-rust@v1 32 | with: 33 | toolchain: ${{ env.RUST_VERSION }} 34 | 35 | - name: Install Solana 36 | uses: metaplex-foundation/actions/install-solana@v1 37 | with: 38 | version: ${{ env.SOLANA_VERSION }} 39 | cache: ${{ env.CACHE }} 40 | 41 | - name: Cache program dependencies 42 | if: env.CACHE == 'true' 43 | uses: metaplex-foundation/actions/cache-program@v1 44 | with: 45 | folder: ./programs/${{ matrix.program }} 46 | key: program-${{ matrix.program }} 47 | 48 | - name: Run cargo fmt 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: fmt 52 | args: --manifest-path ./programs/${{ matrix.program }}/Cargo.toml -- --check 53 | 54 | - name: Run cargo clippy 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: clippy 58 | args: --all-targets --all-features --no-deps --manifest-path ./programs/${{ matrix.program }}/Cargo.toml 59 | 60 | - name: Run tests 61 | shell: bash 62 | working-directory: configs/scripts/program 63 | run: RUST_LOG=error ./test.sh 64 | env: 65 | PROGRAM: ${{ matrix.program }} 66 | -------------------------------------------------------------------------------- /.github/workflows/test-rust-client.yml: -------------------------------------------------------------------------------- 1 | name: Test Rust Client 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | program_matrix: 7 | type: string 8 | git_ref: 9 | type: string 10 | 11 | env: 12 | CACHE: true 13 | 14 | jobs: 15 | test_sdk: 16 | name: Test 17 | runs-on: ubuntu-latest-16-cores 18 | steps: 19 | - name: Git checkout 20 | uses: actions/checkout@v4 21 | with: 22 | ref: ${{ inputs.git_ref }} 23 | 24 | - name: Load environment variables 25 | run: cat .github/.env >> $GITHUB_ENV 26 | 27 | - name: Install Rust 28 | uses: metaplex-foundation/actions/install-rust@v1 29 | with: 30 | toolchain: ${{ env.RUST_CLIENT_RUST_VERSION }} 31 | 32 | - name: Install Solana 33 | uses: metaplex-foundation/actions/install-solana@v1 34 | with: 35 | version: ${{ env.RUST_CLIENT_SOLANA_VERSION }} 36 | cache: ${{ env.CACHE }} 37 | 38 | - name: Sanitize Ref 39 | id: sanitize 40 | shell: bash 41 | run: | 42 | REF="${{ inputs.git_ref }}" 43 | if [ -z "$REF" ]; then 44 | REF="default" 45 | fi 46 | SANITIZED=${REF//\//-} 47 | echo "sanitized=$SANITIZED" >> "$GITHUB_OUTPUT" 48 | 49 | - name: Download program builds 50 | uses: actions/download-artifact@v4 51 | with: 52 | name: program-builds-${{ steps.sanitize.outputs.sanitized }} 53 | 54 | - name: Run tests 55 | shell: bash 56 | working-directory: configs/scripts/client 57 | run: RUST_LOG=error ./test-rust.sh 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | **/.DS_Store 4 | **/target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | dist 9 | .amman 10 | .crates 11 | test-programs 12 | programs/.bin 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./clients/rust/Cargo.toml", 4 | "./programs/bubblegum/Cargo.toml", 5 | ] 6 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Metaplex Bubblegum 2 | 3 | This is a quick guide to help you contribute to Metaplex Bubblegum. 4 | 5 | ## Getting started 6 | 7 | The root folder has a private `package.json` containing a few scripts and JavaScript dependencies that help generate IDLs; clients and start a local validator. First, [ensure you have pnpm installed](https://pnpm.io/installation) and run the following command to install the dependencies. 8 | 9 | ```sh 10 | pnpm install 11 | ``` 12 | 13 | You will then have access to the following commands. 14 | 15 | - `pnpm programs:build` - Builds all programs. 16 | - `pnpm programs:test` - Runs BPF tests for all programs (logs disabled). 17 | - `pnpm programs:debug` - Runs BPF tests for all programs. 18 | - `pnpm programs:clean` - Deletes all generated program binaries. 19 | - `pnpm clients:rust:test` - Runs BPF tests for the Rust client SDK. 20 | - `pnpm clients:js:test` - Runs the JavaScript tests (you must first start the local validator `pnpm validator`). 21 | - `pnpm generate` - Shortcut for `pnpm generate:idls && pnpm generate:clients`. 22 | - `pnpm generate:idls` - Generate IDLs for all programs, as configured in the `configs/shank.cjs` file. 23 | - `pnpm generate:clients` - Generate clients using Kinobi, as configured in the `configs/kinobi.cjs` file. 24 | - `pnpm validator` - Start a local validator using Amman, as configured in the `configs/validator.cjs` file (logs disabled). 25 | - `pnpm validator:debug` - Start a local validator using Amman, as configured in the `configs/validator.cjs` file. 26 | - `pnpm validator:stop` - Stop the local validator. 27 | - `pnpm validator:logs` - Show the logs of the local validator. 28 | 29 | ## Managing clients 30 | 31 | Each client has its own README with instructions on how to get started. You can find them in the `clients` folder. 32 | 33 | - [JavaScript client](./clients/js/README.md) 34 | - [Rust client](./clients/rust/README.md) 35 | 36 | ## Setting up CI/CD using GitHub actions 37 | 38 | Most of the CI/CD should already be set up for you and the `.github/.env` file can be used to tweak the variables of the workflows. 39 | 40 | However, the "Deploy JS Client" workflow — configured in `.github/workflows/deploy-js.yml` — requires a few more steps to work. See the [CONTRIBUTING.md file of the JavaScript client](./clients/js/CONTRIBUTING.md#setting-up-github-actions) for more information. 41 | -------------------------------------------------------------------------------- /clients/js-solita/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaFeatures: { 5 | ecmaVersion: 2020, 6 | sourceType: 'module', 7 | }, 8 | }, 9 | extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended'], 10 | rules: { 11 | '@typescript-eslint/explicit-module-boundary-types': 'off', 12 | '@typescript-eslint/no-empty-function': 'off', 13 | '@typescript-eslint/ban-types': ['error', { extendDefaults: true, types: { '{}': false } }], 14 | '@typescript-eslint/ban-ts-comment': 'off', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /clients/js-solita/.gitignore: -------------------------------------------------------------------------------- 1 | test-ledger/ -------------------------------------------------------------------------------- /clients/js-solita/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /clients/js-solita/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | bracketSameLine: false, 4 | jsxSingleQuote: true, 5 | printWidth: 100, 6 | proseWrap: "always", 7 | semi: true, 8 | singleQuote: true, 9 | tabWidth: 2, 10 | trailingComma: "all", 11 | }; -------------------------------------------------------------------------------- /clients/js-solita/.solitarc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const path = require('path'); 3 | const programDir = path.join(__dirname, '..', '..', 'programs', 'bubblegum', 'program'); 4 | const idlDir = path.join(__dirname, '..', '..', 'idls'); 5 | const sdkDir = path.join(__dirname, 'src', 'generated'); 6 | const binaryInstallDir = path.join(__dirname, '.crates'); 7 | 8 | module.exports = { 9 | idlGenerator: 'anchor', 10 | programName: 'bubblegum', 11 | programId: 'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY', 12 | idlDir, 13 | sdkDir, 14 | binaryInstallDir, 15 | programDir, 16 | rustbin: { 17 | locked: true, 18 | versionRangeFallback: '0.27.0', 19 | }, 20 | idlHook: (idl) => { 21 | const instructions = idl.instructions.filter((ix) => { 22 | return ix.name !== 'setDecompressableState'; 23 | }); 24 | const types = idl.types.filter((ty) => { 25 | return ty.name !== 'InstructionName'; 26 | }); 27 | return { ...idl, instructions, types }; 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /clients/js-solita/README.md: -------------------------------------------------------------------------------- 1 | # mpl-bubblegum 2 | 3 | This package contains the Metaplex Bubblegum program JS SDK code. 4 | 5 | ## HOW IT WORKS 6 | [Program Overview](https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/README.md) 7 | 8 | ## API Docs 9 | 10 | Find the [bubblegum API docs published here](https://metaplex-foundation.github.io/metaplex-program-library/docs/bubblegum/index.html). 11 | 12 | ## Installation 13 | 14 | ```shell 15 | npm install @metaplex-foundation/mpl-bubblegum --save 16 | ``` 17 | 18 | ## Developing 19 | 20 | In order to update the generated SDK when the Rust contract was updated please run: 21 | ``` 22 | yarn api:gen 23 | ``` 24 | and then update the wrapper code and tests. 25 | 26 | ## Running tests 27 | 28 | ```shell 29 | ## Build the latest bubblegum 30 | pushd ../program/ 31 | cargo build-bpf 32 | popd 33 | yarn test 34 | ``` 35 | 36 | ## LICENSE 37 | 38 | Apache v2.0 39 | -------------------------------------------------------------------------------- /clients/js-solita/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/default', 3 | testEnvironment: 'node', 4 | testTimeout: 100000, 5 | resolver: "ts-jest-resolver", 6 | }; 7 | -------------------------------------------------------------------------------- /clients/js-solita/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { initCusper } from '@metaplex-foundation/cusper'; 2 | import { errorFromCode } from './generated'; 3 | 4 | export const cusper = initCusper(errorFromCode); 5 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/accounts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TreeConfig'; 2 | export * from './Voucher'; 3 | 4 | import { TreeConfig } from './TreeConfig'; 5 | import { Voucher } from './Voucher'; 6 | 7 | export const accountProviders = { TreeConfig, Voucher }; 8 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | export * from './accounts'; 3 | export * from './errors'; 4 | export * from './instructions'; 5 | export * from './types'; 6 | 7 | /** 8 | * Program address 9 | * 10 | * @category constants 11 | * @category generated 12 | */ 13 | export const PROGRAM_ADDRESS = 'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY'; 14 | 15 | /** 16 | * Program public key 17 | * 18 | * @category constants 19 | * @category generated 20 | */ 21 | export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS); 22 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/instructions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './burn'; 2 | export * from './cancelRedeem'; 3 | export * from './compress'; 4 | export * from './createTree'; 5 | export * from './decompressV1'; 6 | export * from './delegate'; 7 | export * from './mintToCollectionV1'; 8 | export * from './mintV1'; 9 | export * from './redeem'; 10 | export * from './setAndVerifyCollection'; 11 | export * from './setDecompressibleState'; 12 | export * from './setTreeDelegate'; 13 | export * from './transfer'; 14 | export * from './unverifyCollection'; 15 | export * from './unverifyCreator'; 16 | export * from './updateMetadata'; 17 | export * from './verifyCollection'; 18 | export * from './verifyCreator'; 19 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/BubblegumEventType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum BubblegumEventType { 14 | Uninitialized, 15 | LeafSchemaEvent, 16 | } 17 | 18 | /** 19 | * @category userTypes 20 | * @category generated 21 | */ 22 | export const bubblegumEventTypeBeet = beet.fixedScalarEnum( 23 | BubblegumEventType, 24 | ) as beet.FixedSizeBeet; 25 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/Collection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as web3 from '@solana/web3.js'; 9 | import * as beet from '@metaplex-foundation/beet'; 10 | import * as beetSolana from '@metaplex-foundation/beet-solana'; 11 | export type Collection = { 12 | verified: boolean; 13 | key: web3.PublicKey; 14 | }; 15 | 16 | /** 17 | * @category userTypes 18 | * @category generated 19 | */ 20 | export const collectionBeet = new beet.BeetArgsStruct( 21 | [ 22 | ['verified', beet.bool], 23 | ['key', beetSolana.publicKey], 24 | ], 25 | 'Collection', 26 | ); 27 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/Creator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as web3 from '@solana/web3.js'; 9 | import * as beetSolana from '@metaplex-foundation/beet-solana'; 10 | import * as beet from '@metaplex-foundation/beet'; 11 | export type Creator = { 12 | address: web3.PublicKey; 13 | verified: boolean; 14 | share: number; 15 | }; 16 | 17 | /** 18 | * @category userTypes 19 | * @category generated 20 | */ 21 | export const creatorBeet = new beet.BeetArgsStruct( 22 | [ 23 | ['address', beetSolana.publicKey], 24 | ['verified', beet.bool], 25 | ['share', beet.u8], 26 | ], 27 | 'Creator', 28 | ); 29 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/DecompressibleState.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum DecompressibleState { 14 | Enabled, 15 | Disabled, 16 | } 17 | 18 | /** 19 | * @category userTypes 20 | * @category generated 21 | */ 22 | export const decompressibleStateBeet = beet.fixedScalarEnum( 23 | DecompressibleState, 24 | ) as beet.FixedSizeBeet; 25 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/LeafSchema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as web3 from '@solana/web3.js'; 9 | import * as beet from '@metaplex-foundation/beet'; 10 | import * as beetSolana from '@metaplex-foundation/beet-solana'; 11 | /** 12 | * This type is used to derive the {@link LeafSchema} type as well as the de/serializer. 13 | * However don't refer to it in your code but use the {@link LeafSchema} type instead. 14 | * 15 | * @category userTypes 16 | * @category enums 17 | * @category generated 18 | * @private 19 | */ 20 | export type LeafSchemaRecord = { 21 | V1: { 22 | id: web3.PublicKey; 23 | owner: web3.PublicKey; 24 | delegate: web3.PublicKey; 25 | nonce: beet.bignum; 26 | dataHash: number[] /* size: 32 */; 27 | creatorHash: number[] /* size: 32 */; 28 | }; 29 | }; 30 | 31 | /** 32 | * Union type respresenting the LeafSchema data enum defined in Rust. 33 | * 34 | * NOTE: that it includes a `__kind` property which allows to narrow types in 35 | * switch/if statements. 36 | * Additionally `isLeafSchema*` type guards are exposed below to narrow to a specific variant. 37 | * 38 | * @category userTypes 39 | * @category enums 40 | * @category generated 41 | */ 42 | export type LeafSchema = beet.DataEnumKeyAsKind; 43 | 44 | export const isLeafSchemaV1 = (x: LeafSchema): x is LeafSchema & { __kind: 'V1' } => 45 | x.__kind === 'V1'; 46 | 47 | /** 48 | * @category userTypes 49 | * @category generated 50 | */ 51 | export const leafSchemaBeet = beet.dataEnum([ 52 | [ 53 | 'V1', 54 | new beet.BeetArgsStruct( 55 | [ 56 | ['id', beetSolana.publicKey], 57 | ['owner', beetSolana.publicKey], 58 | ['delegate', beetSolana.publicKey], 59 | ['nonce', beet.u64], 60 | ['dataHash', beet.uniformFixedSizeArray(beet.u8, 32)], 61 | ['creatorHash', beet.uniformFixedSizeArray(beet.u8, 32)], 62 | ], 63 | 'LeafSchemaRecord["V1"]', 64 | ), 65 | ], 66 | ]) as beet.FixableBeet; 67 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/MetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | import { TokenStandard, tokenStandardBeet } from './TokenStandard'; 10 | import { Collection, collectionBeet } from './Collection'; 11 | import { Uses, usesBeet } from './Uses'; 12 | import { TokenProgramVersion, tokenProgramVersionBeet } from './TokenProgramVersion'; 13 | import { Creator, creatorBeet } from './Creator'; 14 | export type MetadataArgs = { 15 | name: string; 16 | symbol: string; 17 | uri: string; 18 | sellerFeeBasisPoints: number; 19 | primarySaleHappened: boolean; 20 | isMutable: boolean; 21 | editionNonce: beet.COption; 22 | tokenStandard: beet.COption; 23 | collection: beet.COption; 24 | uses: beet.COption; 25 | tokenProgramVersion: TokenProgramVersion; 26 | creators: Creator[]; 27 | }; 28 | 29 | /** 30 | * @category userTypes 31 | * @category generated 32 | */ 33 | export const metadataArgsBeet = new beet.FixableBeetArgsStruct( 34 | [ 35 | ['name', beet.utf8String], 36 | ['symbol', beet.utf8String], 37 | ['uri', beet.utf8String], 38 | ['sellerFeeBasisPoints', beet.u16], 39 | ['primarySaleHappened', beet.bool], 40 | ['isMutable', beet.bool], 41 | ['editionNonce', beet.coption(beet.u8)], 42 | ['tokenStandard', beet.coption(tokenStandardBeet)], 43 | ['collection', beet.coption(collectionBeet)], 44 | ['uses', beet.coption(usesBeet)], 45 | ['tokenProgramVersion', tokenProgramVersionBeet], 46 | ['creators', beet.array(creatorBeet)], 47 | ], 48 | 'MetadataArgs', 49 | ); 50 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/TokenProgramVersion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum TokenProgramVersion { 14 | Original, 15 | Token2022, 16 | } 17 | 18 | /** 19 | * @category userTypes 20 | * @category generated 21 | */ 22 | export const tokenProgramVersionBeet = beet.fixedScalarEnum( 23 | TokenProgramVersion, 24 | ) as beet.FixedSizeBeet; 25 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/TokenStandard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum TokenStandard { 14 | NonFungible, 15 | FungibleAsset, 16 | Fungible, 17 | NonFungibleEdition, 18 | } 19 | 20 | /** 21 | * @category userTypes 22 | * @category generated 23 | */ 24 | export const tokenStandardBeet = beet.fixedScalarEnum(TokenStandard) as beet.FixedSizeBeet< 25 | TokenStandard, 26 | TokenStandard 27 | >; 28 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/UpdateArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | import { Creator, creatorBeet } from './Creator'; 10 | export type UpdateArgs = { 11 | name: beet.COption; 12 | symbol: beet.COption; 13 | uri: beet.COption; 14 | creators: beet.COption; 15 | sellerFeeBasisPoints: beet.COption; 16 | primarySaleHappened: beet.COption; 17 | isMutable: beet.COption; 18 | }; 19 | 20 | /** 21 | * @category userTypes 22 | * @category generated 23 | */ 24 | export const updateArgsBeet = new beet.FixableBeetArgsStruct( 25 | [ 26 | ['name', beet.coption(beet.utf8String)], 27 | ['symbol', beet.coption(beet.utf8String)], 28 | ['uri', beet.coption(beet.utf8String)], 29 | ['creators', beet.coption(beet.array(creatorBeet))], 30 | ['sellerFeeBasisPoints', beet.coption(beet.u16)], 31 | ['primarySaleHappened', beet.coption(beet.bool)], 32 | ['isMutable', beet.coption(beet.bool)], 33 | ], 34 | 'UpdateArgs', 35 | ); 36 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/UseMethod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum UseMethod { 14 | Burn, 15 | Multiple, 16 | Single, 17 | } 18 | 19 | /** 20 | * @category userTypes 21 | * @category generated 22 | */ 23 | export const useMethodBeet = beet.fixedScalarEnum(UseMethod) as beet.FixedSizeBeet< 24 | UseMethod, 25 | UseMethod 26 | >; 27 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/Uses.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | import { UseMethod, useMethodBeet } from './UseMethod'; 10 | export type Uses = { 11 | useMethod: UseMethod; 12 | remaining: beet.bignum; 13 | total: beet.bignum; 14 | }; 15 | 16 | /** 17 | * @category userTypes 18 | * @category generated 19 | */ 20 | export const usesBeet = new beet.BeetArgsStruct( 21 | [ 22 | ['useMethod', useMethodBeet], 23 | ['remaining', beet.u64], 24 | ['total', beet.u64], 25 | ], 26 | 'Uses', 27 | ); 28 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/Version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was GENERATED using the solita package. 3 | * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. 4 | * 5 | * See: https://github.com/metaplex-foundation/solita 6 | */ 7 | 8 | import * as beet from '@metaplex-foundation/beet'; 9 | /** 10 | * @category enums 11 | * @category generated 12 | */ 13 | export enum Version { 14 | V1, 15 | } 16 | 17 | /** 18 | * @category userTypes 19 | * @category generated 20 | */ 21 | export const versionBeet = beet.fixedScalarEnum(Version) as beet.FixedSizeBeet; 22 | -------------------------------------------------------------------------------- /clients/js-solita/src/generated/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BubblegumEventType'; 2 | export * from './Collection'; 3 | export * from './Creator'; 4 | export * from './DecompressibleState'; 5 | export * from './LeafSchema'; 6 | export * from './MetadataArgs'; 7 | export * from './TokenProgramVersion'; 8 | export * from './TokenStandard'; 9 | export * from './UpdateArgs'; 10 | export * from './UseMethod'; 11 | export * from './Uses'; 12 | export * from './Version'; 13 | -------------------------------------------------------------------------------- /clients/js-solita/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated'; 2 | export * from './errors'; 3 | export * from './mpl-bubblegum'; 4 | -------------------------------------------------------------------------------- /clients/js-solita/src/mpl-bubblegum.ts: -------------------------------------------------------------------------------- 1 | import { PROGRAM_ID, Creator, MetadataArgs, metadataArgsBeet } from './generated'; 2 | import { keccak_256 } from 'js-sha3'; 3 | import { PublicKey } from '@solana/web3.js'; 4 | import BN from 'bn.js'; 5 | 6 | export * from './generated'; 7 | 8 | export async function getLeafAssetId(tree: PublicKey, leafIndex: BN): Promise { 9 | const [assetId] = await PublicKey.findProgramAddress( 10 | [Buffer.from('asset', 'utf8'), tree.toBuffer(), Uint8Array.from(leafIndex.toArray('le', 8))], 11 | PROGRAM_ID, 12 | ); 13 | return assetId; 14 | } 15 | 16 | export function computeDataHash(metadata: MetadataArgs): Buffer { 17 | const [serializedMetadata] = metadataArgsBeet.serialize(metadata); 18 | const metadataHash = Buffer.from(keccak_256.digest(serializedMetadata)); 19 | 20 | const sellerFeeBasisPointsBuffer = new BN(metadata.sellerFeeBasisPoints).toBuffer('le', 2); 21 | 22 | return Buffer.from(keccak_256.digest(Buffer.concat([metadataHash, sellerFeeBasisPointsBuffer]))); 23 | } 24 | 25 | export function computeCreatorHash(creators: Creator[]) { 26 | const bufferOfCreatorData = Buffer.concat( 27 | creators.map((creator) => { 28 | return Buffer.concat([ 29 | creator.address.toBuffer(), 30 | Buffer.from([creator.verified ? 1 : 0]), 31 | Buffer.from([creator.share]), 32 | ]); 33 | }), 34 | ); 35 | return Buffer.from(keccak_256.digest(bufferOfCreatorData)); 36 | } 37 | 38 | export function computeCompressedNFTHash( 39 | assetId: PublicKey, 40 | owner: PublicKey, 41 | delegate: PublicKey, 42 | treeNonce: BN, 43 | metadata: MetadataArgs, 44 | ): Buffer { 45 | const message = Buffer.concat([ 46 | Buffer.from([0x1]), // All NFTs are version 1 right now 47 | assetId.toBuffer(), 48 | owner.toBuffer(), 49 | delegate.toBuffer(), 50 | treeNonce.toBuffer('le', 8), 51 | computeDataHash(metadata), 52 | computeCreatorHash(metadata.creators), 53 | ]); 54 | 55 | return Buffer.from(keccak_256.digest(message)); 56 | } 57 | -------------------------------------------------------------------------------- /clients/js-solita/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "baseUrl": ".", 5 | "target": "ES2018", 6 | "module": "commonjs", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "declarationMap": false, 10 | "removeComments": true, 11 | "moduleResolution": "node", 12 | "noEmit": false, 13 | "preserveWatchOutput": true, 14 | "emitDeclarationOnly": false, 15 | "importHelpers": false, 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "downlevelIteration": true, 20 | "esModuleInterop": true, 21 | "allowSyntheticDefaultImports": true, 22 | "incremental": false, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "useUnknownInCatchVariables": false 26 | }, 27 | "include": ["./src"], 28 | "exclude": ["node_modules", "dist", "build", "lib"] 29 | } 30 | -------------------------------------------------------------------------------- /clients/js-solita/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "rootDir": "." 5 | }, 6 | "include": ["./src", "test"] 7 | } 8 | -------------------------------------------------------------------------------- /clients/js-solita/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "excludeInternal": true, 4 | "excludePrivate": true, 5 | "categorizeByGroup": false, 6 | "defaultCategory": "sdk", 7 | "categoryOrder": [ 8 | "Accounts", 9 | "Instructions", 10 | "Errors", 11 | "constants", 12 | "enums", 13 | "userTypes", 14 | "sdk", 15 | "generated", 16 | "*" 17 | ], 18 | "readme": "none", 19 | "emit": "both", 20 | "out": "../../docs/bubblegum" 21 | } 22 | -------------------------------------------------------------------------------- /clients/js/.env.example: -------------------------------------------------------------------------------- 1 | READ_API_RPC_DEVNET="..." 2 | -------------------------------------------------------------------------------- /clients/js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb-base', 'airbnb-typescript/base', 'prettier'], 3 | plugins: ['prettier'], 4 | overrides: [ 5 | { 6 | files: ['src/flags.ts'], 7 | rules: { 8 | 'no-bitwise': 'off', 9 | }, 10 | }, 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | sourceType: 'module', 15 | project: 'tsconfig.json', 16 | tsconfigRootDir: __dirname, 17 | }, 18 | rules: { 19 | '@typescript-eslint/no-use-before-define': 'off', 20 | '@typescript-eslint/no-unused-vars': 'off', 21 | 'class-methods-use-this': 'off', 22 | 'import/prefer-default-export': 'off', 23 | 'import/no-cycle': 'off', 24 | 'no-underscore-dangle': 'off', 25 | 'max-classes-per-file': 'off', 26 | 'no-param-reassign': 'off', 27 | 'func-names': 'off', 28 | }, 29 | ignorePatterns: ['dist/**', '.eslintrc.js'], 30 | }; 31 | -------------------------------------------------------------------------------- /clients/js/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | docs 3 | .env 4 | -------------------------------------------------------------------------------- /clients/js/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80, 9 | "parser": "typescript" 10 | } 11 | -------------------------------------------------------------------------------- /clients/js/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript client for Mpl Bubblegum 2 | 3 | A Umi-compatible JavaScript library for the project. 4 | 5 | ## Getting started 6 | 7 | 1. First, if you're not already using Umi, [follow these instructions to install the Umi framework](https://github.com/metaplex-foundation/umi/blob/main/docs/installation.md). 8 | 2. Next, install this library using the package manager of your choice. 9 | ```sh 10 | npm install @metaplex-foundation/mpl-bubblegum 11 | ``` 12 | 3. Finally, register the library with your Umi instance like so. 13 | ```ts 14 | import { mplBubblegum } from '@metaplex-foundation/mpl-bubblegum'; 15 | umi.use(mplBubblegum()); 16 | ``` 17 | 18 | You can learn more about this library's API by reading its generated [TypeDoc documentation](https://mpl-bubblegum-js-docs.vercel.app). 19 | 20 | ## Contributing 21 | 22 | Check out the [Contributing Guide](./CONTRIBUTING.md) to learn more about how to contribute to this library. 23 | -------------------------------------------------------------------------------- /clients/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metaplex-foundation/mpl-bubblegum", 3 | "version": "5.0.1", 4 | "description": "Create and interact with compressed Metaplex NFTs", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf dist && tsc -p tsconfig.json", 9 | "build:docs": "typedoc", 10 | "test": "ava", 11 | "lint": "eslint --ext js,ts,tsx src", 12 | "lint:fix": "eslint --fix --ext js,ts,tsx src", 13 | "format": "prettier --check src test", 14 | "format:fix": "prettier --write src test" 15 | }, 16 | "files": [ 17 | "/dist/src" 18 | ], 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "homepage": "https://metaplex.com", 24 | "repository": "https://github.com/metaplex-foundation/mpl-bubblegum", 25 | "author": "Metaplex Maintainers ", 26 | "license": "Apache-2.0", 27 | "dependencies": { 28 | "@metaplex-foundation/digital-asset-standard-api": "^2.0.0", 29 | "@metaplex-foundation/mpl-account-compression": "^0.0.1", 30 | "@metaplex-foundation/mpl-token-metadata": "3.2.1", 31 | "@metaplex-foundation/mpl-toolbox": "^0.10.0", 32 | "@metaplex-foundation/spl-account-compression": "^0.0.1", 33 | "@noble/hashes": "^1.3.1", 34 | "merkletreejs": "^0.3.11" 35 | }, 36 | "peerDependencies": { 37 | "@metaplex-foundation/umi": ">= 0.8.9 <= 2.0.0" 38 | }, 39 | "devDependencies": { 40 | "@ava/typescript": "^3.0.1", 41 | "@metaplex-foundation/mpl-core": "^1.4.0", 42 | "@metaplex-foundation/umi": "^1.2.0", 43 | "@metaplex-foundation/umi-bundle-tests": "^1.0.0", 44 | "@metaplex-foundation/umi-rpc-web3js": "^1.2.0", 45 | "@solana/web3.js": "^1.73.0", 46 | "@typescript-eslint/eslint-plugin": "^5.13.0", 47 | "@typescript-eslint/parser": "^5.46.1", 48 | "ava": "^5.1.0", 49 | "dotenv": "^16.3.1", 50 | "eslint": "^8.2.0", 51 | "eslint-config-airbnb-typescript": "^17.0.0", 52 | "eslint-config-prettier": "^8.5.0", 53 | "eslint-plugin-import": "^2.26.0", 54 | "eslint-plugin-prettier": "^4.2.1", 55 | "prettier": "^2.5.1", 56 | "rimraf": "^3.0.2", 57 | "typedoc": "^0.23.16", 58 | "typedoc-plugin-expand-object-like-types": "^0.1.1", 59 | "typedoc-plugin-missing-exports": "^1.0.0", 60 | "typescript": "^4.6.2", 61 | "vercel": "^39.3.0" 62 | }, 63 | "ava": { 64 | "typescript": { 65 | "compile": false, 66 | "rewritePaths": { 67 | "src/": "dist/src/", 68 | "test/": "dist/test/" 69 | } 70 | }, 71 | "require": [ 72 | "dotenv/config" 73 | ] 74 | }, 75 | "packageManager": "pnpm@8.15.9" 76 | } 77 | -------------------------------------------------------------------------------- /clients/js/src/canTransfer.ts: -------------------------------------------------------------------------------- 1 | import { DasApiAsset } from '@metaplex-foundation/digital-asset-standard-api'; 2 | import { AssetWithProof } from './getAssetWithProof'; 3 | 4 | function isAssetWithProof( 5 | asset: AssetWithProof | DasApiAsset 6 | ): asset is AssetWithProof { 7 | return 'rpcAsset' in asset; 8 | } 9 | 10 | export function canTransfer(asset: AssetWithProof | DasApiAsset): boolean { 11 | const ownership = isAssetWithProof(asset) 12 | ? asset.rpcAsset.ownership 13 | : asset.ownership; 14 | 15 | const isFrozen = 16 | ownership.frozen === true || ownership.non_transferable === true; 17 | return !isFrozen; 18 | } 19 | -------------------------------------------------------------------------------- /clients/js/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class ReadApiError extends Error { 2 | readonly name: string = 'ReadApiError'; 3 | } 4 | -------------------------------------------------------------------------------- /clients/js/src/flags.ts: -------------------------------------------------------------------------------- 1 | export enum LeafSchemaV2Flags { 2 | None = 0, 3 | FrozenByOwner = 1 << 0, 4 | FrozenByPermDelegate = 1 << 1, 5 | NonTransferable = 1 << 2, 6 | } 7 | 8 | // Checks whether a number is a valid LeafSchemaV2Flags bitmask. 9 | export function isValidLeafSchemaV2Flags(n: unknown): n is LeafSchemaV2Flags { 10 | return ( 11 | typeof n === 'number' && 12 | Number.isInteger(n) && 13 | n >= 0 && 14 | n <= 0xff && // fits in u8 15 | (n & ~0b111) === 0 // only known bits (bits 0–2) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /clients/js/src/generated/accounts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './treeConfig'; 10 | export * from './voucher'; 11 | -------------------------------------------------------------------------------- /clients/js/src/generated/errors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './mplBubblegum'; 10 | -------------------------------------------------------------------------------- /clients/js/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './accounts'; 10 | export * from './errors'; 11 | export * from './instructions'; 12 | export * from './programs'; 13 | export * from './shared'; 14 | export * from './types'; 15 | -------------------------------------------------------------------------------- /clients/js/src/generated/instructions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './burn'; 10 | export * from './burnV2'; 11 | export * from './cancelRedeem'; 12 | export * from './collectV2'; 13 | export * from './createTreeConfig'; 14 | export * from './createTreeConfigV2'; 15 | export * from './decompressV1'; 16 | export * from './delegate'; 17 | export * from './delegateAndFreezeV2'; 18 | export * from './delegateV2'; 19 | export * from './freezeV2'; 20 | export * from './mintToCollectionV1'; 21 | export * from './mintV1'; 22 | export * from './mintV2'; 23 | export * from './redeem'; 24 | export * from './setAndVerifyCollection'; 25 | export * from './setCollectionV2'; 26 | export * from './setDecompressibleState'; 27 | export * from './setNonTransferableV2'; 28 | export * from './setTreeDelegate'; 29 | export * from './thawAndRevokeV2'; 30 | export * from './thawV2'; 31 | export * from './transfer'; 32 | export * from './transferV2'; 33 | export * from './unverifyCollection'; 34 | export * from './unverifyCreator'; 35 | export * from './unverifyCreatorV2'; 36 | export * from './updateAssetDataV2'; 37 | export * from './updateMetadata'; 38 | export * from './updateMetadataV2'; 39 | export * from './verifyCollection'; 40 | export * from './verifyCreator'; 41 | export * from './verifyCreatorV2'; 42 | -------------------------------------------------------------------------------- /clients/js/src/generated/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './mplBubblegum'; 10 | -------------------------------------------------------------------------------- /clients/js/src/generated/programs/mplBubblegum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | ClusterFilter, 11 | Context, 12 | Program, 13 | PublicKey, 14 | } from '@metaplex-foundation/umi'; 15 | import { 16 | getMplBubblegumErrorFromCode, 17 | getMplBubblegumErrorFromName, 18 | } from '../errors'; 19 | 20 | export const MPL_BUBBLEGUM_PROGRAM_ID = 21 | 'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY' as PublicKey<'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY'>; 22 | 23 | export function createMplBubblegumProgram(): Program { 24 | return { 25 | name: 'mplBubblegum', 26 | publicKey: MPL_BUBBLEGUM_PROGRAM_ID, 27 | getErrorFromCode(code: number, cause?: Error) { 28 | return getMplBubblegumErrorFromCode(code, this, cause); 29 | }, 30 | getErrorFromName(name: string, cause?: Error) { 31 | return getMplBubblegumErrorFromName(name, this, cause); 32 | }, 33 | isOnCluster() { 34 | return true; 35 | }, 36 | }; 37 | } 38 | 39 | export function getMplBubblegumProgram( 40 | context: Pick, 41 | clusterFilter?: ClusterFilter 42 | ): T { 43 | return context.programs.get('mplBubblegum', clusterFilter); 44 | } 45 | 46 | export function getMplBubblegumProgramId( 47 | context: Pick, 48 | clusterFilter?: ClusterFilter 49 | ): PublicKey { 50 | return context.programs.getPublicKey( 51 | 'mplBubblegum', 52 | MPL_BUBBLEGUM_PROGRAM_ID, 53 | clusterFilter 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/assetDataSchema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum AssetDataSchema { 12 | Binary, 13 | Json, 14 | MsgPack, 15 | } 16 | 17 | export type AssetDataSchemaArgs = AssetDataSchema; 18 | 19 | export function getAssetDataSchemaSerializer(): Serializer< 20 | AssetDataSchemaArgs, 21 | AssetDataSchema 22 | > { 23 | return scalarEnum(AssetDataSchema, { 24 | description: 'AssetDataSchema', 25 | }) as Serializer; 26 | } 27 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/bubblegumEventType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum BubblegumEventType { 12 | Uninitialized, 13 | LeafSchemaEvent, 14 | } 15 | 16 | export type BubblegumEventTypeArgs = BubblegumEventType; 17 | 18 | export function getBubblegumEventTypeSerializer(): Serializer< 19 | BubblegumEventTypeArgs, 20 | BubblegumEventType 21 | > { 22 | return scalarEnum(BubblegumEventType, { 23 | description: 'BubblegumEventType', 24 | }) as Serializer; 25 | } 26 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/collection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { PublicKey } from '@metaplex-foundation/umi'; 10 | import { 11 | Serializer, 12 | bool, 13 | publicKey as publicKeySerializer, 14 | struct, 15 | } from '@metaplex-foundation/umi/serializers'; 16 | 17 | export type Collection = { verified: boolean; key: PublicKey }; 18 | 19 | export type CollectionArgs = Collection; 20 | 21 | export function getCollectionSerializer(): Serializer< 22 | CollectionArgs, 23 | Collection 24 | > { 25 | return struct( 26 | [ 27 | ['verified', bool()], 28 | ['key', publicKeySerializer()], 29 | ], 30 | { description: 'Collection' } 31 | ) as Serializer; 32 | } 33 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/creator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { PublicKey } from '@metaplex-foundation/umi'; 10 | import { 11 | Serializer, 12 | bool, 13 | publicKey as publicKeySerializer, 14 | struct, 15 | u8, 16 | } from '@metaplex-foundation/umi/serializers'; 17 | 18 | export type Creator = { 19 | address: PublicKey; 20 | verified: boolean; 21 | /** 22 | * The percentage share. 23 | * 24 | * The value is a percentage, not basis points. 25 | */ 26 | share: number; 27 | }; 28 | 29 | export type CreatorArgs = Creator; 30 | 31 | export function getCreatorSerializer(): Serializer { 32 | return struct( 33 | [ 34 | ['address', publicKeySerializer()], 35 | ['verified', bool()], 36 | ['share', u8()], 37 | ], 38 | { description: 'Creator' } 39 | ) as Serializer; 40 | } 41 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/decompressibleState.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum DecompressibleState { 12 | Enabled, 13 | Disabled, 14 | } 15 | 16 | export type DecompressibleStateArgs = DecompressibleState; 17 | 18 | export function getDecompressibleStateSerializer(): Serializer< 19 | DecompressibleStateArgs, 20 | DecompressibleState 21 | > { 22 | return scalarEnum(DecompressibleState, { 23 | description: 'DecompressibleState', 24 | }) as Serializer; 25 | } 26 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './assetDataSchema'; 10 | export * from './bubblegumEventType'; 11 | export * from './collection'; 12 | export * from './creator'; 13 | export * from './decompressibleState'; 14 | export * from './leafSchema'; 15 | export * from './metadataArgs'; 16 | export * from './metadataArgsV2'; 17 | export * from './tokenProgramVersion'; 18 | export * from './tokenStandard'; 19 | export * from './updateArgs'; 20 | export * from './useMethod'; 21 | export * from './uses'; 22 | export * from './version'; 23 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/tokenProgramVersion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum TokenProgramVersion { 12 | Original, 13 | Token2022, 14 | } 15 | 16 | export type TokenProgramVersionArgs = TokenProgramVersion; 17 | 18 | export function getTokenProgramVersionSerializer(): Serializer< 19 | TokenProgramVersionArgs, 20 | TokenProgramVersion 21 | > { 22 | return scalarEnum(TokenProgramVersion, { 23 | description: 'TokenProgramVersion', 24 | }) as Serializer; 25 | } 26 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/tokenStandard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum TokenStandard { 12 | NonFungible, 13 | FungibleAsset, 14 | Fungible, 15 | NonFungibleEdition, 16 | } 17 | 18 | export type TokenStandardArgs = TokenStandard; 19 | 20 | export function getTokenStandardSerializer(): Serializer< 21 | TokenStandardArgs, 22 | TokenStandard 23 | > { 24 | return scalarEnum(TokenStandard, { 25 | description: 'TokenStandard', 26 | }) as Serializer; 27 | } 28 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/updateArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Option, OptionOrNullable, none } from '@metaplex-foundation/umi'; 10 | import { 11 | Serializer, 12 | array, 13 | bool, 14 | mapSerializer, 15 | option, 16 | string, 17 | struct, 18 | u16, 19 | } from '@metaplex-foundation/umi/serializers'; 20 | import { Creator, CreatorArgs, getCreatorSerializer } from '.'; 21 | 22 | export type UpdateArgs = { 23 | name: Option; 24 | symbol: Option; 25 | uri: Option; 26 | creators: Option>; 27 | sellerFeeBasisPoints: Option; 28 | primarySaleHappened: Option; 29 | isMutable: Option; 30 | }; 31 | 32 | export type UpdateArgsArgs = { 33 | name?: OptionOrNullable; 34 | symbol?: OptionOrNullable; 35 | uri?: OptionOrNullable; 36 | creators?: OptionOrNullable>; 37 | sellerFeeBasisPoints?: OptionOrNullable; 38 | primarySaleHappened?: OptionOrNullable; 39 | isMutable?: OptionOrNullable; 40 | }; 41 | 42 | export function getUpdateArgsSerializer(): Serializer< 43 | UpdateArgsArgs, 44 | UpdateArgs 45 | > { 46 | return mapSerializer( 47 | struct( 48 | [ 49 | ['name', option(string())], 50 | ['symbol', option(string())], 51 | ['uri', option(string())], 52 | ['creators', option(array(getCreatorSerializer()))], 53 | ['sellerFeeBasisPoints', option(u16())], 54 | ['primarySaleHappened', option(bool())], 55 | ['isMutable', option(bool())], 56 | ], 57 | { description: 'UpdateArgs' } 58 | ), 59 | (value) => ({ 60 | ...value, 61 | name: value.name ?? none(), 62 | symbol: value.symbol ?? none(), 63 | uri: value.uri ?? none(), 64 | creators: value.creators ?? none(), 65 | sellerFeeBasisPoints: value.sellerFeeBasisPoints ?? none(), 66 | primarySaleHappened: value.primarySaleHappened ?? none(), 67 | isMutable: value.isMutable ?? none(), 68 | }) 69 | ) as Serializer; 70 | } 71 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/useMethod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum UseMethod { 12 | Burn, 13 | Multiple, 14 | Single, 15 | } 16 | 17 | export type UseMethodArgs = UseMethod; 18 | 19 | export function getUseMethodSerializer(): Serializer { 20 | return scalarEnum(UseMethod, { 21 | description: 'UseMethod', 22 | }) as Serializer; 23 | } 24 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/uses.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; 10 | import { UseMethod, UseMethodArgs, getUseMethodSerializer } from '.'; 11 | 12 | export type Uses = { useMethod: UseMethod; remaining: bigint; total: bigint }; 13 | 14 | export type UsesArgs = { 15 | useMethod: UseMethodArgs; 16 | remaining: number | bigint; 17 | total: number | bigint; 18 | }; 19 | 20 | export function getUsesSerializer(): Serializer { 21 | return struct( 22 | [ 23 | ['useMethod', getUseMethodSerializer()], 24 | ['remaining', u64()], 25 | ['total', u64()], 26 | ], 27 | { description: 'Uses' } 28 | ) as Serializer; 29 | } 30 | -------------------------------------------------------------------------------- /clients/js/src/generated/types/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum Version { 12 | V1, 13 | V2, 14 | } 15 | 16 | export type VersionArgs = Version; 17 | 18 | export function getVersionSerializer(): Serializer { 19 | return scalarEnum(Version, { description: 'Version' }) as Serializer< 20 | VersionArgs, 21 | Version 22 | >; 23 | } 24 | -------------------------------------------------------------------------------- /clients/js/src/getCompressionProgramsForV1Ixs.ts: -------------------------------------------------------------------------------- 1 | import { Context, PublicKey } from '@metaplex-foundation/umi'; 2 | import { 3 | SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 4 | SPL_NOOP_PROGRAM_ID, 5 | } from '@metaplex-foundation/spl-account-compression'; 6 | import { 7 | MPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 8 | MPL_NOOP_PROGRAM_ID, 9 | } from '@metaplex-foundation/mpl-account-compression'; 10 | 11 | // Constants for known genesis blockhashes on Solana. 12 | const SOLANA_MAINNET_GENESIS_HASH = 13 | '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d'; 14 | const SOLANA_DEVNET_GENESIS_HASH = 15 | 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG'; 16 | const SOLANA_TESTNET_GENESIS_HASH = 17 | '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY'; 18 | 19 | export type CompressionPrograms = { 20 | logWrapper: PublicKey; 21 | compressionProgram: PublicKey; 22 | }; 23 | 24 | export async function getCompressionProgramsForV1Ixs( 25 | context: Pick 26 | ): Promise { 27 | const genesisHash = await context.rpc.call('getGenesisHash'); 28 | 29 | // Determine if the genesis hash matches known clusters. 30 | const isSolanaCluster = [ 31 | SOLANA_MAINNET_GENESIS_HASH, 32 | SOLANA_DEVNET_GENESIS_HASH, 33 | SOLANA_TESTNET_GENESIS_HASH, 34 | ].includes(genesisHash); 35 | 36 | // Return appropriate program IDs based on the cluster. 37 | if (isSolanaCluster) { 38 | return { 39 | logWrapper: SPL_NOOP_PROGRAM_ID, 40 | compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 41 | }; 42 | } 43 | return { 44 | logWrapper: MPL_NOOP_PROGRAM_ID, 45 | compressionProgram: MPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /clients/js/src/hooked/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mintAuthority'; 2 | export * from './resolvers'; 3 | -------------------------------------------------------------------------------- /clients/js/src/hooked/mintAuthority.ts: -------------------------------------------------------------------------------- 1 | import { Context, Pda, PublicKey } from '@metaplex-foundation/umi'; 2 | import { publicKey } from '@metaplex-foundation/umi/serializers'; 3 | import { MPL_BUBBLEGUM_PROGRAM_ID } from '../generated'; 4 | 5 | export function findMintAuthorityPda( 6 | context: Pick, 7 | seeds: { mint: PublicKey } 8 | ): Pda { 9 | const programId = context.programs.getPublicKey( 10 | 'mplBubblegum', 11 | MPL_BUBBLEGUM_PROGRAM_ID 12 | ); 13 | return context.eddsa.findPda(programId, [publicKey().serialize(seeds.mint)]); 14 | } 15 | -------------------------------------------------------------------------------- /clients/js/src/hooked/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { MetadataArgsArgs } from '../generated'; 2 | import { hashMetadataCreators, hashMetadataData } from '../hash'; 3 | 4 | export const resolveDataHash = ( 5 | context: any, 6 | accounts: any, 7 | args: { metadata: MetadataArgsArgs }, 8 | programId: any, 9 | isWritable: boolean 10 | ): Uint8Array => hashMetadataData(args.metadata); 11 | 12 | export const resolveCreatorHash = ( 13 | context: any, 14 | accounts: any, 15 | args: { metadata: MetadataArgsArgs }, 16 | programId: any, 17 | isWritable: boolean 18 | ): Uint8Array => hashMetadataCreators(args.metadata.creators); 19 | -------------------------------------------------------------------------------- /clients/js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createTree'; 2 | export * from './errors'; 3 | export * from './flags'; 4 | export * from './generated'; 5 | export * from './getAssetWithProof'; 6 | export * from './hash'; 7 | export * from './hooked'; 8 | export * from './leafAssetId'; 9 | export * from './merkle'; 10 | export * from './plugin'; 11 | export * from './canTransfer'; 12 | export * from './getCompressionProgramsForV1Ixs'; 13 | -------------------------------------------------------------------------------- /clients/js/src/merkle.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, publicKey, publicKeyBytes } from '@metaplex-foundation/umi'; 2 | import { keccak_256 } from '@noble/hashes/sha3'; 3 | import { MerkleTree } from 'merkletreejs'; 4 | 5 | /** 6 | * Creates a Merkle Tree from the provided data. 7 | */ 8 | const getMerkleTree = (leaves: PublicKey[], maxDepth: number): MerkleTree => 9 | new MerkleTree( 10 | [ 11 | ...leaves.map((leaf) => publicKeyBytes(leaf)), 12 | ...Array(2 ** maxDepth - leaves.length) 13 | .fill(0) 14 | .map(() => new Uint8Array(32).fill(0)), 15 | ], 16 | keccak_256 17 | ); 18 | 19 | /** 20 | * Creates a Merkle Root from the provided data. 21 | * 22 | * This root provides a short identifier for the 23 | * provided data that is unique and deterministic. 24 | * This means, we can use this root to verify that 25 | * a given data is part of the original data set. 26 | */ 27 | export const getMerkleRoot = ( 28 | leaves: PublicKey[], 29 | maxDepth: number 30 | ): PublicKey => publicKey(getMerkleTree(leaves, maxDepth).getRoot()); 31 | 32 | /** 33 | * Creates a Merkle Proof for a given data item. 34 | * 35 | * This proof can be used to verify that the given 36 | * data item is part of the original data set. 37 | */ 38 | export const getMerkleProof = ( 39 | leaves: PublicKey[], 40 | maxDepth: number, 41 | leaf: PublicKey, 42 | index?: number 43 | ): PublicKey[] => 44 | getMerkleTree(leaves, maxDepth) 45 | .getProof(Buffer.from(publicKeyBytes(leaf)), index) 46 | .map((proofItem) => publicKey(proofItem.data)); 47 | 48 | /** 49 | * Creates a Merkle Proof for a data item at a given index. 50 | * 51 | * This proof can be used to verify that the data item at 52 | * the given index is part of the original data set. 53 | */ 54 | export const getMerkleProofAtIndex = ( 55 | leaves: PublicKey[], 56 | maxDepth: number, 57 | index: number 58 | ): PublicKey[] => getMerkleProof(leaves, maxDepth, leaves[index], index); 59 | -------------------------------------------------------------------------------- /clients/js/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { UmiPlugin } from '@metaplex-foundation/umi'; 2 | import { dasApi } from '@metaplex-foundation/digital-asset-standard-api'; 3 | import { splAccountCompression } from '@metaplex-foundation/spl-account-compression'; 4 | import { createMplBubblegumProgram } from './generated'; 5 | 6 | export const mplBubblegum = (): UmiPlugin => ({ 7 | install(umi) { 8 | umi.use(dasApi()).use(splAccountCompression()); 9 | umi.programs.add(createMplBubblegumProgram(), false); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /clients/js/test/cancelRedeem.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultPublicKey, publicKey } from '@metaplex-foundation/umi'; 2 | import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; 3 | import test from 'ava'; 4 | import { 5 | fetchMerkleTree, 6 | getCurrentRoot, 7 | } from '@metaplex-foundation/spl-account-compression'; 8 | import { 9 | cancelRedeem, 10 | findVoucherPda, 11 | hashLeaf, 12 | hashMetadataCreators, 13 | hashMetadataData, 14 | redeem, 15 | } from '../src'; 16 | import { createTree, createUmi, mint } from './_setup'; 17 | 18 | test('it can cancel the redemption of a compressed NFT', async (t) => { 19 | // Given a tree with a minted NFT. 20 | const umi = await createUmi(); 21 | const merkleTree = await createTree(umi); 22 | const leafOwner = await generateSignerWithSol(umi); 23 | const { metadata, leafIndex } = await mint(umi, { 24 | merkleTree, 25 | leafOwner: leafOwner.publicKey, 26 | }); 27 | 28 | // And given that NFT was redeemed. 29 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 30 | await redeem(umi, { 31 | leafOwner, 32 | merkleTree, 33 | root: getCurrentRoot(merkleTreeAccount.tree), 34 | dataHash: hashMetadataData(metadata), 35 | creatorHash: hashMetadataCreators(metadata.creators), 36 | nonce: leafIndex, 37 | index: leafIndex, 38 | }).sendAndConfirm(umi); 39 | 40 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 41 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, defaultPublicKey()); 42 | 43 | const [voucher] = findVoucherPda(umi, { merkleTree, nonce: leafIndex }); 44 | t.true(await umi.rpc.accountExists(voucher)); 45 | 46 | // When we cancel redeem the NFT. 47 | await cancelRedeem(umi, { 48 | leafOwner, 49 | merkleTree, 50 | voucher, 51 | root: getCurrentRoot(merkleTreeAccount.tree), 52 | }).sendAndConfirm(umi); 53 | 54 | // Then the leaf was added back to the merkle tree. 55 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 56 | t.is( 57 | merkleTreeAccount.tree.rightMostPath.leaf, 58 | publicKey( 59 | hashLeaf(umi, { 60 | merkleTree, 61 | owner: leafOwner.publicKey, 62 | leafIndex, 63 | metadata, 64 | }) 65 | ) 66 | ); 67 | 68 | // And the Voucher account was removed. 69 | t.false(await umi.rpc.accountExists(voucher)); 70 | }); 71 | -------------------------------------------------------------------------------- /clients/js/test/createTree.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { TreeConfig, createTree, fetchTreeConfigFromSeeds } from '../src'; 4 | import { createUmi } from './_setup'; 5 | 6 | test('it can create a Bubblegum tree', async (t) => { 7 | // Given a brand new merkle tree signer. 8 | const umi = await createUmi(); 9 | const merkleTree = generateSigner(umi); 10 | 11 | // When we create a tree at this address. 12 | const builder = await createTree(umi, { 13 | merkleTree, 14 | maxDepth: 14, 15 | maxBufferSize: 64, 16 | }); 17 | await builder.sendAndConfirm(umi); 18 | 19 | // Then an account exists at the merkle tree address. 20 | t.true(await umi.rpc.accountExists(merkleTree.publicKey)); 21 | 22 | // And a tree config was created with the correct data. 23 | const treeConfig = await fetchTreeConfigFromSeeds(umi, { 24 | merkleTree: merkleTree.publicKey, 25 | }); 26 | t.like(treeConfig, { 27 | treeCreator: publicKey(umi.identity), 28 | treeDelegate: publicKey(umi.identity), 29 | totalMintCapacity: 2n ** 14n, 30 | numMinted: 0n, 31 | isPublic: false, 32 | }); 33 | }); 34 | 35 | test('it can create a Bubblegum tree using a newer size', async (t) => { 36 | // Given a brand new merkle tree signer. 37 | const umi = await createUmi(); 38 | const merkleTree = generateSigner(umi); 39 | 40 | // When we create a tree at this address. 41 | const builder = await createTree(umi, { 42 | merkleTree, 43 | maxDepth: 6, 44 | maxBufferSize: 16, 45 | }); 46 | await builder.sendAndConfirm(umi); 47 | 48 | // Then an account exists at the merkle tree address. 49 | t.true(await umi.rpc.accountExists(merkleTree.publicKey)); 50 | 51 | // And a tree config was created with the correct data. 52 | const treeConfig = await fetchTreeConfigFromSeeds(umi, { 53 | merkleTree: merkleTree.publicKey, 54 | }); 55 | t.like(treeConfig, { 56 | treeCreator: publicKey(umi.identity), 57 | treeDelegate: publicKey(umi.identity), 58 | totalMintCapacity: 2n ** 6n, 59 | numMinted: 0n, 60 | isPublic: false, 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /clients/js/test/createTreeV2.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { TreeConfig, createTreeV2, fetchTreeConfigFromSeeds } from '../src'; 4 | import { createUmi } from './_setup'; 5 | 6 | test('it can create a Bubblegum tree with V2 instruction', async (t) => { 7 | // Given a brand new merkle tree signer. 8 | const umi = await createUmi(); 9 | const merkleTree = generateSigner(umi); 10 | 11 | // When we create a tree at this address. 12 | const builder = await createTreeV2(umi, { 13 | merkleTree, 14 | maxDepth: 14, 15 | maxBufferSize: 64, 16 | }); 17 | await builder.sendAndConfirm(umi); 18 | 19 | // Then an account exists at the merkle tree address. 20 | t.true(await umi.rpc.accountExists(merkleTree.publicKey)); 21 | 22 | // And a tree config was created with the correct data. 23 | const treeConfig = await fetchTreeConfigFromSeeds(umi, { 24 | merkleTree: merkleTree.publicKey, 25 | }); 26 | t.like(treeConfig, { 27 | treeCreator: publicKey(umi.identity), 28 | treeDelegate: publicKey(umi.identity), 29 | totalMintCapacity: 2n ** 14n, 30 | numMinted: 0n, 31 | isPublic: false, 32 | }); 33 | }); 34 | 35 | test('it can create a Bubblegum tree with V2 instruction using a newer size', async (t) => { 36 | // Given a brand new merkle tree signer. 37 | const umi = await createUmi(); 38 | const merkleTree = generateSigner(umi); 39 | 40 | // When we create a tree at this address. 41 | const builder = await createTreeV2(umi, { 42 | merkleTree, 43 | maxDepth: 6, 44 | maxBufferSize: 16, 45 | }); 46 | await builder.sendAndConfirm(umi); 47 | 48 | // Then an account exists at the merkle tree address. 49 | t.true(await umi.rpc.accountExists(merkleTree.publicKey)); 50 | 51 | // And a tree config was created with the correct data. 52 | const treeConfig = await fetchTreeConfigFromSeeds(umi, { 53 | merkleTree: merkleTree.publicKey, 54 | }); 55 | t.like(treeConfig, { 56 | treeCreator: publicKey(umi.identity), 57 | treeDelegate: publicKey(umi.identity), 58 | totalMintCapacity: 2n ** 6n, 59 | numMinted: 0n, 60 | isPublic: false, 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /clients/js/test/delegate.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | } from '@metaplex-foundation/spl-account-compression'; 7 | import { 8 | delegate, 9 | hashLeaf, 10 | hashMetadataCreators, 11 | hashMetadataData, 12 | } from '../src'; 13 | import { createTree, createUmi, mint } from './_setup'; 14 | 15 | test('it can delegate a compressed NFT', async (t) => { 16 | // Given a tree with a minted NFT. 17 | const umi = await createUmi(); 18 | const merkleTree = await createTree(umi); 19 | const leafOwner = generateSigner(umi); 20 | const { metadata, leafIndex } = await mint(umi, { 21 | merkleTree, 22 | leafOwner: leafOwner.publicKey, 23 | }); 24 | 25 | // When the owner of the NFT delegates it to another account. 26 | const newDelegate = generateSigner(umi).publicKey; 27 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 28 | await delegate(umi, { 29 | leafOwner, 30 | previousLeafDelegate: leafOwner.publicKey, 31 | newLeafDelegate: newDelegate, 32 | merkleTree, 33 | root: getCurrentRoot(merkleTreeAccount.tree), 34 | dataHash: hashMetadataData(metadata), 35 | creatorHash: hashMetadataCreators(metadata.creators), 36 | nonce: leafIndex, 37 | index: leafIndex, 38 | proof: [], 39 | }).sendAndConfirm(umi); 40 | 41 | // Then the leaf was updated in the merkle tree. 42 | const updatedLeaf = hashLeaf(umi, { 43 | merkleTree, 44 | owner: leafOwner.publicKey, 45 | delegate: newDelegate, 46 | leafIndex, 47 | metadata, 48 | }); 49 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 50 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); 51 | }); 52 | -------------------------------------------------------------------------------- /clients/js/test/getProgram.test.ts: -------------------------------------------------------------------------------- 1 | import { samePublicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { MPL_BUBBLEGUM_PROGRAM_ID } from '../src'; 4 | import { createUmi } from './_setup'; 5 | 6 | test('it registers the program', async (t) => { 7 | // Given a Umi instance using the project's plugin. 8 | const umi = await createUmi(); 9 | 10 | // When we fetch the registered program. 11 | const program = umi.programs.get('mplBubblegum'); 12 | 13 | // Then we expect it to be the same as the program ID constant. 14 | t.true(samePublicKey(program.publicKey, MPL_BUBBLEGUM_PROGRAM_ID)); 15 | }); 16 | -------------------------------------------------------------------------------- /clients/js/test/redeem.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultPublicKey } from '@metaplex-foundation/umi'; 2 | import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; 3 | import test from 'ava'; 4 | import { 5 | fetchMerkleTree, 6 | getCurrentRoot, 7 | } from '@metaplex-foundation/spl-account-compression'; 8 | import { 9 | LeafSchema, 10 | Voucher, 11 | fetchVoucherFromSeeds, 12 | findLeafAssetIdPda, 13 | hashMetadataCreators, 14 | hashMetadataData, 15 | redeem, 16 | } from '../src'; 17 | import { createTree, createUmi, mint } from './_setup'; 18 | 19 | test('it can redeem a compressed NFT', async (t) => { 20 | // Given a tree with a minted NFT. 21 | const umi = await createUmi(); 22 | const merkleTree = await createTree(umi); 23 | const leafOwner = await generateSignerWithSol(umi); 24 | const { metadata, leafIndex } = await mint(umi, { 25 | merkleTree, 26 | leafOwner: leafOwner.publicKey, 27 | }); 28 | 29 | // When leaf owner redeems the compressed NFT. 30 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 31 | const dataHash = hashMetadataData(metadata); 32 | const creatorHash = hashMetadataCreators(metadata.creators); 33 | await redeem(umi, { 34 | leafOwner, 35 | merkleTree, 36 | root: getCurrentRoot(merkleTreeAccount.tree), 37 | dataHash, 38 | creatorHash, 39 | nonce: leafIndex, 40 | index: leafIndex, 41 | proof: [], 42 | }).sendAndConfirm(umi); 43 | 44 | // Then the leaf was removed from the merkle tree. 45 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 46 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, defaultPublicKey()); 47 | 48 | // And a new Voucher account was created. 49 | const voucher = await fetchVoucherFromSeeds(umi, { 50 | merkleTree, 51 | nonce: leafIndex, 52 | }); 53 | t.like(voucher, { 54 | merkleTree, 55 | index: leafIndex, 56 | leafSchema: { 57 | __kind: 'V1', 58 | id: findLeafAssetIdPda(umi, { merkleTree, leafIndex })[0], 59 | owner: leafOwner.publicKey, 60 | delegate: leafOwner.publicKey, 61 | nonce: BigInt(leafIndex), 62 | dataHash, 63 | creatorHash, 64 | }, 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /clients/js/test/setTreeDelegate.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { TreeConfig, fetchTreeConfigFromSeeds, setTreeDelegate } from '../src'; 4 | import { createTree, createUmi, createTreeV2 } from './_setup'; 5 | 6 | test('it can set a delegate on a Bubblegum tree', async (t) => { 7 | // Given a Bubblegum tree. 8 | const umi = await createUmi(); 9 | const merkleTree = await createTree(umi); 10 | let treeConfig = await fetchTreeConfigFromSeeds(umi, { merkleTree }); 11 | t.like(treeConfig, { 12 | treeCreator: umi.identity.publicKey, 13 | treeDelegate: umi.identity.publicKey, 14 | }); 15 | 16 | // When we set a new delegate on the tree. 17 | const treeDelegate = generateSigner(umi).publicKey; 18 | await setTreeDelegate(umi, { 19 | merkleTree, 20 | newTreeDelegate: treeDelegate, 21 | }).sendAndConfirm(umi); 22 | 23 | // Then the tree config account was updated accordingly. 24 | treeConfig = await fetchTreeConfigFromSeeds(umi, { merkleTree }); 25 | t.like(treeConfig, { 26 | treeCreator: umi.identity.publicKey, 27 | treeDelegate, 28 | }); 29 | }); 30 | 31 | test('it can set a delegate on a V2 Bubblegum tree', async (t) => { 32 | // Given a Bubblegum tree. 33 | const umi = await createUmi(); 34 | const merkleTree = await createTreeV2(umi); 35 | let treeConfig = await fetchTreeConfigFromSeeds(umi, { merkleTree }); 36 | t.like(treeConfig, { 37 | treeCreator: umi.identity.publicKey, 38 | treeDelegate: umi.identity.publicKey, 39 | }); 40 | 41 | // When we set a new delegate on the tree. 42 | const treeDelegate = generateSigner(umi).publicKey; 43 | await setTreeDelegate(umi, { 44 | merkleTree, 45 | newTreeDelegate: treeDelegate, 46 | }).sendAndConfirm(umi); 47 | 48 | // Then the tree config account was updated accordingly. 49 | treeConfig = await fetchTreeConfigFromSeeds(umi, { merkleTree }); 50 | t.like(treeConfig, { 51 | treeCreator: umi.identity.publicKey, 52 | treeDelegate, 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /clients/js/test/updateAssetDataV2.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | } from '@metaplex-foundation/mpl-account-compression'; 7 | import { 8 | updateAssetDataV2, 9 | hashLeafV2, 10 | hashMetadataCreators, 11 | hashMetadataDataV2, 12 | } from '../src'; 13 | import { createTreeV2, createUmi, mintV2 } from './_setup'; 14 | 15 | test('it cannot update asset data using V2 instructions', async (t) => { 16 | // Given a tree with a minted NFT. 17 | const umi = await createUmi(); 18 | const merkleTree = await createTreeV2(umi); 19 | const leafOwner = generateSigner(umi); 20 | const { metadata, leafIndex } = await mintV2(umi, { 21 | merkleTree, 22 | leafOwner: leafOwner.publicKey, 23 | }); 24 | 25 | // When the authority of the NFT attempts to update the asset data. 26 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 27 | const promise = updateAssetDataV2(umi, { 28 | leafOwner: leafOwner.publicKey, 29 | merkleTree, 30 | root: getCurrentRoot(merkleTreeAccount.tree), 31 | dataHash: hashMetadataDataV2(metadata), 32 | creatorHash: hashMetadataCreators(metadata.creators), 33 | nonce: leafIndex, 34 | index: leafIndex, 35 | proof: [], 36 | }).sendAndConfirm(umi); 37 | 38 | await t.throwsAsync(promise, { name: 'NotAvailable' }); 39 | 40 | // Then the leaf not was updated in the merkle tree. 41 | const updatedLeaf = hashLeafV2(umi, { 42 | merkleTree, 43 | owner: leafOwner.publicKey, 44 | leafIndex, 45 | metadata, 46 | assetData: new Uint8Array(0), 47 | }); 48 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 49 | t.is(merkleTreeAccount.tree.sequenceNumber, 1n); 50 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); 51 | }); 52 | -------------------------------------------------------------------------------- /clients/js/test/verifyCreator.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | } from '@metaplex-foundation/spl-account-compression'; 7 | import { hashLeaf, verifyCreator } from '../src'; 8 | import { createTree, createUmi, mint } from './_setup'; 9 | 10 | test('it can verify the creator of a minted compressed NFT', async (t) => { 11 | // Given a tree with a minted NFT that has two unverified creators A and B. 12 | const umi = await createUmi(); 13 | const creatorA = generateSigner(umi); 14 | const creatorB = generateSigner(umi); 15 | const merkleTree = await createTree(umi); 16 | const leafOwner = generateSigner(umi).publicKey; 17 | const { metadata, leafIndex } = await mint(umi, { 18 | merkleTree, 19 | leafOwner, 20 | metadata: { 21 | creators: [ 22 | { address: creatorA.publicKey, verified: false, share: 60 }, 23 | { address: creatorB.publicKey, verified: false, share: 40 }, 24 | ], 25 | }, 26 | }); 27 | 28 | // When creator A verifies themselves. 29 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 30 | await verifyCreator(umi, { 31 | leafOwner, 32 | creator: creatorA, 33 | merkleTree, 34 | root: getCurrentRoot(merkleTreeAccount.tree), 35 | nonce: leafIndex, 36 | index: leafIndex, 37 | metadata, 38 | proof: [], 39 | }).sendAndConfirm(umi); 40 | 41 | // Then the leaf was updated in the merkle tree. 42 | const updatedLeaf = hashLeaf(umi, { 43 | merkleTree, 44 | owner: leafOwner, 45 | leafIndex, 46 | metadata: { 47 | ...metadata, 48 | creators: [ 49 | { address: creatorA.publicKey, verified: true, share: 60 }, 50 | { address: creatorB.publicKey, verified: false, share: 40 }, 51 | ], 52 | }, 53 | }); 54 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 55 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); 56 | }); 57 | -------------------------------------------------------------------------------- /clients/js/test/verifyCreatorV2.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | } from '@metaplex-foundation/mpl-account-compression'; 7 | import { hashLeafV2, verifyCreatorV2 } from '../src'; 8 | import { createTreeV2, createUmi, mintV2 } from './_setup'; 9 | 10 | test('it can verify the creator of a minted compressed NFT using V2 instructions', async (t) => { 11 | // Given a tree with a minted NFT that has two unverified creators A and B. 12 | const umi = await createUmi(); 13 | const creatorA = generateSigner(umi); 14 | const creatorB = generateSigner(umi); 15 | const merkleTree = await createTreeV2(umi); 16 | const leafOwner = generateSigner(umi).publicKey; 17 | const { metadata, leafIndex } = await mintV2(umi, { 18 | merkleTree, 19 | leafOwner, 20 | metadata: { 21 | creators: [ 22 | { address: creatorA.publicKey, verified: false, share: 60 }, 23 | { address: creatorB.publicKey, verified: false, share: 40 }, 24 | ], 25 | }, 26 | }); 27 | 28 | // When creator A verifies themselves. 29 | let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 30 | await verifyCreatorV2(umi, { 31 | creator: creatorA, 32 | leafOwner, 33 | merkleTree, 34 | root: getCurrentRoot(merkleTreeAccount.tree), 35 | nonce: leafIndex, 36 | index: leafIndex, 37 | metadata, 38 | proof: [], 39 | }).sendAndConfirm(umi); 40 | 41 | // Then the leaf was updated in the merkle tree. 42 | const updatedLeaf = hashLeafV2(umi, { 43 | merkleTree, 44 | owner: leafOwner, 45 | leafIndex, 46 | metadata: { 47 | ...metadata, 48 | creators: [ 49 | { address: creatorA.publicKey, verified: true, share: 60 }, 50 | { address: creatorB.publicKey, verified: false, share: 40 }, 51 | ], 52 | }, 53 | }); 54 | merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 55 | t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); 56 | }); 57 | -------------------------------------------------------------------------------- /clients/js/test/verifyLeaf.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKeyBytes } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | verifyLeaf, 7 | } from '@metaplex-foundation/spl-account-compression'; 8 | import { createTree, createUmi, mint } from './_setup'; 9 | 10 | test('it can verify a leaf on the merkle tree', async (t) => { 11 | // Given a tree with a minted NFT. 12 | const umi = await createUmi(); 13 | const merkleTree = await createTree(umi); 14 | const merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 15 | const leafOwner = generateSigner(umi).publicKey; 16 | const { leaf, leafIndex } = await mint(umi, { merkleTree, leafOwner }); 17 | 18 | // When we verify that minted leaf. 19 | await verifyLeaf(umi, { 20 | merkleTree, 21 | root: getCurrentRoot(merkleTreeAccount.tree), 22 | leaf: publicKeyBytes(leaf), 23 | index: leafIndex, 24 | proof: [], 25 | }).sendAndConfirm(umi); 26 | 27 | // Then the transaction was successful. 28 | t.pass(); 29 | }); 30 | -------------------------------------------------------------------------------- /clients/js/test/verifyLeafonV2Tree.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSigner, publicKeyBytes } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { 4 | fetchMerkleTree, 5 | getCurrentRoot, 6 | verifyLeaf, 7 | } from '@metaplex-foundation/mpl-account-compression'; 8 | import { createTreeV2, createUmi, mintV2 } from './_setup'; 9 | 10 | test('it can verify a leaf on a V2 merkle tree', async (t) => { 11 | // Given a tree with a minted NFT. 12 | const umi = await createUmi(); 13 | const merkleTree = await createTreeV2(umi); 14 | const merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); 15 | const leafOwner = generateSigner(umi).publicKey; 16 | const { leaf, leafIndex } = await mintV2(umi, { merkleTree, leafOwner }); 17 | 18 | // When we verify that minted leaf. 19 | await verifyLeaf(umi, { 20 | merkleTree, 21 | root: getCurrentRoot(merkleTreeAccount.tree), 22 | leaf: publicKeyBytes(leaf), 23 | index: leafIndex, 24 | proof: [], 25 | }).sendAndConfirm(umi); 26 | 27 | // Then the transaction was successful. 28 | t.pass(); 29 | }); 30 | -------------------------------------------------------------------------------- /clients/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "baseUrl": ".", 5 | "rootDir": ".", 6 | "target": "ES2020", 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": false, 11 | "removeComments": false, 12 | "moduleResolution": "node", 13 | "noEmit": false, 14 | "preserveWatchOutput": true, 15 | "emitDeclarationOnly": false, 16 | "importHelpers": false, 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "downlevelIteration": true, 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true, 23 | "incremental": false, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "useUnknownInCatchVariables": false 27 | }, 28 | "include": ["src", "test"], 29 | "exclude": ["node_modules", "dist", "build", "lib"] 30 | } 31 | -------------------------------------------------------------------------------- /clients/js/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "includeVersion": true, 4 | "readme": "none", 5 | "out": "docs" 6 | } 7 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/.env.example: -------------------------------------------------------------------------------- 1 | READ_API_RPC_DEVNET="..." 2 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb-base', 'airbnb-typescript/base', 'prettier'], 3 | plugins: ['prettier'], 4 | overrides: [], 5 | parserOptions: { 6 | ecmaVersion: 'latest', 7 | sourceType: 'module', 8 | project: 'tsconfig.json', 9 | tsconfigRootDir: __dirname, 10 | }, 11 | rules: { 12 | '@typescript-eslint/no-use-before-define': 'off', 13 | '@typescript-eslint/no-unused-vars': 'off', 14 | 'class-methods-use-this': 'off', 15 | 'import/prefer-default-export': 'off', 16 | 'import/no-cycle': 'off', 17 | 'no-underscore-dangle': 'off', 18 | 'max-classes-per-file': 'off', 19 | 'no-param-reassign': 'off', 20 | 'func-names': 'off', 21 | }, 22 | ignorePatterns: ['dist/**', '.eslintrc.js'], 23 | }; 24 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | docs 3 | .env 4 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80, 9 | "parser": "typescript" 10 | } 11 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript client for Mpl Account Compression 2 | 3 | A Umi-compatible JavaScript library for the project. 4 | 5 | ## Getting started 6 | 7 | 1. First, if you're not already using Umi, [follow these instructions to install the Umi framework](https://github.com/metaplex-foundation/umi/blob/main/docs/installation.md). 8 | 2. Next, install this library using the package manager of your choice. 9 | 10 | ```sh 11 | npm install @metaplex-foundation/mpl-account-compression 12 | ``` 13 | 14 | 3. Finally, register the library with your Umi instance like so. 15 | 16 | ```ts 17 | import { mplAccountCompression } from '@metaplex-foundation/mpl-account-compression'; 18 | umi.use(mplAccountCompression()); 19 | ``` 20 | 21 | You can learn more about this library's API by reading its generated [TypeDoc documentation](https://mpl-account-compression-js-docs.vercel.app). 22 | 23 | ## Contributing 24 | 25 | Check out the [Contributing Guide](./CONTRIBUTING.md) to learn more about how to contribute to this library. 26 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metaplex-foundation/mpl-account-compression", 3 | "version": "0.0.1", 4 | "description": "Create and interact with compressed Metaplex NFTs", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf dist && tsc -p tsconfig.json", 9 | "build:docs": "typedoc", 10 | "test": "ava", 11 | "lint": "eslint --ext js,ts,tsx src", 12 | "lint:fix": "eslint --fix --ext js,ts,tsx src", 13 | "format": "prettier --check src test", 14 | "format:fix": "prettier --write src test" 15 | }, 16 | "files": [ 17 | "/dist/src" 18 | ], 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "homepage": "https://metaplex.com", 24 | "repository": "https://github.com/metaplex-foundation/mpl-account-compression", 25 | "author": "Metaplex Maintainers ", 26 | "license": "Apache-2.0", 27 | "dependencies": { 28 | "@metaplex-foundation/digital-asset-standard-api": "^1.0.5", 29 | "@noble/hashes": "^1.3.1", 30 | "merkletreejs": "^0.3.11" 31 | }, 32 | "peerDependencies": { 33 | "@metaplex-foundation/umi": ">= 0.8.9 <= 1" 34 | }, 35 | "devDependencies": { 36 | "@ava/typescript": "^3.0.1", 37 | "@metaplex-foundation/umi": "^1.0.0", 38 | "@metaplex-foundation/umi-bundle-tests": "^1.0.0", 39 | "@solana/web3.js": "^1.73.0", 40 | "@typescript-eslint/eslint-plugin": "^5.13.0", 41 | "@typescript-eslint/parser": "^5.46.1", 42 | "ava": "^5.1.0", 43 | "dotenv": "^16.3.1", 44 | "eslint": "^8.2.0", 45 | "eslint-config-airbnb-typescript": "^17.0.0", 46 | "eslint-config-prettier": "^8.5.0", 47 | "eslint-plugin-import": "^2.26.0", 48 | "eslint-plugin-prettier": "^4.2.1", 49 | "prettier": "^2.5.1", 50 | "rimraf": "^3.0.2", 51 | "typedoc": "^0.23.16", 52 | "typedoc-plugin-expand-object-like-types": "^0.1.1", 53 | "typedoc-plugin-missing-exports": "^1.0.0", 54 | "typescript": "^4.6.2", 55 | "vercel": "^39.3.0" 56 | }, 57 | "ava": { 58 | "typescript": { 59 | "compile": false, 60 | "rewritePaths": { 61 | "src/": "dist/src/", 62 | "test/": "dist/test/" 63 | } 64 | }, 65 | "require": [ 66 | "dotenv/config" 67 | ] 68 | }, 69 | "packageManager": "pnpm@8.15.9" 70 | } -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/accounts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './merkleTree'; 10 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/errors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './mplAccountCompression'; 10 | export * from './mplNoop'; 11 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/errors/mplNoop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Program, ProgramError } from '@metaplex-foundation/umi'; 10 | 11 | type ProgramErrorConstructor = new ( 12 | program: Program, 13 | cause?: Error 14 | ) => ProgramError; 15 | const codeToErrorMap: Map = new Map(); 16 | const nameToErrorMap: Map = new Map(); 17 | 18 | /** 19 | * Attempts to resolve a custom program error from the provided error code. 20 | * @category Errors 21 | */ 22 | export function getMplNoopErrorFromCode( 23 | code: number, 24 | program: Program, 25 | cause?: Error 26 | ): ProgramError | null { 27 | const constructor = codeToErrorMap.get(code); 28 | return constructor ? new constructor(program, cause) : null; 29 | } 30 | 31 | /** 32 | * Attempts to resolve a custom program error from the provided error name, i.e. 'Unauthorized'. 33 | * @category Errors 34 | */ 35 | export function getMplNoopErrorFromName( 36 | name: string, 37 | program: Program, 38 | cause?: Error 39 | ): ProgramError | null { 40 | const constructor = nameToErrorMap.get(name); 41 | return constructor ? new constructor(program, cause) : null; 42 | } 43 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './accounts'; 10 | export * from './errors'; 11 | export * from './instructions'; 12 | export * from './programs'; 13 | export * from './shared'; 14 | export * from './types'; 15 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/instructions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './append'; 10 | export * from './appendCanopyNodes'; 11 | export * from './closeEmptyTree'; 12 | export * from './initEmptyMerkleTree'; 13 | export * from './initPreparedTreeWithRoot'; 14 | export * from './insertOrAppend'; 15 | export * from './noopInstruction'; 16 | export * from './prepareBatchMerkleTree'; 17 | export * from './replaceLeaf'; 18 | export * from './transferAuthority'; 19 | export * from './verifyLeaf'; 20 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/instructions/noopInstruction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Context, 11 | TransactionBuilder, 12 | transactionBuilder, 13 | } from '@metaplex-foundation/umi'; 14 | import { 15 | Serializer, 16 | bytes, 17 | struct, 18 | u32, 19 | } from '@metaplex-foundation/umi/serializers'; 20 | import { 21 | ResolvedAccount, 22 | ResolvedAccountsWithIndices, 23 | getAccountMetasAndSigners, 24 | } from '../shared'; 25 | 26 | // Data. 27 | export type NoopInstructionInstructionData = { data: Uint8Array }; 28 | 29 | export type NoopInstructionInstructionDataArgs = NoopInstructionInstructionData; 30 | 31 | export function getNoopInstructionInstructionDataSerializer(): Serializer< 32 | NoopInstructionInstructionDataArgs, 33 | NoopInstructionInstructionData 34 | > { 35 | return struct( 36 | [['data', bytes({ size: u32() })]], 37 | { description: 'NoopInstructionInstructionData' } 38 | ) as Serializer< 39 | NoopInstructionInstructionDataArgs, 40 | NoopInstructionInstructionData 41 | >; 42 | } 43 | 44 | // Args. 45 | export type NoopInstructionInstructionArgs = NoopInstructionInstructionDataArgs; 46 | 47 | // Instruction. 48 | export function noopInstruction( 49 | context: Pick, 50 | input: NoopInstructionInstructionArgs 51 | ): TransactionBuilder { 52 | // Program ID. 53 | const programId = context.programs.getPublicKey( 54 | 'mplNoop', 55 | 'mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3' 56 | ); 57 | 58 | // Accounts. 59 | const resolvedAccounts: ResolvedAccountsWithIndices = {}; 60 | 61 | // Arguments. 62 | const resolvedArgs: NoopInstructionInstructionArgs = { ...input }; 63 | 64 | // Accounts in order. 65 | const orderedAccounts: ResolvedAccount[] = Object.values( 66 | resolvedAccounts 67 | ).sort((a, b) => a.index - b.index); 68 | 69 | // Keys and Signers. 70 | const [keys, signers] = getAccountMetasAndSigners( 71 | orderedAccounts, 72 | 'programId', 73 | programId 74 | ); 75 | 76 | // Data. 77 | const data = getNoopInstructionInstructionDataSerializer().serialize( 78 | resolvedArgs as NoopInstructionInstructionDataArgs 79 | ); 80 | 81 | // Bytes Created On Chain. 82 | const bytesCreatedOnChain = 0; 83 | 84 | return transactionBuilder([ 85 | { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, 86 | ]); 87 | } 88 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './mplAccountCompression'; 10 | export * from './mplNoop'; 11 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/programs/mplAccountCompression.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | ClusterFilter, 11 | Context, 12 | Program, 13 | PublicKey, 14 | } from '@metaplex-foundation/umi'; 15 | import { 16 | getMplAccountCompressionErrorFromCode, 17 | getMplAccountCompressionErrorFromName, 18 | } from '../errors'; 19 | 20 | export const MPL_ACCOUNT_COMPRESSION_PROGRAM_ID = 21 | 'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW' as PublicKey<'mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW'>; 22 | 23 | export function createMplAccountCompressionProgram(): Program { 24 | return { 25 | name: 'mplAccountCompression', 26 | publicKey: MPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 27 | getErrorFromCode(code: number, cause?: Error) { 28 | return getMplAccountCompressionErrorFromCode(code, this, cause); 29 | }, 30 | getErrorFromName(name: string, cause?: Error) { 31 | return getMplAccountCompressionErrorFromName(name, this, cause); 32 | }, 33 | isOnCluster() { 34 | return true; 35 | }, 36 | }; 37 | } 38 | 39 | export function getMplAccountCompressionProgram( 40 | context: Pick, 41 | clusterFilter?: ClusterFilter 42 | ): T { 43 | return context.programs.get('mplAccountCompression', clusterFilter); 44 | } 45 | 46 | export function getMplAccountCompressionProgramId( 47 | context: Pick, 48 | clusterFilter?: ClusterFilter 49 | ): PublicKey { 50 | return context.programs.getPublicKey( 51 | 'mplAccountCompression', 52 | MPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 53 | clusterFilter 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/programs/mplNoop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | ClusterFilter, 11 | Context, 12 | Program, 13 | PublicKey, 14 | } from '@metaplex-foundation/umi'; 15 | import { getMplNoopErrorFromCode, getMplNoopErrorFromName } from '../errors'; 16 | 17 | export const MPL_NOOP_PROGRAM_ID = 18 | 'mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3' as PublicKey<'mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3'>; 19 | 20 | export function createMplNoopProgram(): Program { 21 | return { 22 | name: 'mplNoop', 23 | publicKey: MPL_NOOP_PROGRAM_ID, 24 | getErrorFromCode(code: number, cause?: Error) { 25 | return getMplNoopErrorFromCode(code, this, cause); 26 | }, 27 | getErrorFromName(name: string, cause?: Error) { 28 | return getMplNoopErrorFromName(name, this, cause); 29 | }, 30 | isOnCluster() { 31 | return true; 32 | }, 33 | }; 34 | } 35 | 36 | export function getMplNoopProgram( 37 | context: Pick, 38 | clusterFilter?: ClusterFilter 39 | ): T { 40 | return context.programs.get('mplNoop', clusterFilter); 41 | } 42 | 43 | export function getMplNoopProgramId( 44 | context: Pick, 45 | clusterFilter?: ClusterFilter 46 | ): PublicKey { 47 | return context.programs.getPublicKey( 48 | 'mplNoop', 49 | MPL_NOOP_PROGRAM_ID, 50 | clusterFilter 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/applicationDataEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | GetDataEnumKind, 11 | GetDataEnumKindContent, 12 | Serializer, 13 | dataEnum, 14 | struct, 15 | tuple, 16 | } from '@metaplex-foundation/umi/serializers'; 17 | import { 18 | ApplicationDataEventV1, 19 | ApplicationDataEventV1Args, 20 | getApplicationDataEventV1Serializer, 21 | } from '.'; 22 | 23 | export type ApplicationDataEvent = { 24 | __kind: 'V1'; 25 | fields: [ApplicationDataEventV1]; 26 | }; 27 | 28 | export type ApplicationDataEventArgs = { 29 | __kind: 'V1'; 30 | fields: [ApplicationDataEventV1Args]; 31 | }; 32 | 33 | export function getApplicationDataEventSerializer(): Serializer< 34 | ApplicationDataEventArgs, 35 | ApplicationDataEvent 36 | > { 37 | return dataEnum( 38 | [ 39 | [ 40 | 'V1', 41 | struct>([ 42 | ['fields', tuple([getApplicationDataEventV1Serializer()])], 43 | ]), 44 | ], 45 | ], 46 | { description: 'ApplicationDataEvent' } 47 | ) as Serializer; 48 | } 49 | 50 | // Data Enum Helpers. 51 | export function applicationDataEvent( 52 | kind: 'V1', 53 | data: GetDataEnumKindContent['fields'] 54 | ): GetDataEnumKind; 55 | export function applicationDataEvent< 56 | K extends ApplicationDataEventArgs['__kind'] 57 | >(kind: K, data?: any): Extract { 58 | return Array.isArray(data) 59 | ? { __kind: kind, fields: data } 60 | : { __kind: kind, ...(data ?? {}) }; 61 | } 62 | export function isApplicationDataEvent< 63 | K extends ApplicationDataEvent['__kind'] 64 | >( 65 | kind: K, 66 | value: ApplicationDataEvent 67 | ): value is ApplicationDataEvent & { __kind: K } { 68 | return value.__kind === kind; 69 | } 70 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/applicationDataEventV1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Serializer, 11 | bytes, 12 | struct, 13 | u32, 14 | } from '@metaplex-foundation/umi/serializers'; 15 | 16 | export type ApplicationDataEventV1 = { applicationData: Uint8Array }; 17 | 18 | export type ApplicationDataEventV1Args = ApplicationDataEventV1; 19 | 20 | export function getApplicationDataEventV1Serializer(): Serializer< 21 | ApplicationDataEventV1Args, 22 | ApplicationDataEventV1 23 | > { 24 | return struct( 25 | [['applicationData', bytes({ size: u32() })]], 26 | { description: 'ApplicationDataEventV1' } 27 | ) as Serializer; 28 | } 29 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/changeLogEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | GetDataEnumKind, 11 | GetDataEnumKindContent, 12 | Serializer, 13 | dataEnum, 14 | struct, 15 | tuple, 16 | } from '@metaplex-foundation/umi/serializers'; 17 | import { 18 | ChangeLogEventV1, 19 | ChangeLogEventV1Args, 20 | getChangeLogEventV1Serializer, 21 | } from '.'; 22 | 23 | export type ChangeLogEvent = { __kind: 'V1'; fields: [ChangeLogEventV1] }; 24 | 25 | export type ChangeLogEventArgs = { 26 | __kind: 'V1'; 27 | fields: [ChangeLogEventV1Args]; 28 | }; 29 | 30 | export function getChangeLogEventSerializer(): Serializer< 31 | ChangeLogEventArgs, 32 | ChangeLogEvent 33 | > { 34 | return dataEnum( 35 | [ 36 | [ 37 | 'V1', 38 | struct>([ 39 | ['fields', tuple([getChangeLogEventV1Serializer()])], 40 | ]), 41 | ], 42 | ], 43 | { description: 'ChangeLogEvent' } 44 | ) as Serializer; 45 | } 46 | 47 | // Data Enum Helpers. 48 | export function changeLogEvent( 49 | kind: 'V1', 50 | data: GetDataEnumKindContent['fields'] 51 | ): GetDataEnumKind; 52 | export function changeLogEvent( 53 | kind: K, 54 | data?: any 55 | ): Extract { 56 | return Array.isArray(data) 57 | ? { __kind: kind, fields: data } 58 | : { __kind: kind, ...(data ?? {}) }; 59 | } 60 | export function isChangeLogEvent( 61 | kind: K, 62 | value: ChangeLogEvent 63 | ): value is ChangeLogEvent & { __kind: K } { 64 | return value.__kind === kind; 65 | } 66 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/changeLogEventV1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { PublicKey } from '@metaplex-foundation/umi'; 10 | import { 11 | Serializer, 12 | array, 13 | publicKey as publicKeySerializer, 14 | struct, 15 | u32, 16 | u64, 17 | } from '@metaplex-foundation/umi/serializers'; 18 | import { PathNode, PathNodeArgs, getPathNodeSerializer } from '.'; 19 | 20 | export type ChangeLogEventV1 = { 21 | /** Public key of the ConcurrentMerkleTree */ 22 | id: PublicKey; 23 | /** Nodes of off-chain merkle tree needed by indexer */ 24 | path: Array; 25 | /** 26 | * Index corresponding to the number of successful operations on this tree. 27 | * Used by the off-chain indexer to figure out when there are gaps to be backfilled. 28 | */ 29 | seq: bigint; 30 | /** Bitmap of node parity (used when hashing) */ 31 | index: number; 32 | }; 33 | 34 | export type ChangeLogEventV1Args = { 35 | /** Public key of the ConcurrentMerkleTree */ 36 | id: PublicKey; 37 | /** Nodes of off-chain merkle tree needed by indexer */ 38 | path: Array; 39 | /** 40 | * Index corresponding to the number of successful operations on this tree. 41 | * Used by the off-chain indexer to figure out when there are gaps to be backfilled. 42 | */ 43 | seq: number | bigint; 44 | /** Bitmap of node parity (used when hashing) */ 45 | index: number; 46 | }; 47 | 48 | export function getChangeLogEventV1Serializer(): Serializer< 49 | ChangeLogEventV1Args, 50 | ChangeLogEventV1 51 | > { 52 | return struct( 53 | [ 54 | ['id', publicKeySerializer()], 55 | ['path', array(getPathNodeSerializer())], 56 | ['seq', u64()], 57 | ['index', u32()], 58 | ], 59 | { description: 'ChangeLogEventV1' } 60 | ) as Serializer; 61 | } 62 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/compressionAccountType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum CompressionAccountType { 12 | Uninitialized, 13 | ConcurrentMerkleTree, 14 | } 15 | 16 | export type CompressionAccountTypeArgs = CompressionAccountType; 17 | 18 | export function getCompressionAccountTypeSerializer(): Serializer< 19 | CompressionAccountTypeArgs, 20 | CompressionAccountType 21 | > { 22 | return scalarEnum(CompressionAccountType, { 23 | description: 'CompressionAccountType', 24 | }) as Serializer; 25 | } 26 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/concurrentMerkleTreeHeader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; 10 | import { 11 | CompressionAccountType, 12 | CompressionAccountTypeArgs, 13 | ConcurrentMerkleTreeHeaderData, 14 | ConcurrentMerkleTreeHeaderDataArgs, 15 | getCompressionAccountTypeSerializer, 16 | getConcurrentMerkleTreeHeaderDataSerializer, 17 | } from '.'; 18 | 19 | /** 20 | * Initialization parameters for an SPL ConcurrentMerkleTree. 21 | * 22 | * Only the following permutations are valid: 23 | * 24 | * | max_depth | max_buffer_size | 25 | * | --------- | --------------------- | 26 | * | 14 | (64, 256, 1024, 2048) | 27 | * | 20 | (64, 256, 1024, 2048) | 28 | * | 24 | (64, 256, 512, 1024, 2048) | 29 | * | 26 | (64, 256, 512, 1024, 2048) | 30 | * | 30 | (512, 1024, 2048) | 31 | * 32 | */ 33 | 34 | export type ConcurrentMerkleTreeHeader = { 35 | /** Account type */ 36 | accountType: CompressionAccountType; 37 | /** Versioned header */ 38 | header: ConcurrentMerkleTreeHeaderData; 39 | }; 40 | 41 | export type ConcurrentMerkleTreeHeaderArgs = { 42 | /** Account type */ 43 | accountType: CompressionAccountTypeArgs; 44 | /** Versioned header */ 45 | header: ConcurrentMerkleTreeHeaderDataArgs; 46 | }; 47 | 48 | export function getConcurrentMerkleTreeHeaderSerializer(): Serializer< 49 | ConcurrentMerkleTreeHeaderArgs, 50 | ConcurrentMerkleTreeHeader 51 | > { 52 | return struct( 53 | [ 54 | ['accountType', getCompressionAccountTypeSerializer()], 55 | ['header', getConcurrentMerkleTreeHeaderDataSerializer()], 56 | ], 57 | { description: 'ConcurrentMerkleTreeHeader' } 58 | ) as Serializer; 59 | } 60 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './accountCompressionEvent'; 10 | export * from './applicationDataEvent'; 11 | export * from './applicationDataEventV1'; 12 | export * from './changeLogEvent'; 13 | export * from './changeLogEventV1'; 14 | export * from './compressionAccountType'; 15 | export * from './concurrentMerkleTreeHeader'; 16 | export * from './concurrentMerkleTreeHeaderData'; 17 | export * from './pathNode'; 18 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/generated/types/pathNode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Serializer, 11 | bytes, 12 | struct, 13 | u32, 14 | } from '@metaplex-foundation/umi/serializers'; 15 | 16 | export type PathNode = { node: Uint8Array; index: number }; 17 | 18 | export type PathNodeArgs = PathNode; 19 | 20 | export function getPathNodeSerializer(): Serializer { 21 | return struct( 22 | [ 23 | ['node', bytes({ size: 32 })], 24 | ['index', u32()], 25 | ], 26 | { description: 'PathNode' } 27 | ) as Serializer; 28 | } 29 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/hooked/changeLog.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@metaplex-foundation/umi'; 2 | import { 3 | array, 4 | fixSerializer, 5 | publicKey, 6 | struct, 7 | u32, 8 | } from '@metaplex-foundation/umi/serializers'; 9 | 10 | export type ChangeLog = { 11 | root: PublicKey; 12 | pathNodes: PublicKey[]; 13 | index: number; 14 | }; 15 | 16 | export type ChangeLogArgs = ChangeLog; 17 | 18 | export const getChangeLogSerializer = (maxDepth: number) => 19 | struct([ 20 | ['root', publicKey()], 21 | ['pathNodes', array(publicKey(), { size: maxDepth })], 22 | ['index', fixSerializer(u32(), 8)], 23 | ]); 24 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/hooked/concurrentMerkleTree.ts: -------------------------------------------------------------------------------- 1 | import { publicKeyBytes } from '@metaplex-foundation/umi'; 2 | import { array, struct, u64 } from '@metaplex-foundation/umi/serializers'; 3 | import { Path, PathArgs, getPathSerializer } from './path'; 4 | import { ChangeLog, ChangeLogArgs, getChangeLogSerializer } from './changeLog'; 5 | 6 | export type ConcurrentMerkleTree = { 7 | sequenceNumber: bigint; 8 | activeIndex: bigint; 9 | bufferSize: bigint; 10 | changeLogs: ChangeLog[]; 11 | rightMostPath: Path; 12 | }; 13 | 14 | export type ConcurrentMerkleTreeArgs = { 15 | sequenceNumber: bigint | number; 16 | activeIndex: bigint | number; 17 | bufferSize: bigint | number; 18 | changeLogs: ChangeLogArgs[]; 19 | rightMostPath: PathArgs; 20 | }; 21 | 22 | export const getConcurrentMerkleTreeSerializer = ( 23 | maxDepth: number, 24 | maxBufferSize: number 25 | ) => 26 | struct([ 27 | ['sequenceNumber', u64()], 28 | ['activeIndex', u64()], 29 | ['bufferSize', u64()], 30 | [ 31 | 'changeLogs', 32 | array(getChangeLogSerializer(maxDepth), { size: maxBufferSize }), 33 | ], 34 | ['rightMostPath', getPathSerializer(maxDepth)], 35 | ]); 36 | 37 | export const getCurrentRoot = ( 38 | tree: Pick 39 | ): Uint8Array => publicKeyBytes(tree.changeLogs[Number(tree.activeIndex)].root); 40 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/hooked/index.ts: -------------------------------------------------------------------------------- 1 | export * from './merkleTreeAccountData'; 2 | export * from './path'; 3 | export * from './concurrentMerkleTree'; 4 | export * from './changeLog'; 5 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/hooked/path.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@metaplex-foundation/umi'; 2 | import { 3 | Serializer, 4 | array, 5 | mapSerializer, 6 | publicKey, 7 | struct, 8 | u32, 9 | } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export type Path = { 12 | proof: PublicKey[]; 13 | leaf: PublicKey; 14 | index: number; 15 | }; 16 | 17 | export type PathArgs = Path; 18 | 19 | export function getPathSerializer( 20 | maxDepth: number 21 | ): Serializer { 22 | return mapSerializer( 23 | struct( 24 | [ 25 | ['proof', array(publicKey(), { size: maxDepth })], 26 | ['leaf', publicKey()], 27 | ['index', u32()], 28 | ['padding', u32()], 29 | ], 30 | { description: 'Path' } 31 | ), 32 | (path) => ({ ...path, padding: 0 }), 33 | (pathWithPadding) => { 34 | const { padding, ...path } = pathWithPadding; 35 | return path; 36 | } 37 | ) as Serializer; 38 | } 39 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated'; 2 | export * from './plugin'; 3 | export * from './hooked'; 4 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { UmiPlugin } from '@metaplex-foundation/umi'; 2 | import { 3 | createMplAccountCompressionProgram, 4 | createMplNoopProgram, 5 | } from './generated'; 6 | 7 | export const mplAccountCompression = (): UmiPlugin => ({ 8 | install(umi) { 9 | umi.programs.add(createMplAccountCompressionProgram(), false); 10 | umi.programs.add(createMplNoopProgram(), false); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/test/_setup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { SolAmount, TransactionWithMeta } from '@metaplex-foundation/umi'; 3 | import { createUmi as baseCreateUmi } from '@metaplex-foundation/umi-bundle-tests'; 4 | import { mplAccountCompression } from '../src'; 5 | 6 | export const createUmi = async (endpoint?: string, airdropAmount?: SolAmount) => 7 | (await baseCreateUmi(endpoint, undefined, airdropAmount)).use( 8 | mplAccountCompression() 9 | ); 10 | 11 | // TransactionWithMeta doesn't have ReturnData field that is described in 12 | // https://solana.com/docs/rpc/http/gettransaction#result 13 | // so ugly log parsing is provided 14 | export function getReturnLog( 15 | transaction: TransactionWithMeta | null 16 | ): null | [string, string, Buffer] { 17 | if (transaction === null) { 18 | return null; 19 | } 20 | const prefix = 'Program return: '; 21 | let log = transaction.meta.logs.find((logs) => logs.startsWith(prefix)); 22 | if (log === undefined) { 23 | return null; 24 | } 25 | log = log.slice(prefix.length); 26 | const [key, data] = log.split(' ', 2); 27 | const buffer = Buffer.from(data, 'base64'); 28 | return [key, data, buffer]; 29 | } 30 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/test/getProgram.test.ts: -------------------------------------------------------------------------------- 1 | import { samePublicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { MPL_ACCOUNT_COMPRESSION_PROGRAM_ID } from '../src'; 4 | import { createUmi } from './_setup'; 5 | 6 | test('it registers the program', async (t) => { 7 | // Given a Umi instance using the project's plugin. 8 | const umi = await createUmi(); 9 | 10 | // When we fetch the registered program. 11 | const program = umi.programs.get('mplAccountCompression'); 12 | 13 | // Then we expect it to be the same as the program ID constant. 14 | t.true(samePublicKey(program.publicKey, MPL_ACCOUNT_COMPRESSION_PROGRAM_ID)); 15 | }); 16 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "baseUrl": ".", 5 | "rootDir": ".", 6 | "target": "ES2020", 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": false, 11 | "removeComments": false, 12 | "moduleResolution": "node", 13 | "noEmit": false, 14 | "preserveWatchOutput": true, 15 | "emitDeclarationOnly": false, 16 | "importHelpers": false, 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "downlevelIteration": true, 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true, 23 | "incremental": false, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "useUnknownInCatchVariables": false 27 | }, 28 | "include": ["src", "test"], 29 | "exclude": ["node_modules", "dist", "build", "lib"] 30 | } 31 | -------------------------------------------------------------------------------- /clients/mpl-ac-js/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "includeVersion": true, 4 | "readme": "none", 5 | "out": "docs" 6 | } 7 | -------------------------------------------------------------------------------- /clients/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpl-bubblegum" 3 | version = "2.1.0" 4 | description = "Metaplex Bubblegum SDK" 5 | authors = ["Metaplex Developers "] 6 | repository = "https://github.com/metaplex-foundation/mpl-bubblegum" 7 | license-file = "../../LICENSE" 8 | edition = "2021" 9 | readme = "README.md" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [features] 15 | test-sbf = [] 16 | serde = ["dep:serde", "dep:serde_with", "kaigan/serde"] 17 | 18 | [dependencies] 19 | borsh = ">= 0.9, < 1.0" 20 | kaigan = ">= 0.1" 21 | modular-bitfield = "^0.11.2" 22 | num-derive = "^0.3" 23 | num-traits = "^0.2" 24 | serde = { version = "^1.0", features = ["derive"], optional = true } 25 | serde_with = { version = "^3.0", optional = true } 26 | solana-program = ">= 1.14" 27 | thiserror = "^1.0" 28 | 29 | [dev-dependencies] 30 | assert_matches = "1.5.0" 31 | bytemuck = "1.14.0" 32 | mpl-account-compression = { version = "1.0.0", features = ["no-entrypoint", "cpi"]} 33 | mpl-core = "0.10.0" 34 | mpl-noop = { version = "1.0.0", features = ["no-entrypoint"] } 35 | solana-program-test = "~2.1.21" 36 | solana-sdk = "~2.1.21" 37 | spl-account-compression = { version = "1.0.0", features = ["no-entrypoint", "cpi"]} 38 | spl-associated-token-account = { version = "6.0.0", features = ["no-entrypoint"] } 39 | spl-merkle-tree-reference = { version = "1.0.0" } 40 | spl-noop = { version = "1.0.0", features = ["no-entrypoint"] } 41 | spl-token = { version = "7.0.0", features = ["no-entrypoint"] } 42 | -------------------------------------------------------------------------------- /clients/rust/src/generated/accounts/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | pub(crate) mod tree_config; 9 | pub(crate) mod voucher; 10 | 11 | pub use self::tree_config::*; 12 | pub use self::voucher::*; 13 | -------------------------------------------------------------------------------- /clients/rust/src/generated/accounts/tree_config.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::DecompressibleState; 9 | use crate::generated::types::Version; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct TreeConfig { 17 | pub discriminator: [u8; 8], 18 | #[cfg_attr( 19 | feature = "serde", 20 | serde(with = "serde_with::As::") 21 | )] 22 | pub tree_creator: Pubkey, 23 | #[cfg_attr( 24 | feature = "serde", 25 | serde(with = "serde_with::As::") 26 | )] 27 | pub tree_delegate: Pubkey, 28 | pub total_mint_capacity: u64, 29 | pub num_minted: u64, 30 | pub is_public: bool, 31 | pub is_decompressible: DecompressibleState, 32 | pub version: Version, 33 | } 34 | 35 | impl TreeConfig { 36 | pub const LEN: usize = 96; 37 | 38 | pub fn create_pda( 39 | merkle_tree: Pubkey, 40 | bump: u8, 41 | ) -> Result { 42 | solana_program::pubkey::Pubkey::create_program_address( 43 | &[merkle_tree.as_ref(), &[bump]], 44 | &crate::MPL_BUBBLEGUM_ID, 45 | ) 46 | } 47 | 48 | pub fn find_pda(merkle_tree: &Pubkey) -> (solana_program::pubkey::Pubkey, u8) { 49 | solana_program::pubkey::Pubkey::find_program_address( 50 | &[merkle_tree.as_ref()], 51 | &crate::MPL_BUBBLEGUM_ID, 52 | ) 53 | } 54 | 55 | #[inline(always)] 56 | pub fn from_bytes(data: &[u8]) -> Result { 57 | let mut data = data; 58 | Self::deserialize(&mut data) 59 | } 60 | } 61 | 62 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for TreeConfig { 63 | type Error = std::io::Error; 64 | 65 | fn try_from( 66 | account_info: &solana_program::account_info::AccountInfo<'a>, 67 | ) -> Result { 68 | let mut data: &[u8] = &(*account_info.data).borrow(); 69 | Self::deserialize(&mut data) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /clients/rust/src/generated/accounts/voucher.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::LeafSchema; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | use solana_program::pubkey::Pubkey; 12 | 13 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct Voucher { 16 | pub discriminator: [u8; 8], 17 | pub leaf_schema: LeafSchema, 18 | pub index: u32, 19 | #[cfg_attr( 20 | feature = "serde", 21 | serde(with = "serde_with::As::") 22 | )] 23 | pub merkle_tree: Pubkey, 24 | } 25 | 26 | impl Voucher { 27 | pub fn create_pda( 28 | merkle_tree: Pubkey, 29 | nonce: u64, 30 | bump: u8, 31 | ) -> Result { 32 | solana_program::pubkey::Pubkey::create_program_address( 33 | &[ 34 | "voucher".as_bytes(), 35 | merkle_tree.as_ref(), 36 | nonce.to_string().as_ref(), 37 | &[bump], 38 | ], 39 | &crate::MPL_BUBBLEGUM_ID, 40 | ) 41 | } 42 | 43 | pub fn find_pda(merkle_tree: &Pubkey, nonce: u64) -> (solana_program::pubkey::Pubkey, u8) { 44 | solana_program::pubkey::Pubkey::find_program_address( 45 | &[ 46 | "voucher".as_bytes(), 47 | merkle_tree.as_ref(), 48 | nonce.to_string().as_ref(), 49 | ], 50 | &crate::MPL_BUBBLEGUM_ID, 51 | ) 52 | } 53 | 54 | #[inline(always)] 55 | pub fn from_bytes(data: &[u8]) -> Result { 56 | let mut data = data; 57 | Self::deserialize(&mut data) 58 | } 59 | } 60 | 61 | impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Voucher { 62 | type Error = std::io::Error; 63 | 64 | fn try_from( 65 | account_info: &solana_program::account_info::AccountInfo<'a>, 66 | ) -> Result { 67 | let mut data: &[u8] = &(*account_info.data).borrow(); 68 | Self::deserialize(&mut data) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /clients/rust/src/generated/errors/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | pub(crate) mod mpl_bubblegum; 9 | 10 | pub use self::mpl_bubblegum::MplBubblegumError; 11 | -------------------------------------------------------------------------------- /clients/rust/src/generated/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | pub(crate) mod burn; 9 | pub(crate) mod burn_v2; 10 | pub(crate) mod cancel_redeem; 11 | pub(crate) mod collect_v2; 12 | pub(crate) mod create_tree_config; 13 | pub(crate) mod create_tree_config_v2; 14 | pub(crate) mod decompress_v1; 15 | pub(crate) mod delegate; 16 | pub(crate) mod delegate_and_freeze_v2; 17 | pub(crate) mod delegate_v2; 18 | pub(crate) mod freeze_v2; 19 | pub(crate) mod mint_to_collection_v1; 20 | pub(crate) mod mint_v1; 21 | pub(crate) mod mint_v2; 22 | pub(crate) mod redeem; 23 | pub(crate) mod set_and_verify_collection; 24 | pub(crate) mod set_collection_v2; 25 | pub(crate) mod set_decompressible_state; 26 | pub(crate) mod set_non_transferable_v2; 27 | pub(crate) mod set_tree_delegate; 28 | pub(crate) mod thaw_and_revoke_v2; 29 | pub(crate) mod thaw_v2; 30 | pub(crate) mod transfer; 31 | pub(crate) mod transfer_v2; 32 | pub(crate) mod unverify_collection; 33 | pub(crate) mod unverify_creator; 34 | pub(crate) mod unverify_creator_v2; 35 | pub(crate) mod update_asset_data_v2; 36 | pub(crate) mod update_metadata; 37 | pub(crate) mod update_metadata_v2; 38 | pub(crate) mod verify_collection; 39 | pub(crate) mod verify_creator; 40 | pub(crate) mod verify_creator_v2; 41 | 42 | pub use self::burn::*; 43 | pub use self::burn_v2::*; 44 | pub use self::cancel_redeem::*; 45 | pub use self::collect_v2::*; 46 | pub use self::create_tree_config::*; 47 | pub use self::create_tree_config_v2::*; 48 | pub use self::decompress_v1::*; 49 | pub use self::delegate::*; 50 | pub use self::delegate_and_freeze_v2::*; 51 | pub use self::delegate_v2::*; 52 | pub use self::freeze_v2::*; 53 | pub use self::mint_to_collection_v1::*; 54 | pub use self::mint_v1::*; 55 | pub use self::mint_v2::*; 56 | pub use self::redeem::*; 57 | pub use self::set_and_verify_collection::*; 58 | pub use self::set_collection_v2::*; 59 | pub use self::set_decompressible_state::*; 60 | pub use self::set_non_transferable_v2::*; 61 | pub use self::set_tree_delegate::*; 62 | pub use self::thaw_and_revoke_v2::*; 63 | pub use self::thaw_v2::*; 64 | pub use self::transfer::*; 65 | pub use self::transfer_v2::*; 66 | pub use self::unverify_collection::*; 67 | pub use self::unverify_creator::*; 68 | pub use self::unverify_creator_v2::*; 69 | pub use self::update_asset_data_v2::*; 70 | pub use self::update_metadata::*; 71 | pub use self::update_metadata_v2::*; 72 | pub use self::verify_collection::*; 73 | pub use self::verify_creator::*; 74 | pub use self::verify_creator_v2::*; 75 | -------------------------------------------------------------------------------- /clients/rust/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | pub mod accounts; 9 | pub mod errors; 10 | pub mod instructions; 11 | pub mod programs; 12 | pub mod types; 13 | 14 | pub(crate) use programs::*; 15 | -------------------------------------------------------------------------------- /clients/rust/src/generated/programs.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use solana_program::{pubkey, pubkey::Pubkey}; 9 | 10 | /// `mpl_bubblegum` program ID. 11 | pub const MPL_BUBBLEGUM_ID: Pubkey = pubkey!("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY"); 12 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/asset_data_schema.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum AssetDataSchema { 14 | Binary, 15 | Json, 16 | MsgPack, 17 | } 18 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/bubblegum_event_type.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum BubblegumEventType { 14 | Uninitialized, 15 | LeafSchemaEvent, 16 | } 17 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/collection.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use solana_program::pubkey::Pubkey; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct Collection { 15 | pub verified: bool, 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | pub key: Pubkey, 21 | } 22 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/creator.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use solana_program::pubkey::Pubkey; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct Creator { 15 | #[cfg_attr( 16 | feature = "serde", 17 | serde(with = "serde_with::As::") 18 | )] 19 | pub address: Pubkey, 20 | pub verified: bool, 21 | /// The percentage share. 22 | /// 23 | /// The value is a percentage, not basis points. 24 | pub share: u8, 25 | } 26 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/decompressible_state.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum DecompressibleState { 14 | Enabled, 15 | Disabled, 16 | } 17 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/leaf_schema.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use solana_program::pubkey::Pubkey; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub enum LeafSchema { 15 | V1 { 16 | #[cfg_attr( 17 | feature = "serde", 18 | serde(with = "serde_with::As::") 19 | )] 20 | id: Pubkey, 21 | #[cfg_attr( 22 | feature = "serde", 23 | serde(with = "serde_with::As::") 24 | )] 25 | owner: Pubkey, 26 | #[cfg_attr( 27 | feature = "serde", 28 | serde(with = "serde_with::As::") 29 | )] 30 | delegate: Pubkey, 31 | nonce: u64, 32 | data_hash: [u8; 32], 33 | creator_hash: [u8; 32], 34 | }, 35 | V2 { 36 | #[cfg_attr( 37 | feature = "serde", 38 | serde(with = "serde_with::As::") 39 | )] 40 | id: Pubkey, 41 | #[cfg_attr( 42 | feature = "serde", 43 | serde(with = "serde_with::As::") 44 | )] 45 | owner: Pubkey, 46 | #[cfg_attr( 47 | feature = "serde", 48 | serde(with = "serde_with::As::") 49 | )] 50 | delegate: Pubkey, 51 | nonce: u64, 52 | data_hash: [u8; 32], 53 | creator_hash: [u8; 32], 54 | collection_hash: [u8; 32], 55 | asset_data_hash: [u8; 32], 56 | flags: u8, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/metadata_args.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::Collection; 9 | use crate::generated::types::Creator; 10 | use crate::generated::types::TokenProgramVersion; 11 | use crate::generated::types::TokenStandard; 12 | use crate::generated::types::Uses; 13 | use borsh::BorshDeserialize; 14 | use borsh::BorshSerialize; 15 | 16 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 17 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 18 | pub struct MetadataArgs { 19 | /// The name of the asset 20 | pub name: String, 21 | /// The symbol for the asset 22 | pub symbol: String, 23 | /// URI pointing to JSON representing the asset 24 | pub uri: String, 25 | /// Royalty basis points that goes to creators in secondary sales (0-10000) 26 | pub seller_fee_basis_points: u16, 27 | /// Immutable, once flipped, all sales of this metadata are considered secondary. 28 | pub primary_sale_happened: bool, 29 | /// Whether or not the data struct is mutable, default is not 30 | pub is_mutable: bool, 31 | /// nonce for easy calculation of editions, if present 32 | pub edition_nonce: Option, 33 | /// Token standard. Currently only `NonFungible` is allowed. 34 | pub token_standard: Option, 35 | /// Collection 36 | pub collection: Option, 37 | /// Uses 38 | pub uses: Option, 39 | pub token_program_version: TokenProgramVersion, 40 | pub creators: Vec, 41 | } 42 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/metadata_args_v2.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::Creator; 9 | use crate::generated::types::TokenStandard; 10 | use borsh::BorshDeserialize; 11 | use borsh::BorshSerialize; 12 | use solana_program::pubkey::Pubkey; 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub struct MetadataArgsV2 { 17 | /// The name of the asset 18 | pub name: String, 19 | /// The symbol for the asset 20 | pub symbol: String, 21 | /// URI pointing to JSON representing the asset 22 | pub uri: String, 23 | /// Royalty basis points that goes to creators in secondary sales (0-10000) 24 | pub seller_fee_basis_points: u16, 25 | /// Immutable, once flipped, all sales of this metadata are considered secondary. 26 | pub primary_sale_happened: bool, 27 | /// Whether or not the data struct is mutable, default is not 28 | pub is_mutable: bool, 29 | /// Token standard. Currently only `NonFungible` is allowed. 30 | pub token_standard: Option, 31 | /// Creator array 32 | pub creators: Vec, 33 | /// Collection. Note in V2 its just a `Pubkey` and is always considered verified. 34 | pub collection: Option, 35 | } 36 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | pub(crate) mod asset_data_schema; 9 | pub(crate) mod bubblegum_event_type; 10 | pub(crate) mod collection; 11 | pub(crate) mod creator; 12 | pub(crate) mod decompressible_state; 13 | pub(crate) mod leaf_schema; 14 | pub(crate) mod metadata_args; 15 | pub(crate) mod metadata_args_v2; 16 | pub(crate) mod token_program_version; 17 | pub(crate) mod token_standard; 18 | pub(crate) mod update_args; 19 | pub(crate) mod use_method; 20 | pub(crate) mod uses; 21 | pub(crate) mod version; 22 | 23 | pub use self::asset_data_schema::*; 24 | pub use self::bubblegum_event_type::*; 25 | pub use self::collection::*; 26 | pub use self::creator::*; 27 | pub use self::decompressible_state::*; 28 | pub use self::leaf_schema::*; 29 | pub use self::metadata_args::*; 30 | pub use self::metadata_args_v2::*; 31 | pub use self::token_program_version::*; 32 | pub use self::token_standard::*; 33 | pub use self::update_args::*; 34 | pub use self::use_method::*; 35 | pub use self::uses::*; 36 | pub use self::version::*; 37 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/token_program_version.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum TokenProgramVersion { 14 | Original, 15 | Token2022, 16 | } 17 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/token_standard.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum TokenStandard { 14 | NonFungible, 15 | FungibleAsset, 16 | Fungible, 17 | NonFungibleEdition, 18 | } 19 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/update_args.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::Creator; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct UpdateArgs { 15 | pub name: Option, 16 | pub symbol: Option, 17 | pub uri: Option, 18 | pub creators: Option>, 19 | pub seller_fee_basis_points: Option, 20 | pub primary_sale_happened: Option, 21 | pub is_mutable: Option, 22 | } 23 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/use_method.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum UseMethod { 14 | Burn, 15 | Multiple, 16 | Single, 17 | } 18 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/uses.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use crate::generated::types::UseMethod; 9 | use borsh::BorshDeserialize; 10 | use borsh::BorshSerialize; 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct Uses { 15 | pub use_method: UseMethod, 16 | pub remaining: u64, 17 | pub total: u64, 18 | } 19 | -------------------------------------------------------------------------------- /clients/rust/src/generated/types/version.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the kinobi library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun kinobi to update it. 4 | //! 5 | //! [https://github.com/metaplex-foundation/kinobi] 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub enum Version { 14 | V1, 15 | V2, 16 | } 17 | -------------------------------------------------------------------------------- /clients/rust/src/hash.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{keccak, pubkey::Pubkey}; 2 | use std::io::Result; 3 | 4 | use crate::{traits::MetadataArgsCommon, types::Creator}; 5 | 6 | /// Computes the hash of the creators. 7 | /// 8 | /// The hash is computed as the keccak256 hash of the creators bytes. 9 | pub fn hash_creators(creators: &[Creator]) -> [u8; 32] { 10 | // convert creator Vec to bytes Vec 11 | let creator_data = creators 12 | .iter() 13 | .map(|c| [c.address.as_ref(), &[c.verified as u8], &[c.share]].concat()) 14 | .collect::>(); 15 | // computes the hash 16 | keccak::hashv( 17 | creator_data 18 | .iter() 19 | .map(|c| c.as_slice()) 20 | .collect::>() 21 | .as_ref(), 22 | ) 23 | .to_bytes() 24 | } 25 | 26 | /// Computes the hash of the metadata. 27 | /// 28 | /// The hash is computed as the keccak256 hash of the metadata bytes, which is 29 | /// then hashed with the `seller_fee_basis_points`. 30 | pub fn hash_metadata(metadata: &T) -> Result<[u8; 32]> { 31 | let metadata_args_hash = keccak::hashv(&[metadata.try_to_vec()?.as_slice()]); 32 | // Calculate new data hash. 33 | Ok(keccak::hashv(&[ 34 | &metadata_args_hash.to_bytes(), 35 | &metadata.seller_fee_basis_points().to_le_bytes(), 36 | ]) 37 | .to_bytes()) 38 | } 39 | 40 | pub const DEFAULT_COLLECTION: Pubkey = Pubkey::new_from_array([0u8; 32]); 41 | 42 | /// Computes the hash of the collection (or if `None` provides default) for `LeafSchemaV2`. 43 | pub fn hash_collection_option(collection: Option) -> Result<[u8; 32]> { 44 | let collection_key = collection.unwrap_or(DEFAULT_COLLECTION); 45 | Ok(keccak::hashv(&[collection_key.as_ref()]).to_bytes()) 46 | } 47 | 48 | /// Default collection hash for `LeafSchemaV2`. 49 | pub const DEFAULT_COLLECTION_HASH: [u8; 32] = [ 50 | 41, 13, 236, 217, 84, 139, 98, 168, 214, 3, 69, 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 51 | 64, 8, 246, 54, 47, 147, 22, 14, 243, 229, 99, 52 | ]; 53 | 54 | /// Computes the hash of the asset data (or if `None` provides default) for `LeafSchemaV2`. 55 | pub fn hash_asset_data_option(asset_data: Option<&[u8]>) -> Result<[u8; 32]> { 56 | let data = asset_data.unwrap_or(b""); // Treat None as empty data 57 | Ok(keccak::hashv(&[data]).to_bytes()) 58 | } 59 | 60 | /// Default asset data hash for `LeafSchemaV2`. 61 | pub const DEFAULT_ASSET_DATA_HASH: [u8; 32] = [ 62 | 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 63 | 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112, 64 | ]; 65 | -------------------------------------------------------------------------------- /clients/rust/src/utils.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | 3 | /// Computes the asset id of an asset given its tree and nonce values. 4 | pub fn get_asset_id(tree: &Pubkey, nonce: u64) -> Pubkey { 5 | Pubkey::find_program_address(&[b"asset", tree.as_ref(), &nonce.to_le_bytes()], &crate::ID).0 6 | } 7 | -------------------------------------------------------------------------------- /clients/rust/tests/setup/mod.rs: -------------------------------------------------------------------------------- 1 | mod tree_manager; 2 | pub use tree_manager::*; 3 | 4 | use solana_program::pubkey::Pubkey; 5 | use solana_program_test::{ProgramTest, ProgramTestContext}; 6 | use solana_sdk::account::Account; 7 | 8 | /// Asserts that a given error is a custom instruction error. 9 | #[macro_export] 10 | macro_rules! assert_custom_instruction_error { 11 | ($ix:expr, $error:expr, $matcher:pat) => { 12 | match $error { 13 | solana_program_test::BanksClientError::TransactionError( 14 | solana_sdk::transaction::TransactionError::InstructionError( 15 | $ix, 16 | solana_sdk::instruction::InstructionError::Custom(x), 17 | ), 18 | ) => match num_traits::FromPrimitive::from_i32(x as i32) { 19 | Some($matcher) => assert!(true), 20 | Some(other) => { 21 | assert!( 22 | false, 23 | "Expected another custom instruction error than '{:#?}'", 24 | other 25 | ) 26 | } 27 | None => assert!(false, "Expected custom instruction error"), 28 | }, 29 | err => assert!( 30 | false, 31 | "Expected custom instruction error but got '{:#?}'", 32 | err 33 | ), 34 | }; 35 | }; 36 | } 37 | 38 | /// Setup a program test with the required programs. 39 | pub fn create_program_test() -> ProgramTest { 40 | let mut program_test = ProgramTest::new("bubblegum", mpl_bubblegum::ID, None); 41 | // V1: SPL-based programs 42 | program_test.add_program("spl_account_compression", spl_account_compression::ID, None); 43 | program_test.add_program("spl_noop", spl_noop::ID, None); 44 | 45 | // V2: MPL-based programs 46 | program_test.add_program("mpl_account_compression", mpl_account_compression::ID, None); 47 | program_test.add_program("mpl_noop", mpl_noop::ID, None); 48 | program_test.add_program("mpl_core_program", mpl_core::ID, None); 49 | program_test 50 | } 51 | 52 | /// Looks up the account for a given pubkey. 53 | pub async fn find_account(context: &mut ProgramTestContext, pubkey: &Pubkey) -> Option { 54 | context.banks_client.get_account(*pubkey).await.unwrap() 55 | } 56 | 57 | /// Returns the `Account` for a given pubkey. 58 | pub async fn get_account(context: &mut ProgramTestContext, pubkey: &Pubkey) -> Account { 59 | context 60 | .banks_client 61 | .get_account(*pubkey) 62 | .await 63 | .unwrap() 64 | .expect("account not found") 65 | } 66 | -------------------------------------------------------------------------------- /clients/spl-ac-js/.env.example: -------------------------------------------------------------------------------- 1 | READ_API_RPC_DEVNET="..." 2 | -------------------------------------------------------------------------------- /clients/spl-ac-js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb-base', 'airbnb-typescript/base', 'prettier'], 3 | plugins: ['prettier'], 4 | overrides: [], 5 | parserOptions: { 6 | ecmaVersion: 'latest', 7 | sourceType: 'module', 8 | project: 'tsconfig.json', 9 | tsconfigRootDir: __dirname, 10 | }, 11 | rules: { 12 | '@typescript-eslint/no-use-before-define': 'off', 13 | '@typescript-eslint/no-unused-vars': 'off', 14 | 'class-methods-use-this': 'off', 15 | 'import/prefer-default-export': 'off', 16 | 'import/no-cycle': 'off', 17 | 'no-underscore-dangle': 'off', 18 | 'max-classes-per-file': 'off', 19 | 'no-param-reassign': 'off', 20 | 'func-names': 'off', 21 | }, 22 | ignorePatterns: ['dist/**', '.eslintrc.js'], 23 | }; 24 | -------------------------------------------------------------------------------- /clients/spl-ac-js/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | docs 3 | .env 4 | -------------------------------------------------------------------------------- /clients/spl-ac-js/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80, 9 | "parser": "typescript" 10 | } 11 | -------------------------------------------------------------------------------- /clients/spl-ac-js/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript client for Spl Account Compression 2 | 3 | A Umi-compatible JavaScript library for the project. 4 | 5 | ## Getting started 6 | 7 | 1. First, if you're not already using Umi, [follow these instructions to install the Umi framework](https://github.com/metaplex-foundation/umi/blob/main/docs/installation.md). 8 | 2. Next, install this library using the package manager of your choice. 9 | 10 | ```sh 11 | npm install @metaplex-foundation/spl-account-compression 12 | ``` 13 | 14 | 3. Finally, register the library with your Umi instance like so. 15 | 16 | ```ts 17 | import { splAccountCompression } from '@metaplex-foundation/spl-account-compression'; 18 | umi.use(splAccountCompression()); 19 | ``` 20 | 21 | You can learn more about this library's API by reading its generated [TypeDoc documentation](https://spl-account-compression-js-docs.vercel.app). 22 | 23 | ## Contributing 24 | 25 | Check out the [Contributing Guide](./CONTRIBUTING.md) to learn more about how to contribute to this library. 26 | -------------------------------------------------------------------------------- /clients/spl-ac-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metaplex-foundation/spl-account-compression", 3 | "version": "0.0.1", 4 | "description": "Create and interact with compressed Metaplex NFTs", 5 | "main": "dist/src/index.js", 6 | "types": "dist/src/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf dist && tsc -p tsconfig.json", 9 | "build:docs": "typedoc", 10 | "test": "ava", 11 | "lint": "eslint --ext js,ts,tsx src", 12 | "lint:fix": "eslint --fix --ext js,ts,tsx src", 13 | "format": "prettier --check src test", 14 | "format:fix": "prettier --write src test" 15 | }, 16 | "files": [ 17 | "/dist/src" 18 | ], 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "homepage": "https://metaplex.com", 24 | "repository": "https://github.com/metaplex-foundation/mpl-bubblegum", 25 | "author": "Metaplex Maintainers ", 26 | "license": "Apache-2.0", 27 | "dependencies": { 28 | "@metaplex-foundation/digital-asset-standard-api": "^1.0.5", 29 | "@noble/hashes": "^1.3.1", 30 | "merkletreejs": "^0.3.11" 31 | }, 32 | "peerDependencies": { 33 | "@metaplex-foundation/umi": ">= 0.8.9 <= 1" 34 | }, 35 | "devDependencies": { 36 | "@ava/typescript": "^3.0.1", 37 | "@metaplex-foundation/umi": "^1.0.0", 38 | "@metaplex-foundation/umi-bundle-tests": "^1.0.0", 39 | "@solana/web3.js": "^1.73.0", 40 | "@typescript-eslint/eslint-plugin": "^5.13.0", 41 | "@typescript-eslint/parser": "^5.46.1", 42 | "ava": "^5.1.0", 43 | "dotenv": "^16.3.1", 44 | "eslint": "^8.2.0", 45 | "eslint-config-airbnb-typescript": "^17.0.0", 46 | "eslint-config-prettier": "^8.5.0", 47 | "eslint-plugin-import": "^2.26.0", 48 | "eslint-plugin-prettier": "^4.2.1", 49 | "prettier": "^2.5.1", 50 | "rimraf": "^3.0.2", 51 | "typedoc": "^0.23.16", 52 | "typedoc-plugin-expand-object-like-types": "^0.1.1", 53 | "typedoc-plugin-missing-exports": "^1.0.0", 54 | "typescript": "^4.6.2", 55 | "vercel": "^39.3.0" 56 | }, 57 | "ava": { 58 | "typescript": { 59 | "compile": false, 60 | "rewritePaths": { 61 | "src/": "dist/src/", 62 | "test/": "dist/test/" 63 | } 64 | }, 65 | "require": [ 66 | "dotenv/config" 67 | ] 68 | }, 69 | "packageManager": "pnpm@8.15.9" 70 | } -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/accounts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './merkleTree'; 10 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/errors/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './splAccountCompression'; 10 | export * from './splNoop'; 11 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/errors/splNoop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Program, ProgramError } from '@metaplex-foundation/umi'; 10 | 11 | type ProgramErrorConstructor = new ( 12 | program: Program, 13 | cause?: Error 14 | ) => ProgramError; 15 | const codeToErrorMap: Map = new Map(); 16 | const nameToErrorMap: Map = new Map(); 17 | 18 | /** 19 | * Attempts to resolve a custom program error from the provided error code. 20 | * @category Errors 21 | */ 22 | export function getSplNoopErrorFromCode( 23 | code: number, 24 | program: Program, 25 | cause?: Error 26 | ): ProgramError | null { 27 | const constructor = codeToErrorMap.get(code); 28 | return constructor ? new constructor(program, cause) : null; 29 | } 30 | 31 | /** 32 | * Attempts to resolve a custom program error from the provided error name, i.e. 'Unauthorized'. 33 | * @category Errors 34 | */ 35 | export function getSplNoopErrorFromName( 36 | name: string, 37 | program: Program, 38 | cause?: Error 39 | ): ProgramError | null { 40 | const constructor = nameToErrorMap.get(name); 41 | return constructor ? new constructor(program, cause) : null; 42 | } 43 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './accounts'; 10 | export * from './errors'; 11 | export * from './instructions'; 12 | export * from './programs'; 13 | export * from './shared'; 14 | export * from './types'; 15 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/instructions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './append'; 10 | export * from './closeEmptyTree'; 11 | export * from './initEmptyMerkleTree'; 12 | export * from './insertOrAppend'; 13 | export * from './noopInstruction'; 14 | export * from './replaceLeaf'; 15 | export * from './transferAuthority'; 16 | export * from './verifyLeaf'; 17 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/instructions/noopInstruction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Context, 11 | TransactionBuilder, 12 | transactionBuilder, 13 | } from '@metaplex-foundation/umi'; 14 | import { 15 | Serializer, 16 | bytes, 17 | struct, 18 | u32, 19 | } from '@metaplex-foundation/umi/serializers'; 20 | import { 21 | ResolvedAccount, 22 | ResolvedAccountsWithIndices, 23 | getAccountMetasAndSigners, 24 | } from '../shared'; 25 | 26 | // Data. 27 | export type NoopInstructionInstructionData = { data: Uint8Array }; 28 | 29 | export type NoopInstructionInstructionDataArgs = NoopInstructionInstructionData; 30 | 31 | export function getNoopInstructionInstructionDataSerializer(): Serializer< 32 | NoopInstructionInstructionDataArgs, 33 | NoopInstructionInstructionData 34 | > { 35 | return struct( 36 | [['data', bytes({ size: u32() })]], 37 | { description: 'NoopInstructionInstructionData' } 38 | ) as Serializer< 39 | NoopInstructionInstructionDataArgs, 40 | NoopInstructionInstructionData 41 | >; 42 | } 43 | 44 | // Args. 45 | export type NoopInstructionInstructionArgs = NoopInstructionInstructionDataArgs; 46 | 47 | // Instruction. 48 | export function noopInstruction( 49 | context: Pick, 50 | input: NoopInstructionInstructionArgs 51 | ): TransactionBuilder { 52 | // Program ID. 53 | const programId = context.programs.getPublicKey( 54 | 'splNoop', 55 | 'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV' 56 | ); 57 | 58 | // Accounts. 59 | const resolvedAccounts: ResolvedAccountsWithIndices = {}; 60 | 61 | // Arguments. 62 | const resolvedArgs: NoopInstructionInstructionArgs = { ...input }; 63 | 64 | // Accounts in order. 65 | const orderedAccounts: ResolvedAccount[] = Object.values( 66 | resolvedAccounts 67 | ).sort((a, b) => a.index - b.index); 68 | 69 | // Keys and Signers. 70 | const [keys, signers] = getAccountMetasAndSigners( 71 | orderedAccounts, 72 | 'programId', 73 | programId 74 | ); 75 | 76 | // Data. 77 | const data = getNoopInstructionInstructionDataSerializer().serialize( 78 | resolvedArgs as NoopInstructionInstructionDataArgs 79 | ); 80 | 81 | // Bytes Created On Chain. 82 | const bytesCreatedOnChain = 0; 83 | 84 | return transactionBuilder([ 85 | { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, 86 | ]); 87 | } 88 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './splAccountCompression'; 10 | export * from './splNoop'; 11 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/programs/splAccountCompression.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | ClusterFilter, 11 | Context, 12 | Program, 13 | PublicKey, 14 | } from '@metaplex-foundation/umi'; 15 | import { 16 | getSplAccountCompressionErrorFromCode, 17 | getSplAccountCompressionErrorFromName, 18 | } from '../errors'; 19 | 20 | export const SPL_ACCOUNT_COMPRESSION_PROGRAM_ID = 21 | 'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK' as PublicKey<'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'>; 22 | 23 | export function createSplAccountCompressionProgram(): Program { 24 | return { 25 | name: 'splAccountCompression', 26 | publicKey: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 27 | getErrorFromCode(code: number, cause?: Error) { 28 | return getSplAccountCompressionErrorFromCode(code, this, cause); 29 | }, 30 | getErrorFromName(name: string, cause?: Error) { 31 | return getSplAccountCompressionErrorFromName(name, this, cause); 32 | }, 33 | isOnCluster() { 34 | return true; 35 | }, 36 | }; 37 | } 38 | 39 | export function getSplAccountCompressionProgram( 40 | context: Pick, 41 | clusterFilter?: ClusterFilter 42 | ): T { 43 | return context.programs.get('splAccountCompression', clusterFilter); 44 | } 45 | 46 | export function getSplAccountCompressionProgramId( 47 | context: Pick, 48 | clusterFilter?: ClusterFilter 49 | ): PublicKey { 50 | return context.programs.getPublicKey( 51 | 'splAccountCompression', 52 | SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, 53 | clusterFilter 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/programs/splNoop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | ClusterFilter, 11 | Context, 12 | Program, 13 | PublicKey, 14 | } from '@metaplex-foundation/umi'; 15 | import { getSplNoopErrorFromCode, getSplNoopErrorFromName } from '../errors'; 16 | 17 | export const SPL_NOOP_PROGRAM_ID = 18 | 'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV' as PublicKey<'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'>; 19 | 20 | export function createSplNoopProgram(): Program { 21 | return { 22 | name: 'splNoop', 23 | publicKey: SPL_NOOP_PROGRAM_ID, 24 | getErrorFromCode(code: number, cause?: Error) { 25 | return getSplNoopErrorFromCode(code, this, cause); 26 | }, 27 | getErrorFromName(name: string, cause?: Error) { 28 | return getSplNoopErrorFromName(name, this, cause); 29 | }, 30 | isOnCluster() { 31 | return true; 32 | }, 33 | }; 34 | } 35 | 36 | export function getSplNoopProgram( 37 | context: Pick, 38 | clusterFilter?: ClusterFilter 39 | ): T { 40 | return context.programs.get('splNoop', clusterFilter); 41 | } 42 | 43 | export function getSplNoopProgramId( 44 | context: Pick, 45 | clusterFilter?: ClusterFilter 46 | ): PublicKey { 47 | return context.programs.getPublicKey( 48 | 'splNoop', 49 | SPL_NOOP_PROGRAM_ID, 50 | clusterFilter 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/applicationDataEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | GetDataEnumKind, 11 | GetDataEnumKindContent, 12 | Serializer, 13 | dataEnum, 14 | struct, 15 | tuple, 16 | } from '@metaplex-foundation/umi/serializers'; 17 | import { 18 | ApplicationDataEventV1, 19 | ApplicationDataEventV1Args, 20 | getApplicationDataEventV1Serializer, 21 | } from '.'; 22 | 23 | export type ApplicationDataEvent = { 24 | __kind: 'V1'; 25 | fields: [ApplicationDataEventV1]; 26 | }; 27 | 28 | export type ApplicationDataEventArgs = { 29 | __kind: 'V1'; 30 | fields: [ApplicationDataEventV1Args]; 31 | }; 32 | 33 | export function getApplicationDataEventSerializer(): Serializer< 34 | ApplicationDataEventArgs, 35 | ApplicationDataEvent 36 | > { 37 | return dataEnum( 38 | [ 39 | [ 40 | 'V1', 41 | struct>([ 42 | ['fields', tuple([getApplicationDataEventV1Serializer()])], 43 | ]), 44 | ], 45 | ], 46 | { description: 'ApplicationDataEvent' } 47 | ) as Serializer; 48 | } 49 | 50 | // Data Enum Helpers. 51 | export function applicationDataEvent( 52 | kind: 'V1', 53 | data: GetDataEnumKindContent['fields'] 54 | ): GetDataEnumKind; 55 | export function applicationDataEvent< 56 | K extends ApplicationDataEventArgs['__kind'] 57 | >(kind: K, data?: any): Extract { 58 | return Array.isArray(data) 59 | ? { __kind: kind, fields: data } 60 | : { __kind: kind, ...(data ?? {}) }; 61 | } 62 | export function isApplicationDataEvent< 63 | K extends ApplicationDataEvent['__kind'] 64 | >( 65 | kind: K, 66 | value: ApplicationDataEvent 67 | ): value is ApplicationDataEvent & { __kind: K } { 68 | return value.__kind === kind; 69 | } 70 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/applicationDataEventV1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Serializer, 11 | bytes, 12 | struct, 13 | u32, 14 | } from '@metaplex-foundation/umi/serializers'; 15 | 16 | export type ApplicationDataEventV1 = { applicationData: Uint8Array }; 17 | 18 | export type ApplicationDataEventV1Args = ApplicationDataEventV1; 19 | 20 | export function getApplicationDataEventV1Serializer(): Serializer< 21 | ApplicationDataEventV1Args, 22 | ApplicationDataEventV1 23 | > { 24 | return struct( 25 | [['applicationData', bytes({ size: u32() })]], 26 | { description: 'ApplicationDataEventV1' } 27 | ) as Serializer; 28 | } 29 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/changeLogEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | GetDataEnumKind, 11 | GetDataEnumKindContent, 12 | Serializer, 13 | dataEnum, 14 | struct, 15 | tuple, 16 | } from '@metaplex-foundation/umi/serializers'; 17 | import { 18 | ChangeLogEventV1, 19 | ChangeLogEventV1Args, 20 | getChangeLogEventV1Serializer, 21 | } from '.'; 22 | 23 | export type ChangeLogEvent = { __kind: 'V1'; fields: [ChangeLogEventV1] }; 24 | 25 | export type ChangeLogEventArgs = { 26 | __kind: 'V1'; 27 | fields: [ChangeLogEventV1Args]; 28 | }; 29 | 30 | export function getChangeLogEventSerializer(): Serializer< 31 | ChangeLogEventArgs, 32 | ChangeLogEvent 33 | > { 34 | return dataEnum( 35 | [ 36 | [ 37 | 'V1', 38 | struct>([ 39 | ['fields', tuple([getChangeLogEventV1Serializer()])], 40 | ]), 41 | ], 42 | ], 43 | { description: 'ChangeLogEvent' } 44 | ) as Serializer; 45 | } 46 | 47 | // Data Enum Helpers. 48 | export function changeLogEvent( 49 | kind: 'V1', 50 | data: GetDataEnumKindContent['fields'] 51 | ): GetDataEnumKind; 52 | export function changeLogEvent( 53 | kind: K, 54 | data?: any 55 | ): Extract { 56 | return Array.isArray(data) 57 | ? { __kind: kind, fields: data } 58 | : { __kind: kind, ...(data ?? {}) }; 59 | } 60 | export function isChangeLogEvent( 61 | kind: K, 62 | value: ChangeLogEvent 63 | ): value is ChangeLogEvent & { __kind: K } { 64 | return value.__kind === kind; 65 | } 66 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/changeLogEventV1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { PublicKey } from '@metaplex-foundation/umi'; 10 | import { 11 | Serializer, 12 | array, 13 | publicKey as publicKeySerializer, 14 | struct, 15 | u32, 16 | u64, 17 | } from '@metaplex-foundation/umi/serializers'; 18 | import { PathNode, PathNodeArgs, getPathNodeSerializer } from '.'; 19 | 20 | export type ChangeLogEventV1 = { 21 | /** Public key of the ConcurrentMerkleTree */ 22 | id: PublicKey; 23 | /** Nodes of off-chain merkle tree needed by indexer */ 24 | path: Array; 25 | /** 26 | * Index corresponding to the number of successful operations on this tree. 27 | * Used by the off-chain indexer to figure out when there are gaps to be backfilled. 28 | */ 29 | seq: bigint; 30 | /** Bitmap of node parity (used when hashing) */ 31 | index: number; 32 | }; 33 | 34 | export type ChangeLogEventV1Args = { 35 | /** Public key of the ConcurrentMerkleTree */ 36 | id: PublicKey; 37 | /** Nodes of off-chain merkle tree needed by indexer */ 38 | path: Array; 39 | /** 40 | * Index corresponding to the number of successful operations on this tree. 41 | * Used by the off-chain indexer to figure out when there are gaps to be backfilled. 42 | */ 43 | seq: number | bigint; 44 | /** Bitmap of node parity (used when hashing) */ 45 | index: number; 46 | }; 47 | 48 | export function getChangeLogEventV1Serializer(): Serializer< 49 | ChangeLogEventV1Args, 50 | ChangeLogEventV1 51 | > { 52 | return struct( 53 | [ 54 | ['id', publicKeySerializer()], 55 | ['path', array(getPathNodeSerializer())], 56 | ['seq', u64()], 57 | ['index', u32()], 58 | ], 59 | { description: 'ChangeLogEventV1' } 60 | ) as Serializer; 61 | } 62 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/compressionAccountType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export enum CompressionAccountType { 12 | Uninitialized, 13 | ConcurrentMerkleTree, 14 | } 15 | 16 | export type CompressionAccountTypeArgs = CompressionAccountType; 17 | 18 | export function getCompressionAccountTypeSerializer(): Serializer< 19 | CompressionAccountTypeArgs, 20 | CompressionAccountType 21 | > { 22 | return scalarEnum(CompressionAccountType, { 23 | description: 'CompressionAccountType', 24 | }) as Serializer; 25 | } 26 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/concurrentMerkleTreeHeader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; 10 | import { 11 | CompressionAccountType, 12 | CompressionAccountTypeArgs, 13 | ConcurrentMerkleTreeHeaderData, 14 | ConcurrentMerkleTreeHeaderDataArgs, 15 | getCompressionAccountTypeSerializer, 16 | getConcurrentMerkleTreeHeaderDataSerializer, 17 | } from '.'; 18 | 19 | /** 20 | * Initialization parameters for an SPL ConcurrentMerkleTree. 21 | * 22 | * Only the following permutations are valid: 23 | * 24 | * | max_depth | max_buffer_size | 25 | * | --------- | --------------------- | 26 | * | 14 | (64, 256, 1024, 2048) | 27 | * | 20 | (64, 256, 1024, 2048) | 28 | * | 24 | (64, 256, 512, 1024, 2048) | 29 | * | 26 | (64, 256, 512, 1024, 2048) | 30 | * | 30 | (512, 1024, 2048) | 31 | * 32 | */ 33 | 34 | export type ConcurrentMerkleTreeHeader = { 35 | /** Account type */ 36 | accountType: CompressionAccountType; 37 | /** Versioned header */ 38 | header: ConcurrentMerkleTreeHeaderData; 39 | }; 40 | 41 | export type ConcurrentMerkleTreeHeaderArgs = { 42 | /** Account type */ 43 | accountType: CompressionAccountTypeArgs; 44 | /** Versioned header */ 45 | header: ConcurrentMerkleTreeHeaderDataArgs; 46 | }; 47 | 48 | export function getConcurrentMerkleTreeHeaderSerializer(): Serializer< 49 | ConcurrentMerkleTreeHeaderArgs, 50 | ConcurrentMerkleTreeHeader 51 | > { 52 | return struct( 53 | [ 54 | ['accountType', getCompressionAccountTypeSerializer()], 55 | ['header', getConcurrentMerkleTreeHeaderDataSerializer()], 56 | ], 57 | { description: 'ConcurrentMerkleTreeHeader' } 58 | ) as Serializer; 59 | } 60 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | export * from './accountCompressionEvent'; 10 | export * from './applicationDataEvent'; 11 | export * from './applicationDataEventV1'; 12 | export * from './changeLogEvent'; 13 | export * from './changeLogEventV1'; 14 | export * from './compressionAccountType'; 15 | export * from './concurrentMerkleTreeHeader'; 16 | export * from './concurrentMerkleTreeHeaderData'; 17 | export * from './pathNode'; 18 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/generated/types/pathNode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the kinobi library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun kinobi to update it. 5 | * 6 | * @see https://github.com/metaplex-foundation/kinobi 7 | */ 8 | 9 | import { 10 | Serializer, 11 | bytes, 12 | struct, 13 | u32, 14 | } from '@metaplex-foundation/umi/serializers'; 15 | 16 | export type PathNode = { node: Uint8Array; index: number }; 17 | 18 | export type PathNodeArgs = PathNode; 19 | 20 | export function getPathNodeSerializer(): Serializer { 21 | return struct( 22 | [ 23 | ['node', bytes({ size: 32 })], 24 | ['index', u32()], 25 | ], 26 | { description: 'PathNode' } 27 | ) as Serializer; 28 | } 29 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/hooked/changeLog.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@metaplex-foundation/umi'; 2 | import { 3 | array, 4 | fixSerializer, 5 | publicKey, 6 | struct, 7 | u32, 8 | } from '@metaplex-foundation/umi/serializers'; 9 | 10 | export type ChangeLog = { 11 | root: PublicKey; 12 | pathNodes: PublicKey[]; 13 | index: number; 14 | }; 15 | 16 | export type ChangeLogArgs = ChangeLog; 17 | 18 | export const getChangeLogSerializer = (maxDepth: number) => 19 | struct([ 20 | ['root', publicKey()], 21 | ['pathNodes', array(publicKey(), { size: maxDepth })], 22 | ['index', fixSerializer(u32(), 8)], 23 | ]); 24 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/hooked/concurrentMerkleTree.ts: -------------------------------------------------------------------------------- 1 | import { publicKeyBytes } from '@metaplex-foundation/umi'; 2 | import { array, struct, u64 } from '@metaplex-foundation/umi/serializers'; 3 | import { Path, PathArgs, getPathSerializer } from './path'; 4 | import { ChangeLog, ChangeLogArgs, getChangeLogSerializer } from './changeLog'; 5 | 6 | export type ConcurrentMerkleTree = { 7 | sequenceNumber: bigint; 8 | activeIndex: bigint; 9 | bufferSize: bigint; 10 | changeLogs: ChangeLog[]; 11 | rightMostPath: Path; 12 | }; 13 | 14 | export type ConcurrentMerkleTreeArgs = { 15 | sequenceNumber: bigint | number; 16 | activeIndex: bigint | number; 17 | bufferSize: bigint | number; 18 | changeLogs: ChangeLogArgs[]; 19 | rightMostPath: PathArgs; 20 | }; 21 | 22 | export const getConcurrentMerkleTreeSerializer = ( 23 | maxDepth: number, 24 | maxBufferSize: number 25 | ) => 26 | struct([ 27 | ['sequenceNumber', u64()], 28 | ['activeIndex', u64()], 29 | ['bufferSize', u64()], 30 | [ 31 | 'changeLogs', 32 | array(getChangeLogSerializer(maxDepth), { size: maxBufferSize }), 33 | ], 34 | ['rightMostPath', getPathSerializer(maxDepth)], 35 | ]); 36 | 37 | export const getCurrentRoot = ( 38 | tree: Pick 39 | ): Uint8Array => publicKeyBytes(tree.changeLogs[Number(tree.activeIndex)].root); 40 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/hooked/index.ts: -------------------------------------------------------------------------------- 1 | export * from './merkleTreeAccountData'; 2 | export * from './path'; 3 | export * from './concurrentMerkleTree'; 4 | export * from './changeLog'; 5 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/hooked/path.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@metaplex-foundation/umi'; 2 | import { 3 | Serializer, 4 | array, 5 | mapSerializer, 6 | publicKey, 7 | struct, 8 | u32, 9 | } from '@metaplex-foundation/umi/serializers'; 10 | 11 | export type Path = { 12 | proof: PublicKey[]; 13 | leaf: PublicKey; 14 | index: number; 15 | }; 16 | 17 | export type PathArgs = Path; 18 | 19 | export function getPathSerializer( 20 | maxDepth: number 21 | ): Serializer { 22 | return mapSerializer( 23 | struct( 24 | [ 25 | ['proof', array(publicKey(), { size: maxDepth })], 26 | ['leaf', publicKey()], 27 | ['index', u32()], 28 | ['padding', u32()], 29 | ], 30 | { description: 'Path' } 31 | ), 32 | (path) => ({ ...path, padding: 0 }), 33 | (pathWithPadding) => { 34 | const { padding, ...path } = pathWithPadding; 35 | return path; 36 | } 37 | ) as Serializer; 38 | } 39 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated'; 2 | export * from './plugin'; 3 | export * from './hooked'; 4 | -------------------------------------------------------------------------------- /clients/spl-ac-js/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { UmiPlugin } from '@metaplex-foundation/umi'; 2 | import { 3 | createSplAccountCompressionProgram, 4 | createSplNoopProgram, 5 | } from './generated'; 6 | 7 | export const splAccountCompression = (): UmiPlugin => ({ 8 | install(umi) { 9 | umi.programs.add(createSplAccountCompressionProgram(), false); 10 | umi.programs.add(createSplNoopProgram(), false); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /clients/spl-ac-js/test/_setup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { SolAmount, TransactionWithMeta } from '@metaplex-foundation/umi'; 3 | import { createUmi as baseCreateUmi } from '@metaplex-foundation/umi-bundle-tests'; 4 | import { splAccountCompression } from '../src'; 5 | 6 | export const createUmi = async (endpoint?: string, airdropAmount?: SolAmount) => 7 | (await baseCreateUmi(endpoint, undefined, airdropAmount)).use( 8 | splAccountCompression() 9 | ); 10 | 11 | // TransactionWithMeta doesn't have ReturnData field that is described in 12 | // https://solana.com/docs/rpc/http/gettransaction#result 13 | // so ugly log parsing is provided 14 | export function getReturnLog( 15 | transaction: TransactionWithMeta | null 16 | ): null | [string, string, Buffer] { 17 | if (transaction === null) { 18 | return null; 19 | } 20 | const prefix = 'Program return: '; 21 | let log = transaction.meta.logs.find((logs) => logs.startsWith(prefix)); 22 | if (log === undefined) { 23 | return null; 24 | } 25 | log = log.slice(prefix.length); 26 | const [key, data] = log.split(' ', 2); 27 | const buffer = Buffer.from(data, 'base64'); 28 | return [key, data, buffer]; 29 | } 30 | -------------------------------------------------------------------------------- /clients/spl-ac-js/test/getProgram.test.ts: -------------------------------------------------------------------------------- 1 | import { samePublicKey } from '@metaplex-foundation/umi'; 2 | import test from 'ava'; 3 | import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID } from '../src'; 4 | import { createUmi } from './_setup'; 5 | 6 | test('it registers the program', async (t) => { 7 | // Given a Umi instance using the project's plugin. 8 | const umi = await createUmi(); 9 | 10 | // When we fetch the registered program. 11 | const program = umi.programs.get('splAccountCompression'); 12 | 13 | // Then we expect it to be the same as the program ID constant. 14 | t.true(samePublicKey(program.publicKey, SPL_ACCOUNT_COMPRESSION_PROGRAM_ID)); 15 | }); 16 | -------------------------------------------------------------------------------- /clients/spl-ac-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "baseUrl": ".", 5 | "rootDir": ".", 6 | "target": "ES2020", 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": false, 11 | "removeComments": false, 12 | "moduleResolution": "node", 13 | "noEmit": false, 14 | "preserveWatchOutput": true, 15 | "emitDeclarationOnly": false, 16 | "importHelpers": false, 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "downlevelIteration": true, 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true, 23 | "incremental": false, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "useUnknownInCatchVariables": false 27 | }, 28 | "include": ["src", "test"], 29 | "exclude": ["node_modules", "dist", "build", "lib"] 30 | } 31 | -------------------------------------------------------------------------------- /clients/spl-ac-js/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "includeVersion": true, 4 | "readme": "none", 5 | "out": "docs" 6 | } 7 | -------------------------------------------------------------------------------- /configs/scripts/client/test-js.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | # go to parent folder 5 | cd $(dirname $(dirname $(dirname $SCRIPT_DIR))) 6 | WORKING_DIR=$(pwd) 7 | 8 | # command-line input 9 | ARGS=$* 10 | 11 | # js client tests folder 12 | cd ${WORKING_DIR}/clients/js 13 | 14 | pnpm install && pnpm build && pnpm test ${ARGS} 15 | -------------------------------------------------------------------------------- /configs/scripts/client/test-rust.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | PROGRAMS_OUTPUT="./programs/.bin" 5 | # go to parent folder 6 | cd $(dirname $(dirname $(dirname $SCRIPT_DIR))) 7 | 8 | # command-line input 9 | ARGS=$* 10 | 11 | WORKING_DIR=$(pwd) 12 | SOLFMT="solfmt" 13 | export SBF_OUT_DIR="${WORKING_DIR}/${PROGRAMS_OUTPUT}" 14 | 15 | # client SDK tests 16 | cd ${WORKING_DIR}/clients/rust 17 | 18 | if [ ! "$(command -v $SOLFMT)" = "" ]; then 19 | CARGO_TERM_COLOR=always cargo test-sbf --sbf-out-dir ${WORKING_DIR}/${PROGRAMS_OUTPUT} ${ARGS} 2>&1 | ${SOLFMT} 20 | else 21 | cargo test-sbf --sbf-out-dir ${WORKING_DIR}/${PROGRAMS_OUTPUT} ${ARGS} 22 | fi 23 | -------------------------------------------------------------------------------- /configs/scripts/program/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | OUTPUT="./programs/.bin" 5 | # saves external programs binaries to the output directory 6 | source ${SCRIPT_DIR}/dump.sh ${OUTPUT} 7 | 8 | # Dump programs currently on devnet only 9 | source ${SCRIPT_DIR}/dump_devnet.sh ${OUTPUT} 10 | 11 | # go to parent folder 12 | cd $(dirname $(dirname $(dirname ${SCRIPT_DIR}))) 13 | 14 | if [ -z ${PROGRAMS+x} ]; then 15 | PROGRAMS="$(cat .github/.env | grep "PROGRAMS" | cut -d '=' -f 2)" 16 | fi 17 | 18 | # default to input from the command-line 19 | ARGS=$* 20 | 21 | # command-line arguments override env variable 22 | if [ ! -z "$ARGS" ]; then 23 | PROGRAMS="[\"${1}\"]" 24 | shift 25 | ARGS=$* 26 | fi 27 | 28 | PROGRAMS=$(echo ${PROGRAMS} | jq -c '.[]' | sed 's/"//g') 29 | 30 | # creates the output directory if it doesn't exist 31 | if [ ! -d ${OUTPUT} ]; then 32 | mkdir ${OUTPUT} 33 | fi 34 | 35 | WORKING_DIR=$(pwd) 36 | export SBF_OUT_DIR="${WORKING_DIR}/${OUTPUT}" 37 | 38 | for p in ${PROGRAMS[@]}; do 39 | cd ${WORKING_DIR}/programs/${p}/program 40 | cargo build-sbf --sbf-out-dir ${WORKING_DIR}/${OUTPUT} $ARGS 41 | done 42 | -------------------------------------------------------------------------------- /configs/scripts/program/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | OUTPUT="./programs/.bin" 5 | # go to parent folder 6 | cd $(dirname $(dirname $(dirname ${SCRIPT_DIR}))) 7 | 8 | rm -rf $OUTPUT 9 | 10 | if [ -z ${PROGRAMS+x} ]; then 11 | PROGRAMS="$(cat .github/.env | grep "PROGRAMS" | cut -d '=' -f 2)" 12 | fi 13 | 14 | PROGRAMS=$(echo ${PROGRAMS} | jq -c '.[]' | sed 's/"//g') 15 | WORKING_DIR=$(pwd) 16 | 17 | for p in ${PROGRAMS[@]}; do 18 | cd ${WORKING_DIR}/programs/${p} 19 | rm -rf target 20 | done 21 | -------------------------------------------------------------------------------- /configs/scripts/program/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 4 | OUTPUT="./programs/.bin" 5 | # saves external programs binaries to the output directory 6 | source ${SCRIPT_DIR}/dump.sh ${OUTPUT} 7 | # go to parent folder 8 | cd $(dirname $(dirname $(dirname $SCRIPT_DIR))) 9 | 10 | if [ ! -z "$PROGRAM" ]; then 11 | PROGRAMS='["'${PROGRAM}'"]' 12 | fi 13 | 14 | if [ -z "$PROGRAMS" ]; then 15 | PROGRAMS="$(cat .github/.env | grep "PROGRAMS" | cut -d '=' -f 2)" 16 | fi 17 | 18 | # default to input from the command-line 19 | ARGS=$* 20 | 21 | # command-line arguments override env variable 22 | if [ ! -z "$ARGS" ]; then 23 | PROGRAMS="[\"${1}\"]" 24 | shift 25 | ARGS=$* 26 | fi 27 | 28 | PROGRAMS=$(echo $PROGRAMS | jq -c '.[]' | sed 's/"//g') 29 | 30 | WORKING_DIR=$(pwd) 31 | SOLFMT="solfmt" 32 | export SBF_OUT_DIR="${WORKING_DIR}/${OUTPUT}" 33 | 34 | for p in ${PROGRAMS[@]}; do 35 | cd ${WORKING_DIR}/programs/${p} 36 | 37 | if [ ! "$(command -v $SOLFMT)" = "" ]; then 38 | CARGO_TERM_COLOR=always cargo test-sbf --sbf-out-dir ${WORKING_DIR}/${OUTPUT} ${ARGS} 2>&1 | ${SOLFMT} 39 | else 40 | RUST_LOG=error cargo test-sbf --sbf-out-dir ${WORKING_DIR}/${OUTPUT} ${ARGS} 41 | fi 42 | done 43 | -------------------------------------------------------------------------------- /configs/shank.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { generateIdl } = require("@metaplex-foundation/shank-js"); 3 | 4 | const idlDir = path.join(__dirname, "..", "idls"); 5 | const binaryInstallDir = path.join(__dirname, "..", ".crates"); 6 | const programDir = path.join(__dirname, "..", "programs"); 7 | 8 | generateIdl({ 9 | generator: "anchor", 10 | programName: "bubblegum", 11 | programId: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", 12 | idlDir, 13 | binaryInstallDir, 14 | programDir: path.join(programDir, "bubblegum", "program"), 15 | rustbin: { 16 | locked: true, 17 | versionRangeFallback: "0.27.0", 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /configs/validator.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const programDir = path.join(__dirname, "..", "programs"); 4 | 5 | function getProgram(programName) { 6 | return path.join(programDir, ".bin", programName); 7 | } 8 | 9 | module.exports = { 10 | validator: { 11 | commitment: "processed", 12 | programs: [ 13 | { 14 | label: "MPL Account Compression", 15 | programId: "mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW", 16 | deployPath: getProgram("mpl_account_compression.so"), 17 | }, 18 | { 19 | label: "Mpl Bubblegum", 20 | programId: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY", 21 | deployPath: getProgram("bubblegum.so"), 22 | }, 23 | { 24 | label: "MPL Noop", 25 | programId: "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", 26 | deployPath: getProgram("mpl_noop.so"), 27 | }, 28 | { 29 | label: "Token Metadata", 30 | programId: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", 31 | deployPath: getProgram("mpl_token_metadata.so"), 32 | }, 33 | { 34 | label: "SPL Account Compression", 35 | programId: "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK", 36 | deployPath: getProgram("spl_account_compression.so"), 37 | }, 38 | { 39 | label: "SPL Noop", 40 | programId: "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", 41 | deployPath: getProgram("spl_noop.so"), 42 | }, 43 | { 44 | label: "MPL Core", 45 | programId: "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d", 46 | deployPath: getProgram("mpl_core_program.so"), 47 | }, 48 | ], 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /idls/mpl_noop.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1", 3 | "name": "mpl_noop", 4 | "instructions": [ 5 | { 6 | "name": "noopInstruction", 7 | "args": [ 8 | { 9 | "name": "data", 10 | "type": "bytes" 11 | } 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "address": "mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3", 17 | "origin": "shank" 18 | } 19 | } -------------------------------------------------------------------------------- /idls/spl_noop.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.3", 3 | "name": "spl_noop", 4 | "instructions": [ 5 | { 6 | "name": "noopInstruction", 7 | "args": [ 8 | { 9 | "name": "data", 10 | "type": "bytes" 11 | } 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "address": "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", 17 | "origin": "shank" 18 | } 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "programs:build": "./configs/scripts/program/build.sh", 5 | "programs:test": "RUST_LOG=error ./configs/scripts/program/test.sh", 6 | "programs:debug": "./configs/scripts/program/test.sh", 7 | "programs:clean": "./configs/scripts/program/clean.sh", 8 | "clients:rust:test": "./configs/scripts/client/test-rust.sh", 9 | "clients:js:test": "./configs/scripts/client/test-js.sh", 10 | "generate": "pnpm generate:idls && pnpm generate:clients", 11 | "generate:idls": "node ./configs/shank.cjs", 12 | "generate:clients": "node ./configs/kinobi.cjs && node ./configs/spl-ac-kinobi.cjs && node ./configs/mpl-ac-kinobi.cjs", 13 | "validator": "CI=1 amman start --config ./configs/validator.cjs", 14 | "validator:debug": "amman start --config ./configs/validator.cjs", 15 | "validator:logs": "CI=1 amman logs", 16 | "validator:stop": "amman stop" 17 | }, 18 | "devDependencies": { 19 | "@metaplex-foundation/amman": "^0.12.1", 20 | "@metaplex-foundation/kinobi": "^0.16.0", 21 | "@metaplex-foundation/shank-js": "^0.1.5", 22 | "typescript": "^4.9.4" 23 | }, 24 | "packageManager": "pnpm@8.15.9" 25 | } -------------------------------------------------------------------------------- /programs/bubblegum/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | 4 | [registry] 5 | url = "https://anchor.projectserum.com" 6 | 7 | [provider] 8 | cluster = "localnet" 9 | wallet = "~/.config/solana/id.json" 10 | 11 | [workspace] 12 | members = [ 13 | "program", 14 | ] 15 | -------------------------------------------------------------------------------- /programs/bubblegum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.release] 2 | overflow-checks = true 3 | 4 | [workspace] 5 | members = [ 6 | "program" 7 | ] 8 | resolver = "2" 9 | -------------------------------------------------------------------------------- /programs/bubblegum/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bubblegum" 3 | version = "0.12.0" 4 | description = "NFT Compression" 5 | authors = ["Metaplex Developers "] 6 | repository = "https://github.com/metaplex-foundation/mpl-bubblegum" 7 | license-file = "../../../LICENSE" 8 | edition = "2021" 9 | readme = "../README.md" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [features] 15 | no-entrypoint = [] 16 | no-idl = [] 17 | no-log-ix-name = [] 18 | cpi = ["no-entrypoint"] 19 | test-sbf = [] 20 | default = [] 21 | 22 | [dependencies] 23 | anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } 24 | anchor-spl = "0.29.0" 25 | bytemuck = "1.13.0" 26 | modular-bitfield = "0.11.2" 27 | mpl-account-compression = { version = "0.4.2", features = ["cpi"] } 28 | mpl-core = "0.10.0-beta.1" 29 | mpl-noop = { version = "0.2.1", features = ["no-entrypoint"] } 30 | mpl-token-metadata = "4.1.2" 31 | num-traits = "0.2.15" 32 | solana-program = "~1.18.15" 33 | spl-account-compression = { version = "0.4.2", features = ["cpi"] } 34 | spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } 35 | spl-noop = { version = "0.2.0", features = ["no-entrypoint"] } 36 | spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } 37 | 38 | [dev-dependencies] 39 | async-trait = "0.1.71" 40 | mpl-token-auth-rules = { version = "1.5.1", features = ["no-entrypoint"] } 41 | solana-program-test = "~1.18.15" 42 | solana-sdk = "~1.18.15" 43 | spl-concurrent-merkle-tree = "0.4.1" 44 | spl-merkle-tree-reference = "0.1.1" 45 | -------------------------------------------------------------------------------- /programs/bubblegum/program/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/collect.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | error::BubblegumError, 5 | state::{collect::COLLECT_RECIPIENT, leaf_schema::Version, TreeConfig, TREE_AUTHORITY_SIZE}, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | pub struct CollectV2<'info> { 10 | #[account(mut)] 11 | pub tree_authority: Account<'info, TreeConfig>, 12 | /// CHECK: Hardcoded recipient with no data 13 | #[account(mut, address = COLLECT_RECIPIENT)] 14 | destination: UncheckedAccount<'info>, 15 | } 16 | 17 | pub(crate) fn collect_v2<'info>(ctx: Context<'_, '_, '_, 'info, CollectV2<'info>>) -> Result<()> { 18 | // V2 instructions only work with V2 trees. 19 | require!( 20 | ctx.accounts.tree_authority.version == Version::V2, 21 | BubblegumError::UnsupportedSchemaVersion 22 | ); 23 | 24 | let rent_amount = Rent::get()?.minimum_balance(TREE_AUTHORITY_SIZE); 25 | let source = ctx.accounts.tree_authority.to_account_info(); 26 | let destination = ctx.accounts.destination.to_account_info(); 27 | 28 | let fee_amount = source 29 | .lamports() 30 | .checked_sub(rent_amount) 31 | .ok_or(BubblegumError::NumericalOverflowError)?; 32 | 33 | **destination.try_borrow_mut_lamports()? = destination 34 | .lamports() 35 | .checked_add(fee_amount) 36 | .ok_or(BubblegumError::NumericalOverflowError)?; 37 | 38 | **source.try_borrow_mut_lamports()? = rent_amount; 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/compress.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use spl_account_compression::{program::SplAccountCompression, Noop as SplNoop}; 3 | 4 | use crate::state::metaplex_anchor::{MasterEdition, TokenMetadata}; 5 | 6 | #[derive(Accounts)] 7 | pub struct Compress<'info> { 8 | #[account( 9 | seeds = [merkle_tree.key().as_ref()], 10 | bump, 11 | )] 12 | /// CHECK: This account is neither written to nor read from. 13 | pub tree_authority: UncheckedAccount<'info>, 14 | /// CHECK: This account is checked in the instruction 15 | pub leaf_owner: Signer<'info>, 16 | /// CHECK: This account is checked in the instruction 17 | pub leaf_delegate: UncheckedAccount<'info>, 18 | /// CHECK: This account is not read 19 | pub merkle_tree: UncheckedAccount<'info>, 20 | /// CHECK: versioning is handled in the instruction 21 | #[account(mut)] 22 | pub token_account: AccountInfo<'info>, 23 | /// CHECK: versioning is handled in the instruction 24 | #[account(mut)] 25 | pub mint: AccountInfo<'info>, 26 | #[account(mut)] 27 | pub metadata: Box>, 28 | #[account(mut)] 29 | pub master_edition: Box>, 30 | #[account(mut)] 31 | pub payer: Signer<'info>, 32 | pub log_wrapper: Program<'info, SplNoop>, 33 | pub compression_program: Program<'info, SplAccountCompression>, 34 | /// CHECK: 35 | pub token_program: UncheckedAccount<'info>, 36 | /// CHECK: 37 | pub token_metadata_program: UncheckedAccount<'info>, 38 | pub system_program: Program<'info, System>, 39 | } 40 | 41 | pub(crate) fn compress(_ctx: Context) -> Result<()> { 42 | // TODO 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/set_and_verify_collection.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | error::BubblegumError, 5 | processor::{process_collection_verification, verify_collection::CollectionVerification}, 6 | state::metaplex_adapter::MetadataArgs, 7 | }; 8 | 9 | pub(crate) fn set_and_verify_collection<'info>( 10 | ctx: Context<'_, '_, '_, 'info, CollectionVerification<'info>>, 11 | root: [u8; 32], 12 | data_hash: [u8; 32], 13 | creator_hash: [u8; 32], 14 | nonce: u64, 15 | index: u32, 16 | message: MetadataArgs, 17 | collection: Pubkey, 18 | ) -> Result<()> { 19 | let incoming_tree_delegate = &ctx.accounts.tree_delegate; 20 | let tree_creator = ctx.accounts.tree_authority.tree_creator; 21 | let tree_delegate = ctx.accounts.tree_authority.tree_delegate; 22 | let collection_metadata = &ctx.accounts.collection_metadata; 23 | 24 | // Require that either the tree authority signed this transaction, or the tree authority is 25 | // the collection update authority which means the leaf update is approved via proxy, when 26 | // we later call `assert_has_collection_authority()`. 27 | // 28 | // This is similar to logic in token-metadata for `set_and_verify_collection()` except 29 | // this logic also allows the tree authority (which we are treating as the leaf metadata 30 | // authority) to be different than the collection authority (actual or delegated). The 31 | // token-metadata program required them to be the same. 32 | let tree_authority_signed = incoming_tree_delegate.is_signer 33 | && (incoming_tree_delegate.key() == tree_creator 34 | || incoming_tree_delegate.key() == tree_delegate); 35 | 36 | let tree_authority_is_collection_update_authority = collection_metadata.update_authority 37 | == tree_creator 38 | || collection_metadata.update_authority == tree_delegate; 39 | 40 | require!( 41 | tree_authority_signed || tree_authority_is_collection_update_authority, 42 | BubblegumError::UpdateAuthorityIncorrect 43 | ); 44 | 45 | process_collection_verification( 46 | ctx, 47 | root, 48 | data_hash, 49 | creator_hash, 50 | nonce, 51 | index, 52 | message, 53 | true, 54 | Some(collection), 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/set_decompressible_state.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | processor::BubblegumError, 5 | state::{leaf_schema::Version, DecompressibleState, TreeConfig}, 6 | }; 7 | 8 | #[derive(Accounts)] 9 | pub struct SetDecompressibleState<'info> { 10 | #[account(mut, has_one = tree_creator)] 11 | pub tree_authority: Account<'info, TreeConfig>, 12 | pub tree_creator: Signer<'info>, 13 | } 14 | 15 | pub(crate) fn set_decompressible_state( 16 | ctx: Context, 17 | decompressable_state: DecompressibleState, 18 | ) -> Result<()> { 19 | // V1 instructions only work with V1 trees. 20 | require!( 21 | ctx.accounts.tree_authority.version == Version::V1, 22 | BubblegumError::UnsupportedSchemaVersion 23 | ); 24 | 25 | ctx.accounts.tree_authority.is_decompressible = decompressable_state; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/set_tree_delegate.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::state::TreeConfig; 4 | 5 | #[derive(Accounts)] 6 | pub struct SetTreeDelegate<'info> { 7 | #[account( 8 | mut, 9 | seeds = [merkle_tree.key().as_ref()], 10 | bump, 11 | has_one = tree_creator 12 | )] 13 | pub tree_authority: Account<'info, TreeConfig>, 14 | pub tree_creator: Signer<'info>, 15 | /// CHECK: this account is neither read from or written to 16 | pub new_tree_delegate: UncheckedAccount<'info>, 17 | /// CHECK: Used to derive `tree_authority` 18 | pub merkle_tree: UncheckedAccount<'info>, 19 | pub system_program: Program<'info, System>, 20 | } 21 | 22 | pub(crate) fn set_tree_delegate(ctx: Context) -> Result<()> { 23 | ctx.accounts.tree_authority.tree_delegate = ctx.accounts.new_tree_delegate.key(); 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/thaw.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::processor::{freeze::FreezeV2, process_freeze}; 4 | 5 | pub(crate) fn thaw_v2<'info>( 6 | ctx: Context<'_, '_, '_, 'info, FreezeV2<'info>>, 7 | root: [u8; 32], 8 | data_hash: [u8; 32], 9 | creator_hash: [u8; 32], 10 | asset_data_hash: Option<[u8; 32]>, 11 | flags: Option, 12 | nonce: u64, 13 | index: u32, 14 | ) -> Result<()> { 15 | process_freeze( 16 | ctx, 17 | root, 18 | data_hash, 19 | creator_hash, 20 | asset_data_hash, 21 | flags, 22 | nonce, 23 | index, 24 | false, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/processor/unverify_collection.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | processor::{process_collection_verification, verify_collection::CollectionVerification}, 5 | state::metaplex_adapter::MetadataArgs, 6 | }; 7 | 8 | pub(crate) fn unverify_collection<'info>( 9 | ctx: Context<'_, '_, '_, 'info, CollectionVerification<'info>>, 10 | root: [u8; 32], 11 | data_hash: [u8; 32], 12 | creator_hash: [u8; 32], 13 | nonce: u64, 14 | index: u32, 15 | message: MetadataArgs, 16 | ) -> Result<()> { 17 | process_collection_verification( 18 | ctx, 19 | root, 20 | data_hash, 21 | creator_hash, 22 | nonce, 23 | index, 24 | message, 25 | false, 26 | None, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/state/collect.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | pub(crate) const COLLECT_RECIPIENT: Pubkey = 4 | solana_program::pubkey!("2dgJVPC5fjLTBTmMvKDRig9JJUGK2Fgwr3EHShFxckhv"); 5 | 6 | pub(crate) const MINT_V2_FEE_LAMPORTS: u64 = 90000; 7 | pub(crate) const TRANSFER_V2_FEE_LAMPORTS: u64 = 6000; 8 | -------------------------------------------------------------------------------- /programs/bubblegum/program/src/state/metaplex_anchor.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, solana_program::pubkey::Pubkey}; 2 | use std::ops::Deref; 3 | 4 | #[derive(Clone, AnchorDeserialize, AnchorSerialize)] 5 | pub struct MasterEdition(mpl_token_metadata::accounts::MasterEdition); 6 | 7 | impl anchor_lang::AccountDeserialize for MasterEdition { 8 | fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { 9 | mpl_token_metadata::accounts::MasterEdition::safe_deserialize(buf) 10 | .map(MasterEdition) 11 | .map_err(Into::into) 12 | } 13 | } 14 | 15 | impl anchor_lang::AccountSerialize for MasterEdition {} 16 | 17 | impl anchor_lang::Owner for MasterEdition { 18 | fn owner() -> Pubkey { 19 | mpl_token_metadata::ID 20 | } 21 | } 22 | 23 | impl Deref for MasterEdition { 24 | type Target = mpl_token_metadata::accounts::MasterEdition; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | &self.0 28 | } 29 | } 30 | 31 | #[derive(Clone, AnchorDeserialize, AnchorSerialize)] 32 | pub struct TokenMetadata(mpl_token_metadata::accounts::Metadata); 33 | 34 | impl anchor_lang::AccountDeserialize for TokenMetadata { 35 | fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { 36 | mpl_token_metadata::accounts::Metadata::safe_deserialize(buf) 37 | .map(TokenMetadata) 38 | .map_err(Into::into) 39 | } 40 | } 41 | 42 | impl anchor_lang::AccountSerialize for TokenMetadata {} 43 | 44 | impl anchor_lang::Owner for TokenMetadata { 45 | fn owner() -> Pubkey { 46 | mpl_token_metadata::ID 47 | } 48 | } 49 | 50 | impl Deref for TokenMetadata { 51 | type Target = mpl_token_metadata::accounts::Metadata; 52 | 53 | fn deref(&self) -> &Self::Target { 54 | &self.0 55 | } 56 | } 57 | 58 | #[derive(Clone)] 59 | pub struct MplTokenMetadata; 60 | 61 | impl anchor_lang::Id for MplTokenMetadata { 62 | fn id() -> Pubkey { 63 | mpl_token_metadata::ID 64 | } 65 | } 66 | 67 | #[derive(Clone)] 68 | pub struct MplCore; 69 | 70 | impl anchor_lang::Id for MplCore { 71 | fn id() -> Pubkey { 72 | mpl_core::ID 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /programs/bubblegum/rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | max_width = 100 3 | imports_indent = "Block" 4 | imports_layout = "Mixed" 5 | imports_granularity = "Crate" 6 | group_imports = "Preserve" 7 | reorder_imports = true 8 | reorder_modules = true 9 | reorder_impl_items = false 10 | --------------------------------------------------------------------------------