├── .ammanrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── actions │ ├── install-rust │ │ └── action.yml │ └── install-solana │ │ └── action.yml ├── labeler.yml └── workflows │ ├── build-lint.yml │ ├── labeler.yml │ ├── pull_request.yml │ ├── release.yml │ └── test-integration.yml ├── .gitignore ├── .husky └── commit-msg ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .yarn └── releases │ └── yarn-3.1.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── commitlint.config.js ├── examples ├── iife │ ├── index.html │ ├── index.js │ └── package.json ├── node │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── yarn.lock └── web │ ├── index.html │ ├── index.ts │ └── package.json ├── jest.config.ts ├── package.json ├── release.config.js ├── rollup.config.js ├── sh └── publish-docs ├── src ├── Connection.ts ├── actions │ ├── addTokensToVault.ts │ ├── burnToken.ts │ ├── cancelBid.ts │ ├── claimBid.ts │ ├── createMasterEdition.ts │ ├── createMetadata.ts │ ├── index.ts │ ├── initStore.ts │ ├── initStoreV2.ts │ ├── instantSale.ts │ ├── mintEditionFromMaster.ts │ ├── mintNFT.ts │ ├── placeBid.ts │ ├── redeemFullRightsTransferBid.ts │ ├── redeemParticipationBidV3.ts │ ├── redeemPrintingV2Bid.ts │ ├── sendToken.ts │ ├── shared │ │ ├── approve.ts │ │ ├── index.ts │ │ ├── mint.ts │ │ └── wrapped-account.ts │ ├── signMetadata.ts │ ├── transactions.ts │ ├── updateMetadata.ts │ └── utility │ │ ├── closeVault.ts │ │ ├── createExternalPriceAccount.ts │ │ ├── createVault.ts │ │ ├── index.ts │ │ └── initAuction.ts ├── index.ts ├── programs │ └── index.ts ├── providers │ ├── conversion │ │ ├── Coingecko.ts │ │ ├── ConversionRateProvider.ts │ │ └── index.ts │ ├── index.ts │ └── storage │ │ ├── Storage.ts │ │ ├── arweave │ │ ├── ArweaveStorage.ts │ │ └── index.ts │ │ └── index.ts ├── transactions │ ├── CreateAssociatedTokenAccount.ts │ ├── CreateMint.ts │ ├── CreateTokenAccount.ts │ ├── MintTo.ts │ ├── PayForFiles.ts │ └── index.ts ├── types.ts ├── utils │ ├── crypto.ts │ ├── index.ts │ ├── metadata.ts │ └── transactions-batch.ts └── wallet │ └── index.ts ├── test ├── actions │ ├── addTokensToVault.test.ts │ ├── createMetadataAndME.test.ts │ ├── initStore.test.ts │ ├── metadata.test.ts │ ├── metaplex.test.ts │ ├── mintEditionFromMaster.test.ts │ ├── mintNFT.test.ts │ ├── redeemParticipationBidV3.test.ts │ ├── shared │ │ └── index.ts │ ├── signMetadata.test.ts │ ├── updateMetadata.test.ts │ └── utility │ │ ├── closeVault.test.ts │ │ ├── createExternalPriceAccountLocal.test.ts │ │ ├── createVault.test.ts │ │ └── initAuction.test.ts ├── auction.test.ts ├── conversion.test.ts ├── metadata.test.ts ├── metaplex.test.ts ├── setup │ └── build.ts ├── storage.test.ts ├── transactions │ ├── __snapshots__ │ │ ├── auction.test.ts.snap │ │ ├── metaplex.test.ts.snap │ │ └── vault.test.ts.snap │ ├── auction.test.ts │ ├── metaplex.test.ts │ └── vault.test.ts ├── uploads │ └── metaplex.jpg ├── utils.ts └── vault.test.ts ├── tsconfig.build.json ├── tsconfig.json ├── typedoc.json └── yarn.lock /.ammanrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | const { tmpdir } = require('os'); 4 | const path = require('path'); 5 | const { LOCALHOST, tmpLedgerDir } = require('@metaplex-foundation/amman'); 6 | 7 | const isLocal = process.env.LOCAL_MPL === '1'; 8 | 9 | const localDeployDir = isLocal ? 10 | path.resolve(process.cwd(), '../metaplex-program-library', 'target', 'deploy') : 11 | path.join(tmpdir(), 'test', 'rust', 'target', 'deploy'); 12 | 13 | const programIds = { 14 | metadata: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s', 15 | vault: 'vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn', 16 | auction: 'auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8', 17 | metaplex: 'p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98', 18 | }; 19 | 20 | function localDeployPath(programName) { 21 | return path.join(localDeployDir, `${programName}.so`); 22 | } 23 | const programs = [ 24 | { 25 | programId: programIds.metadata, 26 | deployPath: localDeployPath('mpl_token_metadata'), 27 | }, 28 | { 29 | programId: programIds.vault, 30 | deployPath: localDeployPath('mpl_token_vault'), 31 | }, 32 | { programId: programIds.auction, deployPath: localDeployPath('mpl_auction') }, 33 | { 34 | programId: programIds.metaplex, 35 | deployPath: localDeployPath('mpl_metaplex'), 36 | }, 37 | ]; 38 | 39 | const validator = { 40 | killRunningValidators: true, 41 | programs, 42 | jsonRpcUrl: LOCALHOST, 43 | websocketUrl: '', 44 | commitment: 'confirmed', 45 | ledgerDir: tmpLedgerDir(), 46 | resetLedger: true, 47 | verifyFees: false, 48 | }; 49 | 50 | module.exports = { 51 | validator, 52 | }; 53 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | lib 5 | coverage 6 | target 7 | -------------------------------------------------------------------------------- /.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/member-ordering': ['error'], 14 | '@typescript-eslint/ban-types': ['error', { extendDefaults: true, types: { '{}': false } }], 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | assignees: '' 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - OS: [e.g. iOS] 27 | - Browser [e.g. chrome, safari] 28 | - Version [e.g. 22] 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Browser [e.g. stock browser, safari] 34 | - Version [e.g. 22] 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | assignees: '' 6 | 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### Why? 13 | 14 | - Closes [issue link] 15 | 16 | 19 | 20 | ### What's being changed? 21 | 22 | 23 | 24 | ### Check off the following: 25 | 26 | - [ ] I have run `yarn fix` on my code before committing and opening this Pull Request 27 | - [ ] I made sure there are passing tests which cover this change 28 | - [ ] I ran `yarn test:all-local-mpl` locally and tests passed before opening this Pull Request 29 | -------------------------------------------------------------------------------- /.github/actions/install-rust/action.yml: -------------------------------------------------------------------------------- 1 | name: Install Rust 2 | 3 | inputs: 4 | toolchain: 5 | description: The Rust version to use, default env.RUST_TOOLCHAIN 6 | required: true 7 | 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Install Rust Stable 12 | id: rust_toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: ${{ inputs.toolchain }} 16 | override: true 17 | profile: minimal 18 | components: rustfmt, clippy 19 | 20 | - name: Add Cargo bin to Path 21 | run: | 22 | echo "$HOME/.cargo/bin" >> $GITHUB_PATH 23 | shell: bash 24 | 25 | - name: Verify Rust install 26 | run: | 27 | echo "Verifying rust '${{ inputs.toolchain }}' ..." 28 | rustc --version 29 | cargo --version 30 | cargo clippy --version 31 | rustfmt --version 32 | shell: bash 33 | 34 | - name: Share rustc hash 35 | run: | 36 | echo 'RUSTC_HASH=${{ steps.rust_toolchain.outputs.rustc_hash }}' >> $GITHUB_ENV 37 | shell: bash 38 | -------------------------------------------------------------------------------- /.github/actions/install-solana/action.yml: -------------------------------------------------------------------------------- 1 | name: Install Solana 2 | 3 | inputs: 4 | solana_version: 5 | description: Version of Solana to install, default env.SOLANA_VERSION 6 | required: true 7 | 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Cache Solana Install 12 | if: ${{ !env.ACT }} 13 | id: cache-solana-install 14 | uses: actions/cache@v2 15 | with: 16 | path: "$HOME/.local/share/solana/install/releases/${{ env.SOLANA_VERSION }}" 17 | key: ${{ runner.os }}-Solana-v${{ inputs.solana_version }} 18 | 19 | - name: Install Solana 20 | if: ${{ !env.ACT }} && steps.cache-solana-install.cache-hit != 'true' 21 | run: | 22 | sh -c "$(curl -sSfL https://release.solana.com/v${{ inputs.solana_version }}/install)" 23 | shell: bash 24 | 25 | - name: Set Active Solana Version 26 | run: | 27 | rm -f "$HOME/.local/share/solana/install/active_release" 28 | ln -s "$HOME/.local/share/solana/install/releases/${{ inputs.solana_version }}/solana-release" "$HOME/.local/share/solana/install/active_release" 29 | shell: bash 30 | 31 | - name: Add Solana bin to Path 32 | run: | 33 | echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH 34 | shell: bash 35 | 36 | - name: Verify Solana install 37 | run: | 38 | solana --version 39 | shell: bash 40 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/marketplace/actions/auto-labeler 2 | 3 | labels: 4 | 'SDK: JS': 5 | - '.*' -------------------------------------------------------------------------------- /.github/workflows/build-lint.yml: -------------------------------------------------------------------------------- 1 | name: Build Lint 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | release: 8 | name: Build Lint 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 14.17.0 18 | registry-url: 'https://registry.npmjs.org' 19 | - name: Install dependencies 20 | run: yarn install --immutable 21 | - name: Lint 22 | run: yarn lint && yarn prettier 23 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/marketplace/actions/auto-labeler 2 | 3 | name: Issue and Pull Request labeler 4 | on: 5 | issues: 6 | types: [opened, transferred, reopened] 7 | pull_request_target: 8 | types: [opened, reopened] 9 | 10 | jobs: 11 | labeler: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check Labels 15 | id: labeler 16 | uses: jimschubert/labeler-action@v2 17 | with: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | release: 8 | name: Pull Request 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 14.17.0 18 | registry-url: 'https://registry.npmjs.org' 19 | - name: Install dependencies 20 | run: yarn install --immutable 21 | - name: Lint 22 | run: yarn lint && yarn prettier 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | env: 8 | SOLANA_VERSION: 1.8.5 9 | RUST_TOOLCHAIN: stable 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | persist-credentials: false 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: 14.17.0 26 | registry-url: 'https://registry.npmjs.org' 27 | - name: Install dependencies 28 | run: yarn install --immutable 29 | - name: Install Solana 30 | uses: ./.github/actions/install-solana 31 | with: 32 | solana_version: ${{ env.SOLANA_VERSION }} 33 | - name: Install Rust 34 | uses: ./.github/actions/install-rust 35 | with: 36 | toolchain: ${{ env.RUST_TOOLCHAIN }} 37 | - name: Test 38 | run: yarn test:all 39 | - name: Build 40 | run: yarn build 41 | - name: Release 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 46 | run: npx semantic-release 47 | - name: Update docs 48 | run: | 49 | git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git 50 | yarn doc 51 | yarn gh-pages -d docs -u "github-actions-bot " 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test-integration.yml: -------------------------------------------------------------------------------- 1 | name: Test Integration 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | SOLANA_VERSION: 1.8.5 11 | RUST_TOOLCHAIN: stable 12 | 13 | jobs: 14 | release: 15 | name: Test Integration 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 14.17.0 25 | registry-url: 'https://registry.npmjs.org' 26 | - name: Install dependencies 27 | run: yarn install --immutable 28 | - name: Install Solana 29 | uses: ./.github/actions/install-solana 30 | with: 31 | solana_version: ${{ env.SOLANA_VERSION }} 32 | - name: Install Rust 33 | uses: ./.github/actions/install-rust 34 | with: 35 | toolchain: ${{ env.RUST_TOOLCHAIN }} 36 | 37 | - name: Test 38 | run: yarn test:all 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Package manager 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/releases 5 | !.yarn/plugins 6 | !.yarn/sdks 7 | !.yarn/versions 8 | .pnp.* 9 | 10 | # Dependencies 11 | node_modules/ 12 | 13 | # ESLint 14 | .eslintcache 15 | 16 | # Build and test 17 | dist/ 18 | build/ 19 | lib/ 20 | coverage/ 21 | 22 | # JetBrains 23 | .idea/ 24 | 25 | # VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | *.code-workspace 32 | 33 | # Local History for Visual Studio Code 34 | .history/ 35 | 36 | # Misc 37 | .DS_Store 38 | ._* 39 | .Spotlight-V100 40 | .Trashes 41 | *.swp 42 | 43 | # Cache 44 | .cache/ 45 | 46 | # Env 47 | *keypair.json 48 | 49 | # Docs 50 | docs 51 | 52 | # Bundle stats file 53 | stats.html 54 | 55 | .pnp.* 56 | .yarn/* 57 | !.yarn/patches 58 | !.yarn/plugins 59 | !.yarn/releases 60 | !.yarn/sdks 61 | !.yarn/versions 62 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | .idea 5 | .yarn 6 | test/ 7 | src/ 8 | stats.html 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.17.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "jsxSingleQuote": true, 5 | "printWidth": 100, 6 | "proseWrap": "always", 7 | "semi": true, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "all" 11 | } 12 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.1.1.cjs 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Metaplex JS SDK 2 | 3 | Thank you for your interest in contributing to the Metaplex JS SDK. In this doc you'll find an overview of the contribution workflow. 4 | 5 | ## Contribution Guide 6 | 7 | ### Reporting new issues 8 | 9 | If you find a bug or have an idea for a new feature, [open an Issue 10 | here](https://github.com/metaplex/js/issues), following the instructions to 11 | ensure the issue is triaged properly. 12 | 13 | ### Working on existing issues 14 | 15 | Search through the [currently open issues](https://github.com/metaplex-foundation/js/issues) for something you find interesting. 16 | If you're new to this project you might also [filter by the "good first issue" 17 | label](https://github.com/metaplex/js/issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue"). Issues that are not yet assigned are free to tackle! 18 | 19 | ### Contributing code changes 20 | 21 | This project uses the latest version of [yarn][] in `node_modules` mode which means 22 | you get great and fast package management but the same `package.json` file setup 23 | like you are used to. In order to build this project or test it see the 24 | `package.json` for all the possible scripts you can run. Generally the dev flow is 25 | this: 26 | 27 | ``` 28 | // Clone the repo 29 | git clone git@github.com:metaplex-foundation/js.git 30 | 31 | // Install deps 32 | yarn 33 | 34 | // Make your changes 35 | 36 | // Fix lint issues 37 | yarn fix 38 | 39 | // Test your code 40 | yarn test 41 | 42 | // Build it 43 | yarn build 44 | 45 | // Commit your changes 46 | ``` 47 | 48 | #### Commit Message Style Guide 49 | 50 | All commit messages and Pull Request titles SHOULD adhere to the [Conventional Commits 51 | specification](https://conventionalcommits.org/). Our auto-release tooling uses this 52 | specification to determine the type of release to make. 53 | 54 | For example, if the current version of the package was 1.0.0: 55 | 56 | |Example message|Result| 57 | |----------------------|-----| 58 | |fix: use proper key on WhitelistedCreator| bump the version to 1.0.1| 59 | |feat: implement ArweaveStorage provider |bump the version to 1.1.0| 60 | |BREAKING CHANGE: rewrite Metaplex.init |bump the version to 2.0.0| 61 | |feat!: rewrite Metaplex.init |bump the version to 2.0.0| 62 | 63 | 64 | When you're finished with the changes, create a pull request, also known as a PR. 65 | - First, self-review your own changes before opening a PR. This speeds up the review process by spotting potential issues locally. 66 | - Now open the PR, filling out the information requested in the Github UI. Use the Commit Message Style Guide above to format your PR title. 67 | - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one. 68 | - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge. 69 | Once you submit your PR, a foundation member will review your proposal. We may ask questions or request additional information. 70 | - We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. 71 | - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). 72 | 73 | Once your PR is merged, it will be publicly visible in the repository! Congrats! :tada::tada: You are now an official contributor to [Metaplex][] :sunglasses:. 74 | 75 | ## (Optional) Contributing To Low Level Contract SDKs 76 | 77 | The Metaplex JS SDK is a high level tool to accomplish specific actions and 78 | compose those actions to create NFT experiences on the web. It is made up of 79 | small contract specific packages which are tightly coupled to the actual 80 | Smart Contracts they implement. These packages are mostly generated from rust source 81 | code and are instruction specific. They live in 82 | https://github.com/metaplex-foundation/metaplex-program-library and are 83 | published on NPM here https://www.npmjs.com/search?q=%40metaplex-foundation. 84 | When developing features and fixes for the JS SDK you may need to contribute to 85 | these low level packages as well, but most of the time you won't have too. The 86 | following is a guide on how to get your development environment setup for local 87 | contribution to both repos. 88 | 89 | Within the same parent folder pull both JS SDK and the MPL repos like this: 90 | 91 | ``` 92 | git clone git@github.com:metaplex-foundation/js.git 93 | git clone git@github.com:metaplex-foundation/metaplex-program-library.git 94 | ``` 95 | 96 | Then jump into the js repo and run the local setup script like this: 97 | 98 | ``` 99 | cd js 100 | yarn 101 | yarn run link-mpl 102 | ``` 103 | 104 | Your local `metaplex-program-library` (MPL) JS packages are now linked to your 105 | JS SDK. You can change branches in MPL and re-build to see the changes 106 | reflected in the SDK. All packages in MPL support `yarn build` and `lerna 107 | build` see https://github.com/metaplex-foundation/metaplex-program-library for 108 | more details. 109 | 110 | Additionally, if you need to make rust changes to the `metaplex-program-library` 111 | and test them out in the JS SDK, run the following package scripts to get your 112 | changes running in your local CI: 113 | 114 | ``` 115 | yarn run test:all-local-mpl 116 | ``` 117 | 118 | This command will (re)build your local `metaplex-program-library` rust contracts 119 | and (re)start a local `solana-test-validator` with them pre-loaded, which the JS 120 | SDK CI integration tests will use automatically. Now you can debug the entire 121 | end-to-end flow locally! 122 | 123 | [yarn]: https://yarnpkg.com/ 124 | [Metaplex]: https://metaplex.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 metaplex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @metaplex/js · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/metaplex/js/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/@metaplex/js.svg?style=flat)](https://www.npmjs.com/package/@metaplex/js) 2 | 3 | 🚨 **Deprecated library** 4 | 5 | Please note that this library has been archived and is no longer being maintained. To integrate Metaplex's program with your JavaScript applications **please use the new JS SDK**: 6 | 7 | - 🐙 [New JS SDK repository](https://github.com/metaplex-foundation/js) 8 | - 📦 [New JS SDK NPM package](https://www.npmjs.com/package/@metaplex-foundation/js) 9 | 10 | --- 11 | 12 | ## Stability 13 | 14 | [Stability: 1 - Experimental](https://docs.metaplex.com/stability) 15 | 16 | > **In Development** - All interfaces are very likely to change very frequently. 17 | Please use caution when making use of this library. Bugs or behavior changes may 18 | surprise users when Experimental API modifications occur. 19 | 20 | ## Documentation 21 | 22 | - [API Docs](https://metaplex-foundation.github.io/js) 23 | - [Guides](https://docs.metaplex.com/sdk/js/getting-started) 24 | 25 | ## Community 26 | 27 | Join our [Discussions](https://github.com/metaplex-foundation/js/discussions) 28 | 29 | ## Contributing 30 | Visit [Contributing](CONTRIBUTING.md) 31 | 32 | ## Good First Issues 33 | 34 | See the list of starter issues [here](https://github.com/metaplex-foundation/js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 35 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | - [x] Structure 4 | - [x] Builds and Deployments 5 | - [x] Connection, Account, Transaction, Errors 6 | - [x] Programs (serialize/deserialize accounts, rpc transactions, simple actions) 7 | - [ ] Metadata 8 | - [x] Accounts 9 | - [x] Metadata 10 | - [x] Master Edition 11 | - [x] Edition 12 | - [x] Edition Marker 13 | - [ ] Instructions 14 | - [x] CreateMetadataAccount 15 | - [x] UpdateMetadataAccount 16 | - [x] UpdatePrimarySaleHappenedViaToken 17 | - [x] SignMetadata 18 | - [x] CreateMasterEdition 19 | - [x] MintNewEditionFromMasterEditionViaToken 20 | - [ ] [MintNewEditionFromMasterEditionViaVaultProxy](https://github.com/metaplex-foundation/js/issues/116) 21 | - [x] Actions 22 | - [X] Create 23 | - [X] Update 24 | - [X] Sign 25 | - [x] Send 26 | - [X] Mint Master Edition 27 | - [X] Mint Limited Edition from Master 28 | - [x] Burn 29 | - [ ] Metaplex 30 | - [ ] Accounts 31 | - [x] Bid Redemption Ticket 32 | - [x] Auction Manager (V2) 33 | - [ ] Safety Deposit Validation Ticket 34 | - [x] Payout Ticket 35 | - [ ] Token Tracker 36 | - [x] Prize Tracking Ticket 37 | - [ ] Auction Cache 38 | - [ ] Store Indexer 39 | - [x] Store 40 | - [x] Whitelisted Creator 41 | - [x] Safety Deposit Config 42 | - [ ] Original Authority Lookup 43 | - [ ] Instructions 44 | - [x] RedeemBid 45 | - [x] RedeemFullRightsTransferBid 46 | - [x] RedeemPrintingV2Bid 47 | - [x] StartAuction 48 | - [ ] EndAuction 49 | - [x] ClaimBid 50 | - [ ] EmptyPaymentAccount 51 | - [x] SetStore 52 | - [x] SetWhitelistedCreator 53 | - [ ] RedeemUnusedWinningConfigItemsAsAuctioneer 54 | - [ ] DecommissionAuctionManager 55 | - [x] RedeemPrintingV2Bid 56 | - [ ] WithdrawMasterEdition 57 | - [x] InitAuctionManagerV2 58 | - [ ] ValidateSafetyDepositBoxV2 59 | - [x] RedeemParticipationBidV3 60 | - [ ] SetStoreIndex 61 | - [ ] SetAuctionCache 62 | - [ ] Actions 63 | - [ ] Auction 64 | - [x] Accounts 65 | - [x] Auction 66 | - [x] Auction Extended 67 | - [x] Bidder Pot 68 | - [x] Bidder Meta 69 | - [ ] Instructions 70 | - [x] CancelBid 71 | - [x] CreateAuction 72 | - [x] CreateAuctionV2 73 | - [x] SetAuthority 74 | - [x] PlaceBid 75 | - [ ] Actions (no standalone actions) 76 | - [x] Cancel Bid 77 | - [x] Place Bid 78 | - [x] Redeem Full Rights Transfer Bid 79 | - [x] Redeem Printing V2 Bid 80 | - [x] Instant Sale 81 | - [ ] Vault 82 | - [ ] Accounts 83 | - [x] Safety Deposit Box 84 | - [x] Vault 85 | - [x] External Price 86 | - [x] Instructions 87 | - [x] InitVault 88 | - [x] AddTokenToInactiveVault 89 | - [x] ActivateVault 90 | - [x] CombineVault 91 | - [x] RedeemShares 92 | - [x] WithdrawTokenFromSafetyDepositBox 93 | - [x] MintFractionalShares 94 | - [x] WithdrawSharesFromTreasury 95 | - [x] AddSharesToTreasury 96 | - [x] UpdateExternalPriceAccount 97 | - [x] SetAuthority 98 | - [ ] Actions 99 | - [x] CreateVault 100 | - [x] CloseVault 101 | - [x] CreateExternalPriceAccount 102 | - [ ] AddTokensToVault 103 | - [ ] SetVaultAndAuctionAuthorities 104 | - [ ] UnwindVault 105 | - [ ] Candy Machine 106 | - [ ] [Accounts](https://github.com/metaplex-foundation/metaplex-program-library/issues/28) 107 | - [ ] Candy Machine 108 | - [ ] [Instructions](https://github.com/metaplex-foundation/metaplex-program-library/issues/28) 109 | - [ ] Mint 110 | - [ ] Update 111 | - [ ] Initialize 112 | - [ ] Initialize Config 113 | - [ ] Add Config Lines 114 | - [ ] [Actions](https://github.com/metaplex-foundation/js/issues/110) 115 | - [ ] Fair Launch 116 | - [ ] Accounts 117 | - [ ] Fair Launch 118 | - [ ] Ticket 119 | - [ ] Ticket Seq Lookup 120 | - [ ] Lottery Bitmap 121 | - [ ] Instructions 122 | - [ ] Initialize 123 | - [ ] Update 124 | - [ ] Create Lottery Bitmap 125 | - [ ] Update Lottery Bitmap 126 | - [ ] Start Phase Three 127 | - [ ] Restart Phase Two 128 | - [ ] Purchase Ticket 129 | - [ ] Adjust Ticket 130 | - [ ] Punch Ticket 131 | - [ ] Create Ticket Seq 132 | - [ ] Withdraw Funds 133 | - [ ] Receive Refund 134 | - [ ] Set Token Metadata 135 | - [ ] Set Participation NFT 136 | - [ ] Update Participation NFT 137 | - [ ] Mint Participation NFT 138 | - [ ] Mint Tokens 139 | - [ ] Actions 140 | - [ ] Packs TBD 141 | - [ ] Airdrop TBD 142 | - [ ] Fusion TBD 143 | - [ ] Providers 144 | - [ ] CoinGecko 145 | - [ ] Arweave 146 | - [ ] Global (common use cases) 147 | - [ ] Actions 148 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/iife/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Metaplex 5 | 6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/iife/index.js: -------------------------------------------------------------------------------- 1 | console.log(solanaWeb3); 2 | console.log(metaplex); 3 | 4 | const { PublicKey } = solanaWeb3; 5 | const { programs, Connection } = metaplex; 6 | const { Metadata } = programs.metadata; 7 | 8 | const metadataPubkey = new PublicKey('CZkFeERacU42qjApPyjamS13fNtz7y1wYLu5jyLpN1WL'); 9 | const connection = new Connection('devnet'); 10 | const btn = document.getElementById('metadata'); 11 | 12 | const run = async () => { 13 | btn.addEventListener('click', async () => { 14 | const metadata = await Metadata.load(connection, metadataPubkey); 15 | console.log(metadata); 16 | }); 17 | }; 18 | 19 | run(); 20 | -------------------------------------------------------------------------------- /examples/iife/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-iife", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "repository": { 6 | "url": "https://github.com/metaplex/js.git" 7 | }, 8 | "private": true, 9 | "scripts": { 10 | "start": "parcel index.html --cache-dir build/cache" 11 | }, 12 | "devDependencies": { 13 | "parcel": "next" 14 | }, 15 | "browserslist": [ 16 | "last 2 Chrome versions" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/node/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, NodeWallet, programs, actions } from '@metaplex/js'; 2 | import { Keypair, PublicKey } from '@solana/web3.js'; 3 | 4 | const { Metadata } = programs.metadata; 5 | const { initStore } = actions; 6 | 7 | const payer = Keypair.fromSecretKey( 8 | new Uint8Array([ 9 | 225, 60, 117, 68, 123, 252, 1, 200, 41, 251, 54, 121, 6, 167, 204, 18, 140, 168, 206, 74, 254, 10 | 156, 230, 10, 212, 124, 162, 85, 120, 78, 122, 106, 187, 209, 148, 182, 34, 149, 175, 173, 192, 11 | 85, 175, 252, 231, 130, 76, 40, 175, 177, 44, 111, 250, 168, 3, 236, 149, 34, 236, 19, 46, 9, 12 | 66, 138, 13 | ]), 14 | ); 15 | const metadataPubkey = new PublicKey('CZkFeERacU42qjApPyjamS13fNtz7y1wYLu5jyLpN1WL'); 16 | const connection = new Connection('devnet'); 17 | // Wallet 18 | const wallet = new NodeWallet(payer); 19 | 20 | const run = async () => { 21 | // Find metadata by owner 22 | const ownedMetadata = await Metadata.findByOwnerV2(connection, payer.publicKey); 23 | console.log(ownedMetadata); 24 | 25 | // Find specific metadada 26 | const metadata = await Metadata.load(connection, metadataPubkey); 27 | console.log(metadata); 28 | 29 | // Transactions 30 | 31 | // Set store 32 | // const storeId = await Store.getPDA(wallet.publicKey); 33 | // const tx = new SetStore( 34 | // { feePayer: wallet.publicKey }, 35 | // { 36 | // admin: wallet.publicKey, 37 | // store: storeId, 38 | // isPublic: true, 39 | // }, 40 | // ); 41 | 42 | // tx.recentBlockhash = (await connection.getRecentBlockhash()).blockhash; 43 | // const signedTx = await wallet.signTransaction(tx); 44 | // const txId = await connection.sendRawTransaction(signedTx.serialize()); 45 | // console.log(storeId.toString(), txId); 46 | 47 | // Metaplex 48 | const { storeId, txId } = await initStore({ connection, wallet }); 49 | console.log(storeId, txId); 50 | }; 51 | 52 | run(); 53 | -------------------------------------------------------------------------------- /examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-node", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "repository": { 6 | "url": "https://github.com/metaplex/js.git" 7 | }, 8 | "private": true, 9 | "scripts": { 10 | "start": "ts-node -P tsconfig.json index.ts" 11 | }, 12 | "dependencies": { 13 | "@metaplex/js": "workspace:^1.1.1", 14 | "@solana/spl-token": "^0.1.8", 15 | "@solana/web3.js": "^1.24.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^16.9.2", 19 | "ts-node": "^10.2.1", 20 | "ts-node-dev": "^1.1.8", 21 | "typescript": "^4.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "declaration": true, 6 | "removeComments": true, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "baseUrl": ".", 12 | "skipLibCheck": true, 13 | }, 14 | "exclude": ["node_modules", "dist", "build", "lib"], 15 | "include": ["./"] 16 | } 17 | -------------------------------------------------------------------------------- /examples/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Metaplex 5 | 6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/web/index.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { programs, Connection } from '@metaplex/js'; 3 | 4 | const { Metadata } = programs.metadata; 5 | 6 | const metadataPubkey = new PublicKey('CZkFeERacU42qjApPyjamS13fNtz7y1wYLu5jyLpN1WL'); 7 | const connection = new Connection('devnet'); 8 | const btn = document.getElementById('metadata'); 9 | 10 | const run = async () => { 11 | btn.addEventListener('click', async () => { 12 | const metadata = await Metadata.load(connection, metadataPubkey); 13 | console.log(metadata); 14 | }); 15 | }; 16 | 17 | run(); 18 | -------------------------------------------------------------------------------- /examples/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-web", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "repository": { 6 | "url": "https://github.com/metaplex/js.git" 7 | }, 8 | "private": true, 9 | "scripts": { 10 | "start": "parcel index.html --cache-dir build/cache" 11 | }, 12 | "dependencies": { 13 | "@metaplex/js": "workspace:^1.1.1", 14 | "@solana/spl-token": "^0.1.8", 15 | "@solana/web3.js": "^1.24.1" 16 | }, 17 | "devDependencies": { 18 | "parcel": "next", 19 | "typescript": "^4.4.3" 20 | }, 21 | "browserslist": [ 22 | "last 2 Chrome versions" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/*.(spec|test).ts'], 5 | testPathIgnorePatterns: ['rust'], 6 | globals: { 7 | 'ts-jest': { 8 | tsconfig: './tsconfig.json', 9 | diagnostics: false, 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "yarn@3.1.1", 3 | "name": "@metaplex/js", 4 | "main": "lib/index.cjs.js", 5 | "module": "lib/index.esm.js", 6 | "types": "lib/index.d.ts", 7 | "browser": { 8 | "./lib/index.cjs.js": "./lib/index.browser.esm.js", 9 | "./lib/index.esm.js": "./lib/index.browser.esm.js" 10 | }, 11 | "files": [ 12 | "/lib", 13 | "/src" 14 | ], 15 | "version": "4.12.0", 16 | "license": "MIT", 17 | "description": "Metaplex JavaScript API", 18 | "keywords": [ 19 | "nft", 20 | "metaplex", 21 | "solana", 22 | "blockchain" 23 | ], 24 | "author": "Metaplex Maintainers ", 25 | "homepage": "https://metaplex.com", 26 | "repository": { 27 | "url": "https://github.com/metaplex/js.git" 28 | }, 29 | "scripts": { 30 | "doc": "typedoc", 31 | "doc:watch": "typedoc --watch", 32 | "doc:update": "yarn doc && ./sh/publish-docs", 33 | "build": "rimraf lib && rollup -c", 34 | "dev": "rollup -c --watch", 35 | "lint": "eslint \"{src,test}/**/*.ts\" --format stylish", 36 | "fix:lint": "yarn lint --fix", 37 | "prettier": "npx prettier \"{src,test}/**/*.ts\" --check", 38 | "fix:prettier": "yarn prettier --write", 39 | "fix": "yarn fix:lint && yarn fix:prettier", 40 | "test:build": "esr ./test/setup/build.ts", 41 | "test:build-local-mpl": "LOCAL_MPL=1 esr ./test/setup/build.ts", 42 | "test:init": "DEBUG=\"amman*\" amman validator", 43 | "test:init-local-mpl": "DEBUG=\"amman*\" LOCAL_MPL=1 amman validator", 44 | "test:all": "yarn test:build && yarn test:init && yarn test", 45 | "test:all-local-mpl": "yarn test:build-local-mpl && yarn test:init-local-mpl && yarn test", 46 | "test": "jest --silent false", 47 | "test:watch": "jest --watch", 48 | "prepare": "husky install", 49 | "link-mpl": "yarn link ../metaplex-program-library --private -r -A" 50 | }, 51 | "dependencies": { 52 | "@metaplex-foundation/mpl-auction": "^0.0.2", 53 | "@metaplex-foundation/mpl-core": "^0.0.2", 54 | "@metaplex-foundation/mpl-metaplex": "^0.0.5", 55 | "@metaplex-foundation/mpl-token-metadata": "^1.1.0", 56 | "@metaplex-foundation/mpl-token-vault": "^0.0.2", 57 | "@solana/spl-token": "^0.1.8", 58 | "@solana/web3.js": "^1.30.2", 59 | "@types/bs58": "^4.0.1", 60 | "axios": "^0.25.0", 61 | "bn.js": "^5.2.0", 62 | "borsh": "^0.4.0", 63 | "bs58": "^4.0.1", 64 | "buffer": "^6.0.3", 65 | "crypto-hash": "^1.3.0", 66 | "form-data": "^4.0.0" 67 | }, 68 | "peerDependencies": { 69 | "@metaplex-foundation/mpl-auction": "^0.0.2", 70 | "@metaplex-foundation/mpl-core": "^0.0.2", 71 | "@metaplex-foundation/mpl-metaplex": "^0.0.5", 72 | "@metaplex-foundation/mpl-token-metadata": "^0.0.2", 73 | "@metaplex-foundation/mpl-token-vault": "^0.0.2", 74 | "@solana/spl-token": "^0.1.8", 75 | "@solana/web3.js": "^1.30.2" 76 | }, 77 | "devDependencies": { 78 | "@commitlint/cli": "^13.2.0", 79 | "@commitlint/config-conventional": "^13.2.0", 80 | "@metaplex-foundation/amman": "^0.0.8", 81 | "@rollup/plugin-commonjs": "^20.0.0", 82 | "@rollup/plugin-json": "^4.1.0", 83 | "@rollup/plugin-node-resolve": "^13.0.4", 84 | "@rollup/plugin-replace": "^3.0.0", 85 | "@semantic-release/changelog": "^6.0.0", 86 | "@semantic-release/git": "^10.0.0", 87 | "@types/async-retry": "^1.4.3", 88 | "@types/jest": "^27.0.1", 89 | "@types/node": "^16.9.2", 90 | "@typescript-eslint/eslint-plugin": "^4.30.0", 91 | "@typescript-eslint/parser": "^4.30.0", 92 | "@zerollup/ts-transform-paths": "^1.7.18", 93 | "esbuild": "^0.14.2", 94 | "esbuild-runner": "^2.2.1", 95 | "eslint": "^7.32.0", 96 | "eslint-config-prettier": "^8.3.0", 97 | "eslint-plugin-prettier": "^4.0.0", 98 | "gh-pages": "^3.2.3", 99 | "husky": "^7.0.2", 100 | "jest": "^27.1.0", 101 | "node-stream-zip": "^1.15.0", 102 | "prettier": "^2.3.2", 103 | "rimraf": "^3.0.2", 104 | "rollup": "^2.56.3", 105 | "rollup-plugin-terser": "^7.0.2", 106 | "rollup-plugin-typescript2": "^0.30.0", 107 | "rollup-plugin-visualizer": "^5.5.2", 108 | "semantic-release": "^18.0.0", 109 | "ts-jest": "^27.0.5", 110 | "ts-node": "^10.2.1", 111 | "ttypescript": "^1.5.12", 112 | "typedoc": "^0.22.3", 113 | "typescript": "^4.4.3" 114 | }, 115 | "browserslist": [ 116 | "defaults", 117 | "not IE 11", 118 | "maintained node versions" 119 | ], 120 | "commitlint": { 121 | "extends": [ 122 | "@commitlint/config-conventional" 123 | ] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: ['main'], 3 | plugins: [ 4 | [ 5 | '@semantic-release/commit-analyzer', 6 | { 7 | preset: 'angular', 8 | releaseRules: [ 9 | { type: 'refactor', release: 'patch' }, 10 | { type: 'style', release: 'patch' }, 11 | ], 12 | parserOpts: { 13 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'], 14 | }, 15 | }, 16 | ], 17 | [ 18 | '@semantic-release/release-notes-generator', 19 | { 20 | preset: 'angular', 21 | parserOpts: { 22 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'], 23 | }, 24 | }, 25 | ], 26 | [ 27 | '@semantic-release/changelog', 28 | { 29 | changelogFile: 'CHANGELOG.md', 30 | }, 31 | ], 32 | '@semantic-release/npm', 33 | '@semantic-release/github', 34 | [ 35 | '@semantic-release/git', 36 | { 37 | assets: ['CHANGELOG.md', 'package.json'], 38 | message: 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]', 39 | }, 40 | ], 41 | ], 42 | }; 43 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import ttypescript from 'ttypescript'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import json from '@rollup/plugin-json'; 6 | import { visualizer } from 'rollup-plugin-visualizer'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | import * as pkg from './package.json'; 9 | 10 | const external = [ 11 | ...Object.keys(pkg.peerDependencies || {}), 12 | ]; 13 | 14 | const input = 'src/index.ts'; 15 | 16 | const plugins = ({ browser }) => [ 17 | typescript({ 18 | typescript: ttypescript, 19 | tsconfig: 'tsconfig.build.json', 20 | tsconfigOverride: { 21 | compilerOptions: { 22 | declaration: true, 23 | module: 'ES2015', 24 | }, 25 | }, 26 | }), 27 | resolve({ 28 | browser, 29 | dedupe: ['bn.js', 'buffer', 'crypto-hash'], 30 | preferBuiltins: !browser, 31 | }), 32 | commonjs(), 33 | json(), 34 | ]; 35 | 36 | const config = ({ browser, format } = { browser: false }) => { 37 | const config = { 38 | input, 39 | plugins: plugins({ browser }), 40 | // Default external, can be overrided 41 | external: [ 42 | ...external, 43 | '@types/bs58', 44 | 'axios', 45 | 'bn.js', 46 | 'borsh', 47 | 'bs58', 48 | 'buffer', 49 | 'crypto-hash', 50 | ], 51 | }; 52 | 53 | if (browser) { 54 | switch (format) { 55 | case 'esm': 56 | config.output = { 57 | file: 'lib/index.browser.esm.js', 58 | format: 'es', 59 | sourcemap: true, 60 | }; 61 | break; 62 | case 'iife': 63 | const base = { 64 | format: 'iife', 65 | name: 'metaplex', 66 | sourcemap: true, 67 | globals: { 68 | '@solana/web3.js': 'solanaWeb3', 69 | '@solana/spl-token': 'splToken', 70 | }, 71 | }; 72 | config.output = [ 73 | { 74 | ...base, 75 | file: 'lib/index.iife.js', 76 | }, 77 | { 78 | ...base, 79 | file: 'lib/index.iife.min.js', 80 | plugins: [terser(), visualizer()], 81 | }, 82 | ]; 83 | config.context = 'window'; 84 | config.external = ['@solana/web3.js', '@solana/spl-token']; 85 | break; 86 | default: 87 | throw new Error(`Unknown format: ${format}`); 88 | } 89 | } else { 90 | config.output = [ 91 | { 92 | file: 'lib/index.cjs.js', 93 | format: 'cjs', 94 | sourcemap: true, 95 | }, 96 | { 97 | file: 'lib/index.esm.js', 98 | format: 'es', 99 | sourcemap: true, 100 | }, 101 | ]; 102 | } 103 | 104 | return config; 105 | }; 106 | 107 | export default [ 108 | // Node 109 | config(), 110 | // Browser 111 | config({ browser: true, format: 'esm' }), 112 | config({ browser: true, format: 'iife' }), 113 | ]; 114 | -------------------------------------------------------------------------------- /sh/publish-docs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | MAIN_BRANCH='main' 4 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 5 | 6 | if [[ $CURRENT_BRANCH != $MAIN_BRANCH ]]; then 7 | echo "Aborting doc generation. Please switch to the $MAIN_BRANCH branch to generate documentation." 8 | exit 1 9 | fi 10 | 11 | if [[ -z $(git status --porcelain) ]]; 12 | then 13 | echo "Publishing docs to gh-pages.." 14 | yarn gh-pages -d docs 15 | echo "Docs will be live in a few seconds: https://metaplex-foundation.github.io/js" 16 | else 17 | echo "Aborting doc generation. Please commit any changes before updating docs." 18 | echo "" 19 | git status 20 | fi -------------------------------------------------------------------------------- /src/Connection.ts: -------------------------------------------------------------------------------- 1 | import { clusterApiUrl, Commitment, Connection as SolanaConnection } from '@solana/web3.js'; 2 | 3 | export enum ChainId { 4 | MainnetBeta = 101, 5 | Testnet = 102, 6 | Devnet = 103, 7 | } 8 | 9 | export const ENV: Record = { 10 | devnet: { 11 | endpoint: clusterApiUrl('devnet'), 12 | ChainId: ChainId.Devnet, 13 | }, 14 | 'mainnet-beta': { 15 | endpoint: 'https://api.metaplex.solana.com/', 16 | ChainId: ChainId.MainnetBeta, 17 | }, 18 | 'mainnet-beta (Solana)': { 19 | endpoint: 'https://api.mainnet-beta.solana.com', 20 | ChainId: ChainId.MainnetBeta, 21 | }, 22 | 'mainnet-beta (Serum)': { 23 | endpoint: 'https://solana-api.projectserum.com/', 24 | ChainId: ChainId.MainnetBeta, 25 | }, 26 | testnet: { 27 | endpoint: clusterApiUrl('testnet'), 28 | ChainId: ChainId.Testnet, 29 | }, 30 | }; 31 | 32 | export class Connection extends SolanaConnection { 33 | constructor(endpoint: keyof typeof ENV | string = 'mainnet-beta', commitment?: Commitment) { 34 | if (endpoint in ENV) endpoint = ENV[endpoint].endpoint; 35 | super(endpoint, commitment); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/actions/addTokensToVault.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { AccountLayout } from '@solana/spl-token'; 3 | import { 4 | Vault, 5 | SafetyDepositBox, 6 | AddTokenToInactiveVault, 7 | } from '@metaplex-foundation/mpl-token-vault'; 8 | import { Connection, TransactionSignature, PublicKey, Keypair } from '@solana/web3.js'; 9 | 10 | import { Wallet } from '../wallet'; 11 | import { createApproveTxs } from './shared'; 12 | import { sendTransaction } from './transactions'; 13 | import { CreateTokenAccount } from '../transactions'; 14 | import { TransactionsBatch } from '../utils/transactions-batch'; 15 | 16 | interface Token2Add { 17 | tokenAccount: PublicKey; 18 | tokenMint: PublicKey; 19 | amount: BN; 20 | } 21 | 22 | interface SafetyDepositTokenStore { 23 | txId: TransactionSignature; 24 | tokenAccount: PublicKey; 25 | tokenStoreAccount: PublicKey; 26 | tokenMint: PublicKey; 27 | } 28 | 29 | interface AddTokensToVaultParams { 30 | connection: Connection; 31 | wallet: Wallet; 32 | vault: PublicKey; 33 | nfts: Token2Add[]; 34 | } 35 | 36 | interface AddTokensToVaultResponse { 37 | safetyDepositTokenStores: SafetyDepositTokenStore[]; 38 | } 39 | 40 | export const addTokensToVault = async ({ 41 | connection, 42 | wallet, 43 | vault, 44 | nfts, 45 | }: AddTokensToVaultParams): Promise => { 46 | const txOptions = { feePayer: wallet.publicKey }; 47 | const safetyDepositTokenStores: SafetyDepositTokenStore[] = []; 48 | 49 | const vaultAuthority = await Vault.getPDA(vault); 50 | const accountRent = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 51 | 52 | for (const nft of nfts) { 53 | const tokenTxBatch = new TransactionsBatch({ transactions: [] }); 54 | const safetyDepositBox = await SafetyDepositBox.getPDA(vault, nft.tokenMint); 55 | 56 | const tokenStoreAccount = Keypair.generate(); 57 | const tokenStoreAccountTx = new CreateTokenAccount(txOptions, { 58 | newAccountPubkey: tokenStoreAccount.publicKey, 59 | lamports: accountRent, 60 | mint: nft.tokenMint, 61 | owner: vaultAuthority, 62 | }); 63 | tokenTxBatch.addTransaction(tokenStoreAccountTx); 64 | tokenTxBatch.addSigner(tokenStoreAccount); 65 | 66 | const { authority: transferAuthority, createApproveTx } = createApproveTxs({ 67 | account: nft.tokenAccount, 68 | owner: wallet.publicKey, 69 | amount: nft.amount.toNumber(), 70 | }); 71 | tokenTxBatch.addTransaction(createApproveTx); 72 | tokenTxBatch.addSigner(transferAuthority); 73 | 74 | const addTokenTx = new AddTokenToInactiveVault(txOptions, { 75 | vault, 76 | vaultAuthority: wallet.publicKey, 77 | tokenAccount: nft.tokenAccount, 78 | tokenStoreAccount: tokenStoreAccount.publicKey, 79 | transferAuthority: transferAuthority.publicKey, 80 | safetyDepositBox: safetyDepositBox, 81 | amount: nft.amount, 82 | }); 83 | tokenTxBatch.addTransaction(addTokenTx); 84 | 85 | const txId = await sendTransaction({ 86 | connection, 87 | wallet, 88 | txs: tokenTxBatch.transactions, 89 | signers: tokenTxBatch.signers, 90 | }); 91 | 92 | safetyDepositTokenStores.push({ 93 | txId, 94 | tokenStoreAccount: tokenStoreAccount.publicKey, 95 | tokenMint: nft.tokenMint, 96 | tokenAccount: nft.tokenAccount, 97 | }); 98 | } 99 | 100 | return { safetyDepositTokenStores }; 101 | }; 102 | -------------------------------------------------------------------------------- /src/actions/burnToken.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { sendTransaction } from './transactions'; 5 | import { Transaction } from '@metaplex-foundation/mpl-core'; 6 | import { Token, TOKEN_PROGRAM_ID, u64 } from '@solana/spl-token'; 7 | 8 | /** Parameters for {@link burnToken} **/ 9 | export interface BurnTokenParams { 10 | connection: Connection; 11 | /** Will be used as the fee payer and as the `owner` if none is specified. If ${@link close} is left as its' default `true` value, the `wallet` parameter will also serve as the destination wallet to refund rent exemption SOL lamports to. **/ 12 | wallet: Wallet; 13 | /** The associated token account containing the tokens to be burned **/ 14 | account: PublicKey; 15 | /** The mint account of the token to be burned **/ 16 | mint: PublicKey; 17 | /** Amount of tokens (accounting for decimals) to burn. One important nuance to remember is that each token mint has a different amount of decimals, which need to be accounted while specifying the amount. For instance, to burn 1 token with a 0 decimal mint you would provide `1` as the amount, but for a token mint with 6 decimals you would provide `1000000` as the amount to burn one whole token **/ 18 | amount: number | u64; 19 | /** The owner authority of the associated token account containing the burnt tokens **/ 20 | owner?: PublicKey; 21 | /** Set to `true` if you wish to close the token account after burning the token **/ 22 | close?: boolean; 23 | } 24 | 25 | export interface BurnTokenResponse { 26 | txId: string; 27 | } 28 | 29 | /** 30 | * Burns token of the given mint and optionally closes the associated token account. Please note that by default this action attempts to close the account after burning tokens, which will fail if not all the tokens contained in the account were burned. If you want to burn only part of the account balance, make sure you set `close` to `false`. 31 | */ 32 | export const burnToken = async ({ 33 | connection, 34 | wallet, 35 | account, 36 | mint, 37 | amount, 38 | owner, 39 | close = true, 40 | }: BurnTokenParams): Promise => { 41 | const tx = new Transaction({ feePayer: wallet.publicKey }).add( 42 | Token.createBurnInstruction( 43 | TOKEN_PROGRAM_ID, 44 | mint, 45 | account, 46 | owner ?? wallet.publicKey, 47 | [], 48 | amount, 49 | ), 50 | ); 51 | 52 | if (close) { 53 | tx.add( 54 | Token.createCloseAccountInstruction( 55 | TOKEN_PROGRAM_ID, 56 | account, 57 | wallet.publicKey, 58 | owner ?? wallet.publicKey, 59 | [], 60 | ), 61 | ); 62 | } 63 | 64 | const txId = await sendTransaction({ connection, wallet, txs: [tx] }); 65 | 66 | return { txId }; 67 | }; 68 | -------------------------------------------------------------------------------- /src/actions/cancelBid.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from '@solana/web3.js'; 2 | import { AccountLayout, NATIVE_MINT, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Wallet } from '../wallet'; 4 | import { Connection } from '../Connection'; 5 | import { sendTransaction } from './transactions'; 6 | import { 7 | AuctionExtended, 8 | BidderMetadata, 9 | BidderPot, 10 | CancelBid, 11 | } from '@metaplex-foundation/mpl-auction'; 12 | import { TransactionsBatch } from '../utils/transactions-batch'; 13 | import { AuctionManager } from '@metaplex-foundation/mpl-metaplex'; 14 | import { CreateTokenAccount } from '../transactions'; 15 | import { Transaction } from '@metaplex-foundation/mpl-core'; 16 | 17 | /** 18 | * Parameters for {@link cancelBid} 19 | */ 20 | export interface CancelBidParams { 21 | connection: Connection; 22 | /** Wallet of the original bidder **/ 23 | wallet: Wallet; 24 | /** Program account of the auction for the bid to be cancelled **/ 25 | auction: PublicKey; 26 | /** SPL associated token account where the tokens are deposited **/ 27 | bidderPotToken: PublicKey; 28 | /** The bidders token account they'll receive refund with **/ 29 | destAccount?: PublicKey; 30 | } 31 | 32 | export interface CancelBidResponse { 33 | txId: string; 34 | } 35 | 36 | /** 37 | * Cancel a bid on a running auction. Any bidder can cancel any time during an auction, but only non-winners of the auction can cancel after it ends. When users cancel, they receive full refunds. 38 | */ 39 | export const cancelBid = async ({ 40 | connection, 41 | wallet, 42 | auction, 43 | bidderPotToken, 44 | destAccount, 45 | }: CancelBidParams): Promise => { 46 | const bidder = wallet.publicKey; 47 | const auctionManager = await AuctionManager.getPDA(auction); 48 | const manager = await AuctionManager.load(connection, auctionManager); 49 | const { 50 | data: { tokenMint }, 51 | } = await manager.getAuction(connection); 52 | 53 | const auctionTokenMint = new PublicKey(tokenMint); 54 | const vault = new PublicKey(manager.data.vault); 55 | const auctionExtended = await AuctionExtended.getPDA(vault); 56 | const bidderPot = await BidderPot.getPDA(auction, bidder); 57 | const bidderMeta = await BidderMetadata.getPDA(auction, bidder); 58 | 59 | const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 60 | const txBatch = await getCancelBidTransactions({ 61 | destAccount, 62 | bidder, 63 | accountRentExempt, 64 | bidderPot, 65 | bidderPotToken, 66 | bidderMeta, 67 | auction, 68 | auctionExtended, 69 | auctionTokenMint, 70 | vault, 71 | }); 72 | 73 | const txId = await sendTransaction({ 74 | connection, 75 | wallet, 76 | txs: txBatch.toTransactions(), 77 | signers: txBatch.signers, 78 | }); 79 | 80 | return { txId }; 81 | }; 82 | 83 | interface CancelBidTransactionsParams { 84 | destAccount?: PublicKey; 85 | bidder: PublicKey; 86 | accountRentExempt: number; 87 | bidderPot: PublicKey; 88 | bidderPotToken: PublicKey; 89 | bidderMeta: PublicKey; 90 | auction: PublicKey; 91 | auctionExtended: PublicKey; 92 | auctionTokenMint: PublicKey; 93 | vault: PublicKey; 94 | } 95 | 96 | export const getCancelBidTransactions = async ({ 97 | destAccount, 98 | bidder, 99 | accountRentExempt, 100 | bidderPot, 101 | bidderPotToken, 102 | bidderMeta, 103 | auction, 104 | auctionExtended, 105 | auctionTokenMint, 106 | vault, 107 | }: CancelBidTransactionsParams): Promise => { 108 | const txBatch = new TransactionsBatch({ transactions: [] }); 109 | if (!destAccount) { 110 | const account = Keypair.generate(); 111 | const createTokenAccountTransaction = new CreateTokenAccount( 112 | { feePayer: bidder }, 113 | { 114 | newAccountPubkey: account.publicKey, 115 | lamports: accountRentExempt, 116 | mint: NATIVE_MINT, 117 | }, 118 | ); 119 | const closeTokenAccountInstruction = new Transaction().add( 120 | Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, account.publicKey, bidder, bidder, []), 121 | ); 122 | txBatch.addTransaction(createTokenAccountTransaction); 123 | txBatch.addAfterTransaction(closeTokenAccountInstruction); 124 | txBatch.addSigner(account); 125 | destAccount = account.publicKey; 126 | } 127 | 128 | const cancelBidTransaction = new CancelBid( 129 | { feePayer: bidder }, 130 | { 131 | bidder, 132 | bidderToken: destAccount, 133 | bidderPot, 134 | bidderPotToken, 135 | bidderMeta, 136 | auction, 137 | auctionExtended, 138 | tokenMint: auctionTokenMint, 139 | resource: vault, 140 | }, 141 | ); 142 | txBatch.addTransaction(cancelBidTransaction); 143 | 144 | return txBatch; 145 | }; 146 | -------------------------------------------------------------------------------- /src/actions/claimBid.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { sendTransaction } from './transactions'; 5 | import { Auction, AuctionExtended, BidderPot } from '@metaplex-foundation/mpl-auction'; 6 | import { TransactionsBatch } from '../utils/transactions-batch'; 7 | import { AuctionManager, ClaimBid } from '@metaplex-foundation/mpl-metaplex'; 8 | 9 | /** 10 | * Parameters for {@link claimBid} 11 | */ 12 | export interface ClaimBidParams { 13 | connection: Connection; 14 | /** Wallet of the bidder the bid that is being cancelled belongs to **/ 15 | wallet: Wallet; 16 | /** The address of the auction program account for the bid that is being cancelled **/ 17 | auction: PublicKey; 18 | /** The address of the store the auction manager the bid is being cancelled on belongs to **/ 19 | store: PublicKey; 20 | /** Bidder pot SPL associated token account **/ 21 | bidderPotToken: PublicKey; 22 | } 23 | 24 | export interface ClaimBidResponse { 25 | txId: string; 26 | } 27 | 28 | /** 29 | * Claim a winning bid as the auctioneer. Pulling money out of the auction contract as an auctioneer can only be done after an auction has ended and must be done for each winning bid, one after the other. 30 | */ 31 | export const claimBid = async ({ 32 | connection, 33 | wallet, 34 | store, 35 | auction, 36 | bidderPotToken, 37 | }: ClaimBidParams): Promise => { 38 | // get data for transactions 39 | const bidder = wallet.publicKey; 40 | const auctionManager = await AuctionManager.getPDA(auction); 41 | const manager = await AuctionManager.load(connection, auctionManager); 42 | const vault = new PublicKey(manager.data.vault); 43 | const { 44 | data: { tokenMint }, 45 | } = await Auction.load(connection, auction); 46 | const acceptPayment = new PublicKey(manager.data.acceptPayment); 47 | const auctionExtended = await AuctionExtended.getPDA(vault); 48 | const auctionTokenMint = new PublicKey(tokenMint); 49 | const bidderPot = await BidderPot.getPDA(auction, bidder); 50 | //// 51 | 52 | const txBatch = await getClaimBidTransactions({ 53 | auctionTokenMint, 54 | bidder, 55 | store, 56 | vault, 57 | auction, 58 | auctionExtended, 59 | auctionManager, 60 | acceptPayment, 61 | bidderPot, 62 | bidderPotToken, 63 | }); 64 | 65 | const txId = await sendTransaction({ 66 | connection, 67 | wallet, 68 | txs: txBatch.toTransactions(), 69 | signers: txBatch.signers, 70 | }); 71 | 72 | return { txId }; 73 | }; 74 | 75 | interface IClaimBidTransactionsParams { 76 | bidder: PublicKey; 77 | bidderPotToken?: PublicKey; 78 | bidderPot: PublicKey; 79 | auction: PublicKey; 80 | auctionExtended: PublicKey; 81 | auctionTokenMint: PublicKey; 82 | vault: PublicKey; 83 | store: PublicKey; 84 | auctionManager: PublicKey; 85 | acceptPayment: PublicKey; 86 | } 87 | 88 | export const getClaimBidTransactions = async ({ 89 | bidder, 90 | auctionTokenMint, 91 | store, 92 | vault, 93 | auction, 94 | auctionManager, 95 | auctionExtended, 96 | acceptPayment, 97 | bidderPot, 98 | bidderPotToken, 99 | }: IClaimBidTransactionsParams) => { 100 | const txBatch = new TransactionsBatch({ transactions: [] }); 101 | 102 | // create claim bid 103 | const claimBidTransaction = new ClaimBid( 104 | { feePayer: bidder }, 105 | { 106 | store, 107 | vault, 108 | auction, 109 | auctionExtended, 110 | auctionManager, 111 | bidder, 112 | tokenMint: auctionTokenMint, 113 | acceptPayment, 114 | bidderPot, 115 | bidderPotToken, 116 | }, 117 | ); 118 | txBatch.addTransaction(claimBidTransaction); 119 | //// 120 | 121 | return txBatch; 122 | }; 123 | -------------------------------------------------------------------------------- /src/actions/createMasterEdition.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { 4 | CreateMasterEdition, 5 | MasterEdition, 6 | Metadata, 7 | } from '@metaplex-foundation/mpl-token-metadata'; 8 | import { sendTransaction } from './transactions'; 9 | import BN from 'bn.js'; 10 | 11 | /** Parameters for {@link createMasterEdition} **/ 12 | export interface CreateMasterEditionParams { 13 | connection: Connection; 14 | /** The signer and fee payer for the operation. This wallet must be the same signer used to create the {@link Metadata} program account. **/ 15 | wallet: Wallet; 16 | /** This has to be the same mint provided when creating the {@link Metadata} program account and that account must already exist prior to creating the {@link MasterEdition} account. **/ 17 | editionMint: PublicKey; 18 | /** 19 | * You can optionally specify an updateAuthority different from the provided {@link wallet} 20 | * @default The updateAuthority will be set to the provided {@link wallet} address if not otherwise specified. 21 | **/ 22 | updateAuthority?: PublicKey; 23 | maxSupply?: BN; 24 | } 25 | 26 | /** 27 | * Creates a MasterEdition program account. 28 | * 29 | * Please note that for this action to execute successfully: 30 | * 1. A metadata account must already exist 31 | * 2. There must be exactly 1 editionMint token with 0 decimals outstanding 32 | * @return This action returns the resulting transaction id once it has been executed 33 | */ 34 | export const createMasterEdition = async ( 35 | { connection, wallet, editionMint, updateAuthority, maxSupply } = {} as CreateMasterEditionParams, 36 | ): Promise => { 37 | const metadata = await Metadata.getPDA(editionMint); 38 | const edition = await MasterEdition.getPDA(editionMint); 39 | 40 | const createMetadataTx = new CreateMasterEdition( 41 | { feePayer: wallet.publicKey }, 42 | { 43 | edition, 44 | metadata, 45 | updateAuthority: updateAuthority ?? wallet.publicKey, 46 | mint: editionMint, 47 | mintAuthority: wallet.publicKey, 48 | maxSupply, 49 | }, 50 | ); 51 | return sendTransaction({ 52 | connection, 53 | signers: [], 54 | txs: [createMetadataTx], 55 | wallet, 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /src/actions/createMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { 4 | CreateMetadata, 5 | Metadata, 6 | MetadataDataData, 7 | } from '@metaplex-foundation/mpl-token-metadata'; 8 | import { sendTransaction } from './transactions'; 9 | 10 | /** 11 | * Parameters for {@link createMetadata} 12 | */ 13 | export interface CreateMetadataParams { 14 | connection: Connection; 15 | /** Will be used as the fee payer, mint authority and a default update authority if {@link updateAuthority} isn't specified **/ 16 | wallet: Wallet; 17 | /** Can be any mint with 0 decimals **/ 18 | editionMint: PublicKey; 19 | metadataData: MetadataDataData; 20 | /** 21 | * You can optionally specify an updateAuthority different from the provided {@link wallet} 22 | * @default The updateAuthority will be set to the provided {@link wallet} address if not otherwise specified. 23 | **/ 24 | updateAuthority?: PublicKey; 25 | } 26 | 27 | /** 28 | * Creates a Metadata program account. This action is used in {@link mintNFT}. 29 | * @return This action returns the resulting transaction id once it has been executed 30 | */ 31 | export const createMetadata = async ( 32 | { connection, wallet, editionMint, metadataData, updateAuthority } = {} as CreateMetadataParams, 33 | ): Promise => { 34 | const metadata = await Metadata.getPDA(editionMint); 35 | 36 | const createMetadataTx = new CreateMetadata( 37 | { feePayer: wallet.publicKey }, 38 | { 39 | metadata, 40 | metadataData, 41 | updateAuthority: updateAuthority ?? wallet.publicKey, 42 | mint: editionMint, 43 | mintAuthority: wallet.publicKey, 44 | }, 45 | ); 46 | return sendTransaction({ 47 | connection, 48 | signers: [], 49 | txs: [createMetadataTx], 50 | wallet, 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addTokensToVault'; 2 | export * from './transactions'; 3 | export * from './initStore'; 4 | export * from './initStoreV2'; 5 | export * from './mintNFT'; 6 | export * from './mintEditionFromMaster'; 7 | export * from './createMetadata'; 8 | export * from './createMasterEdition'; 9 | export * from './signMetadata'; 10 | export * from './updateMetadata'; 11 | export * from './cancelBid'; 12 | export * from './placeBid'; 13 | export * from './redeemFullRightsTransferBid'; 14 | export * from './redeemPrintingV2Bid'; 15 | export * from './redeemParticipationBidV3'; 16 | export * from './claimBid'; 17 | export * from './instantSale'; 18 | export * from './burnToken'; 19 | export * from './sendToken'; 20 | export * from './shared'; 21 | export * from './utility'; 22 | -------------------------------------------------------------------------------- /src/actions/initStore.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { sendTransaction } from './transactions'; 5 | import { SetStore, Store } from '@metaplex-foundation/mpl-metaplex'; 6 | 7 | /** Parameters for {@link initStore} **/ 8 | export interface InitStoreParams { 9 | connection: Connection; 10 | /** Administrator wallet for the store **/ 11 | wallet: Wallet; 12 | /** 13 | * - `true`: anyone can list on the store 14 | * - `false`: only [whitelisted creators](https://docs.metaplex.com/architecture/deep_dive/metaplex#whitelistedcreator) can list on the store 15 | **/ 16 | isPublic?: boolean; 17 | } 18 | 19 | export interface InitStoreResponse { 20 | storeId: PublicKey; 21 | txId: string; 22 | } 23 | 24 | /** 25 | * Initialize a {@link Store} account. 26 | * This action will get a {@link Store} program derived account address for the provided wallet and initialize a store with that address, setting the given `wallet` as the admin 27 | */ 28 | export const initStore = async ({ 29 | connection, 30 | wallet, 31 | isPublic = true, 32 | }: InitStoreParams): Promise => { 33 | const storeId = await Store.getPDA(wallet.publicKey); 34 | const tx = new SetStore( 35 | { feePayer: wallet.publicKey }, 36 | { 37 | admin: new PublicKey(wallet.publicKey), 38 | store: storeId, 39 | isPublic, 40 | }, 41 | ); 42 | 43 | const txId = await sendTransaction({ connection, wallet, txs: [tx] }); 44 | 45 | return { storeId, txId }; 46 | }; 47 | -------------------------------------------------------------------------------- /src/actions/initStoreV2.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { sendTransaction } from './transactions'; 5 | import { SetStoreV2, Store, StoreConfig } from '@metaplex-foundation/mpl-metaplex'; 6 | 7 | /** Parameters for {@link initStoreV2} **/ 8 | export interface InitStoreV2Params { 9 | connection: Connection; 10 | /** Administrator wallet for the store **/ 11 | wallet: Wallet; 12 | /** 13 | * - `true`: anyone can list on the store 14 | * - `false`: only [whitelisted creators](https://docs.metaplex.com/architecture/deep_dive/metaplex#whitelistedcreator) can list on the store 15 | **/ 16 | isPublic?: boolean; 17 | settingsUri?: string | null; 18 | } 19 | 20 | export interface InitStoreV2Response { 21 | storeId: PublicKey; 22 | configId: PublicKey; 23 | txId: string; 24 | } 25 | 26 | /** 27 | * Initialize a {@link Store} account. 28 | * This action will get {@link Store} and {@link StoreConfig} program derived account addresses for the provided wallet and initialize a store, setting the given `wallet` as the admin 29 | */ 30 | export const initStoreV2 = async ({ 31 | connection, 32 | wallet, 33 | settingsUri = null, 34 | isPublic = true, 35 | }: InitStoreV2Params): Promise => { 36 | const storeId = await Store.getPDA(wallet.publicKey); 37 | const configId = await StoreConfig.getPDA(storeId); 38 | const tx = new SetStoreV2( 39 | { feePayer: wallet.publicKey }, 40 | { 41 | admin: new PublicKey(wallet.publicKey), 42 | store: storeId, 43 | config: configId, 44 | isPublic, 45 | settingsUri, 46 | }, 47 | ); 48 | 49 | const txId = await sendTransaction({ connection, wallet, txs: [tx] }); 50 | 51 | return { storeId, configId, txId }; 52 | }; 53 | -------------------------------------------------------------------------------- /src/actions/instantSale.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, TransactionSignature } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { Auction, AuctionExtended } from '@metaplex-foundation/mpl-auction'; 5 | import { 6 | AuctionManager, 7 | SafetyDepositConfig, 8 | WinningConfigType, 9 | } from '@metaplex-foundation/mpl-metaplex'; 10 | import { placeBid } from './placeBid'; 11 | import { claimBid } from './claimBid'; 12 | import { Vault } from '@metaplex-foundation/mpl-token-vault'; 13 | import { redeemFullRightsTransferBid } from './redeemFullRightsTransferBid'; 14 | import { redeemPrintingV2Bid } from './redeemPrintingV2Bid'; 15 | import { 16 | isEligibleForParticipationPrize, 17 | redeemParticipationBidV3, 18 | } from './redeemParticipationBidV3'; 19 | import { cancelBid } from './cancelBid'; 20 | 21 | export interface InstantSaleParams { 22 | connection: Connection; 23 | wallet: Wallet; 24 | auction: PublicKey; 25 | store: PublicKey; 26 | } 27 | 28 | export interface InstantSaleResponse { 29 | txIds: TransactionSignature[]; 30 | } 31 | 32 | export const instantSale = async ({ 33 | connection, 34 | wallet, 35 | store, 36 | auction, 37 | }: InstantSaleParams): Promise => { 38 | const txIds = []; 39 | // get data for transactions 40 | const auctionManagerPDA = await AuctionManager.getPDA(auction); 41 | const manager = await AuctionManager.load(connection, auctionManagerPDA); 42 | const vault = await Vault.load(connection, manager.data.vault); 43 | const auctionExtendedPDA = await AuctionExtended.getPDA(vault.pubkey); 44 | const { 45 | data: { instantSalePrice }, 46 | } = await AuctionExtended.load(connection, auctionExtendedPDA); 47 | const [safetyDepositBox] = await vault.getSafetyDepositBoxes(connection); 48 | const safetyDepositConfigPDA = await SafetyDepositConfig.getPDA( 49 | auctionManagerPDA, 50 | safetyDepositBox.pubkey, 51 | ); 52 | const { 53 | data: { winningConfigType, participationConfig }, 54 | } = await SafetyDepositConfig.load(connection, safetyDepositConfigPDA); 55 | //// 56 | 57 | const { txId: placeBidTxId, bidderPotToken } = await placeBid({ 58 | connection, 59 | wallet, 60 | amount: instantSalePrice, 61 | auction, 62 | }); 63 | txIds.push(placeBidTxId); 64 | 65 | // wait for all accounts to be created 66 | await connection.confirmTransaction(placeBidTxId, 'finalized'); 67 | 68 | const { 69 | data: { bidState }, 70 | } = await Auction.load(connection, auction); 71 | const winIndex = bidState.getWinnerIndex(wallet.publicKey.toBase58()); 72 | const hasWinner = winIndex !== null; 73 | 74 | // NOTE: it's divided into several transactions since transaction size is restricted 75 | if (hasWinner) { 76 | switch (winningConfigType) { 77 | case WinningConfigType.FullRightsTransfer: { 78 | const { txId } = await redeemFullRightsTransferBid({ connection, wallet, store, auction }); 79 | txIds.push(txId); 80 | break; 81 | } 82 | case WinningConfigType.PrintingV2: { 83 | const { txId } = await redeemPrintingV2Bid({ connection, wallet, store, auction }); 84 | txIds.push(txId); 85 | break; 86 | } 87 | default: 88 | throw new Error(`${winningConfigType} winning type isn't supported yet`); 89 | } 90 | 91 | const { txId: claimBidTxId } = await claimBid({ 92 | connection, 93 | wallet, 94 | store, 95 | auction, 96 | bidderPotToken, 97 | }); 98 | txIds.push(claimBidTxId); 99 | } else { 100 | // if user didn't win, user must have a bid we can refund before we check for open editions 101 | const { txId } = await cancelBid({ connection, wallet, auction, bidderPotToken }); 102 | txIds.push(txId); 103 | } 104 | 105 | const hasWonParticipationPrize = isEligibleForParticipationPrize(winIndex, participationConfig); 106 | if (hasWonParticipationPrize) { 107 | const { txIds } = await redeemParticipationBidV3({ connection, wallet, store, auction }); 108 | txIds.push(...txIds); 109 | } 110 | 111 | return { txIds: txIds }; 112 | }; 113 | -------------------------------------------------------------------------------- /src/actions/mintEditionFromMaster.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Wallet } from '../wallet'; 4 | import { 5 | Edition, 6 | EditionMarker, 7 | MasterEdition, 8 | Metadata, 9 | MintNewEditionFromMasterEditionViaToken, 10 | } from '@metaplex-foundation/mpl-token-metadata'; 11 | import { Account } from '@metaplex-foundation/mpl-core'; 12 | import BN from 'bn.js'; 13 | import { prepareTokenAccountAndMintTxs } from './shared'; 14 | import { sendTransaction } from './transactions'; 15 | 16 | export interface MintEditionFromMasterParams { 17 | connection: Connection; 18 | wallet: Wallet; 19 | masterEditionMint: PublicKey; 20 | updateAuthority?: PublicKey; 21 | } 22 | 23 | export interface MintEditionFromMasterResponse { 24 | txId: string; 25 | mint: PublicKey; 26 | metadata: PublicKey; 27 | edition: PublicKey; 28 | } 29 | 30 | export const mintEditionFromMaster = async ( 31 | { connection, wallet, masterEditionMint, updateAuthority } = {} as MintEditionFromMasterParams, 32 | ): Promise => { 33 | const masterPDA = await MasterEdition.getPDA(masterEditionMint); 34 | const masterMetaPDA = await Metadata.getPDA(masterEditionMint); 35 | const masterInfo = await Account.getInfo(connection, masterPDA); 36 | const masterData = new MasterEdition(masterPDA, masterInfo).data; 37 | 38 | //take the current outstanding supply and increment by 1 39 | const editionValue = masterData.supply.add(new BN(1)); 40 | 41 | const { mint, createMintTx, createAssociatedTokenAccountTx, mintToTx } = 42 | await prepareTokenAccountAndMintTxs(connection, wallet.publicKey); 43 | 44 | const tokenAccount = await Token.getAssociatedTokenAddress( 45 | ASSOCIATED_TOKEN_PROGRAM_ID, 46 | TOKEN_PROGRAM_ID, 47 | masterEditionMint, 48 | wallet.publicKey, 49 | ); 50 | 51 | const metadataPDA = await Metadata.getPDA(mint.publicKey); 52 | const editionMarker = await EditionMarker.getPDA(masterEditionMint, editionValue); 53 | const editionPDA = await Edition.getPDA(mint.publicKey); 54 | 55 | const newEditionFromMasterTx = new MintNewEditionFromMasterEditionViaToken( 56 | { feePayer: wallet.publicKey }, 57 | { 58 | edition: editionPDA, //empty, created inside program 59 | metadata: metadataPDA, //empty, created inside program 60 | updateAuthority: updateAuthority ?? wallet.publicKey, 61 | mint: mint.publicKey, 62 | mintAuthority: wallet.publicKey, 63 | masterEdition: masterPDA, 64 | masterMetadata: masterMetaPDA, 65 | editionMarker, // empty if this is the 1st limited edition being created 66 | tokenOwner: wallet.publicKey, 67 | tokenAccount, 68 | editionValue, 69 | }, 70 | ); 71 | 72 | const txId = await sendTransaction({ 73 | connection, 74 | signers: [mint], 75 | txs: [createMintTx, createAssociatedTokenAccountTx, mintToTx, newEditionFromMasterTx], 76 | wallet, 77 | }); 78 | 79 | return { 80 | txId, 81 | mint: mint.publicKey, 82 | metadata: metadataPDA, 83 | edition: editionPDA, 84 | }; 85 | }; 86 | -------------------------------------------------------------------------------- /src/actions/mintNFT.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | import { Connection } from '../Connection'; 4 | import { 5 | CreateMasterEdition, 6 | CreateMetadata, 7 | Creator, 8 | MasterEdition, 9 | Metadata, 10 | MetadataDataData, 11 | } from '@metaplex-foundation/mpl-token-metadata'; 12 | import { Wallet } from '../wallet'; 13 | import { sendTransaction } from './transactions'; 14 | import { lookup } from '../utils/metadata'; 15 | import { prepareTokenAccountAndMintTxs } from './shared'; 16 | 17 | /** Parameters for {@link mintNFT} **/ 18 | export interface MintNFTParams { 19 | connection: Connection; 20 | /** Wallet of the NFT creator and fee payer for the minting action **/ 21 | wallet: Wallet; 22 | /** URI for a json file compatible with the {@link MetadataJson} format. Note that the `properties` field has to contain at least one creator and one of the provided creators must have the same public key as the provided {@link wallet} **/ 23 | uri: string; 24 | /** Maximum supply of limited edition prints that can be created from the master edition of the minted NFT **/ 25 | maxSupply?: number; 26 | } 27 | 28 | export interface MintNFTResponse { 29 | txId: string; 30 | mint: PublicKey; 31 | metadata: PublicKey; 32 | edition: PublicKey; 33 | } 34 | 35 | export const mintNFT = async ({ 36 | connection, 37 | wallet, 38 | uri, 39 | maxSupply, 40 | }: MintNFTParams): Promise => { 41 | const { mint, createMintTx, createAssociatedTokenAccountTx, mintToTx } = 42 | await prepareTokenAccountAndMintTxs(connection, wallet.publicKey); 43 | 44 | const metadataPDA = await Metadata.getPDA(mint.publicKey); 45 | const editionPDA = await MasterEdition.getPDA(mint.publicKey); 46 | 47 | const { 48 | name, 49 | symbol, 50 | seller_fee_basis_points, 51 | properties: { creators }, 52 | } = await lookup(uri); 53 | 54 | const creatorsData = creators.reduce((memo, { address, share }) => { 55 | const verified = address === wallet.publicKey.toString(); 56 | 57 | const creator = new Creator({ 58 | address, 59 | share, 60 | verified, 61 | }); 62 | 63 | memo = [...memo, creator]; 64 | 65 | return memo; 66 | }, []); 67 | 68 | const metadataData = new MetadataDataData({ 69 | name, 70 | symbol, 71 | uri, 72 | sellerFeeBasisPoints: seller_fee_basis_points, 73 | creators: creatorsData, 74 | }); 75 | 76 | const createMetadataTx = new CreateMetadata( 77 | { 78 | feePayer: wallet.publicKey, 79 | }, 80 | { 81 | metadata: metadataPDA, 82 | metadataData, 83 | updateAuthority: wallet.publicKey, 84 | mint: mint.publicKey, 85 | mintAuthority: wallet.publicKey, 86 | }, 87 | ); 88 | 89 | const masterEditionTx = new CreateMasterEdition( 90 | { feePayer: wallet.publicKey }, 91 | { 92 | edition: editionPDA, 93 | metadata: metadataPDA, 94 | updateAuthority: wallet.publicKey, 95 | mint: mint.publicKey, 96 | mintAuthority: wallet.publicKey, 97 | maxSupply: maxSupply || maxSupply === 0 ? new BN(maxSupply) : null, 98 | }, 99 | ); 100 | 101 | const txId = await sendTransaction({ 102 | connection, 103 | signers: [mint], 104 | txs: [ 105 | createMintTx, 106 | createMetadataTx, 107 | createAssociatedTokenAccountTx, 108 | mintToTx, 109 | masterEditionTx, 110 | ], 111 | wallet, 112 | }); 113 | 114 | return { 115 | txId, 116 | mint: mint.publicKey, 117 | metadata: metadataPDA, 118 | edition: editionPDA, 119 | }; 120 | }; 121 | -------------------------------------------------------------------------------- /src/actions/placeBid.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { Commitment, Keypair, PublicKey, TransactionSignature } from '@solana/web3.js'; 3 | import { AccountLayout } from '@solana/spl-token'; 4 | import { Wallet } from '../wallet'; 5 | import { Connection } from '../Connection'; 6 | import { sendTransaction } from './transactions'; 7 | import { 8 | AuctionExtended, 9 | BidderMetadata, 10 | BidderPot, 11 | PlaceBid, 12 | } from '@metaplex-foundation/mpl-auction'; 13 | import { AuctionManager } from '@metaplex-foundation/mpl-metaplex'; 14 | import { TransactionsBatch } from '../utils/transactions-batch'; 15 | import { getCancelBidTransactions } from './cancelBid'; 16 | import { CreateTokenAccount } from '../transactions'; 17 | import { createApproveTxs, createWrappedAccountTxs } from './shared'; 18 | 19 | /** 20 | * Parameters for {@link placeBid} 21 | */ 22 | export interface PlaceBidParams { 23 | connection: Connection; 24 | /** The wallet from which tokens will be taken and transferred to the {@link bidderPotToken} account **/ 25 | wallet: Wallet; 26 | /** The {@link Auction} program account address for the bid **/ 27 | auction: PublicKey; 28 | /** Associated token account for the bidder pot **/ 29 | bidderPotToken?: PublicKey; 30 | /** Amount of tokens (accounting for decimals) or lamports to bid. One important nuance to remember is that each token mint has a different amount of decimals, which need to be accounted while specifying the amount. For instance, to send 1 token with a 0 decimal mint you would provide `1` as the amount, but for a token mint with 6 decimals you would provide `1000000` as the amount to transfer one whole token **/ 31 | amount: BN; 32 | commitment?: Commitment; 33 | } 34 | 35 | export interface PlaceBidResponse { 36 | txId: TransactionSignature; 37 | bidderPotToken: PublicKey; 38 | bidderMeta: PublicKey; 39 | } 40 | 41 | /** 42 | * Place a bid by taking it from the provided wallet and placing it in the bidder pot account. 43 | */ 44 | export const placeBid = async ({ 45 | connection, 46 | wallet, 47 | amount, 48 | auction, 49 | bidderPotToken, 50 | }: PlaceBidParams): Promise => { 51 | // get data for transactions 52 | const bidder = wallet.publicKey; 53 | const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 54 | const auctionManager = await AuctionManager.getPDA(auction); 55 | const manager = await AuctionManager.load(connection, auctionManager); 56 | const { 57 | data: { tokenMint }, 58 | } = await manager.getAuction(connection); 59 | const auctionTokenMint = new PublicKey(tokenMint); 60 | const vault = new PublicKey(manager.data.vault); 61 | const auctionExtended = await AuctionExtended.getPDA(vault); 62 | const bidderPot = await BidderPot.getPDA(auction, bidder); 63 | const bidderMeta = await BidderMetadata.getPDA(auction, bidder); 64 | //// 65 | 66 | let txBatch = new TransactionsBatch({ transactions: [] }); 67 | 68 | if (bidderPotToken) { 69 | // cancel prev bid 70 | txBatch = await getCancelBidTransactions({ 71 | destAccount: null, 72 | bidder, 73 | accountRentExempt, 74 | bidderPot, 75 | bidderPotToken, 76 | bidderMeta, 77 | auction, 78 | auctionExtended, 79 | auctionTokenMint, 80 | vault, 81 | }); 82 | //// 83 | } else { 84 | // create a new account for bid 85 | const account = Keypair.generate(); 86 | const createBidderPotTransaction = new CreateTokenAccount( 87 | { feePayer: bidder }, 88 | { 89 | newAccountPubkey: account.publicKey, 90 | lamports: accountRentExempt, 91 | mint: auctionTokenMint, 92 | owner: auction, 93 | }, 94 | ); 95 | txBatch.addSigner(account); 96 | txBatch.addTransaction(createBidderPotTransaction); 97 | bidderPotToken = account.publicKey; 98 | //// 99 | } 100 | 101 | // create paying account 102 | const { 103 | account: payingAccount, 104 | createTokenAccountTx, 105 | closeTokenAccountTx, 106 | } = await createWrappedAccountTxs(connection, bidder, amount.toNumber() + accountRentExempt * 2); 107 | txBatch.addTransaction(createTokenAccountTx); 108 | txBatch.addAfterTransaction(closeTokenAccountTx); 109 | txBatch.addSigner(payingAccount); 110 | //// 111 | 112 | // transfer authority 113 | const { 114 | authority: transferAuthority, 115 | createApproveTx, 116 | createRevokeTx, 117 | } = createApproveTxs({ 118 | account: payingAccount.publicKey, 119 | owner: bidder, 120 | amount: amount.toNumber(), 121 | }); 122 | txBatch.addTransaction(createApproveTx); 123 | txBatch.addAfterTransaction(createRevokeTx); 124 | txBatch.addSigner(transferAuthority); 125 | //// 126 | 127 | // create place bid transaction 128 | const placeBidTransaction = new PlaceBid( 129 | { feePayer: bidder }, 130 | { 131 | bidder, 132 | bidderToken: payingAccount.publicKey, 133 | bidderPot, 134 | bidderPotToken, 135 | bidderMeta, 136 | auction, 137 | auctionExtended, 138 | tokenMint: auctionTokenMint, 139 | transferAuthority: transferAuthority.publicKey, 140 | amount, 141 | resource: vault, 142 | }, 143 | ); 144 | txBatch.addTransaction(placeBidTransaction); 145 | //// 146 | 147 | const txId = await sendTransaction({ 148 | connection, 149 | wallet, 150 | txs: txBatch.toTransactions(), 151 | signers: txBatch.signers, 152 | }); 153 | 154 | return { txId, bidderPotToken, bidderMeta }; 155 | }; 156 | -------------------------------------------------------------------------------- /src/actions/redeemFullRightsTransferBid.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from '@solana/web3.js'; 2 | import { AccountLayout } from '@solana/spl-token'; 3 | import { Wallet } from '../wallet'; 4 | import { Connection } from '../Connection'; 5 | import { sendTransaction } from './transactions'; 6 | import { AuctionExtended, BidderMetadata } from '@metaplex-foundation/mpl-auction'; 7 | import { TransactionsBatch } from '../utils/transactions-batch'; 8 | import { 9 | AuctionManager, 10 | MetaplexProgram, 11 | RedeemFullRightsTransferBid, 12 | SafetyDepositConfig, 13 | } from '@metaplex-foundation/mpl-metaplex'; 14 | import { CreateTokenAccount } from '../transactions'; 15 | import { Vault } from '@metaplex-foundation/mpl-token-vault'; 16 | import { 17 | Metadata, 18 | UpdatePrimarySaleHappenedViaToken, 19 | } from '@metaplex-foundation/mpl-token-metadata'; 20 | 21 | export interface RedeemFullRightsTransferBidParams { 22 | connection: Connection; 23 | wallet: Wallet; 24 | auction: PublicKey; 25 | store: PublicKey; 26 | } 27 | 28 | export interface RedeemFullRightsTransferBidResponse { 29 | txId: string; 30 | } 31 | 32 | export const redeemFullRightsTransferBid = async ({ 33 | connection, 34 | wallet, 35 | store, 36 | auction, 37 | }: RedeemFullRightsTransferBidParams): Promise => { 38 | // get data for transactions 39 | const bidder = wallet.publicKey; 40 | const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 41 | const auctionManager = await AuctionManager.getPDA(auction); 42 | const manager = await AuctionManager.load(connection, auctionManager); 43 | const vault = await Vault.load(connection, manager.data.vault); 44 | const fractionMint = new PublicKey(vault.data.fractionMint); 45 | const auctionExtended = await AuctionExtended.getPDA(vault.pubkey); 46 | // assuming we have 1 item 47 | const [safetyDepositBox] = await vault.getSafetyDepositBoxes(connection); 48 | const tokenMint = new PublicKey(safetyDepositBox.data.tokenMint); 49 | const safetyDepositTokenStore = new PublicKey(safetyDepositBox.data.store); 50 | const bidderMeta = await BidderMetadata.getPDA(auction, bidder); 51 | const bidRedemption = await getBidRedemptionPDA(auction, bidderMeta); 52 | const safetyDepositConfig = await SafetyDepositConfig.getPDA( 53 | auctionManager, 54 | safetyDepositBox.pubkey, 55 | ); 56 | const transferAuthority = await Vault.getPDA(vault.pubkey); 57 | const metadata = await Metadata.getPDA(tokenMint); 58 | //// 59 | 60 | const txBatch = await getRedeemFRTBidTransactions({ 61 | accountRentExempt, 62 | tokenMint, 63 | bidder, 64 | bidderMeta, 65 | store, 66 | vault: vault.pubkey, 67 | auction, 68 | auctionExtended, 69 | auctionManager, 70 | fractionMint, 71 | safetyDepositTokenStore, 72 | safetyDeposit: safetyDepositBox.pubkey, 73 | bidRedemption, 74 | safetyDepositConfig, 75 | transferAuthority, 76 | metadata, 77 | }); 78 | 79 | const txId = await sendTransaction({ 80 | connection, 81 | wallet, 82 | txs: txBatch.toTransactions(), 83 | signers: txBatch.signers, 84 | }); 85 | 86 | return { txId }; 87 | }; 88 | 89 | interface RedeemFRTBidTransactionsParams { 90 | bidder: PublicKey; 91 | accountRentExempt: number; 92 | bidderPotToken?: PublicKey; 93 | bidderMeta: PublicKey; 94 | auction: PublicKey; 95 | auctionExtended: PublicKey; 96 | tokenMint: PublicKey; 97 | vault: PublicKey; 98 | store: PublicKey; 99 | auctionManager: PublicKey; 100 | bidRedemption: PublicKey; 101 | safetyDepositTokenStore: PublicKey; 102 | safetyDeposit: PublicKey; 103 | fractionMint: PublicKey; 104 | safetyDepositConfig: PublicKey; 105 | transferAuthority: PublicKey; 106 | metadata: PublicKey; 107 | } 108 | 109 | export const getRedeemFRTBidTransactions = async ({ 110 | accountRentExempt, 111 | bidder, 112 | tokenMint, 113 | store, 114 | vault, 115 | auction, 116 | auctionManager, 117 | auctionExtended, 118 | bidRedemption, 119 | bidderMeta: bidMetadata, 120 | safetyDepositTokenStore, 121 | safetyDeposit, 122 | fractionMint, 123 | safetyDepositConfig, 124 | transferAuthority, 125 | metadata, 126 | }: RedeemFRTBidTransactionsParams) => { 127 | const txBatch = new TransactionsBatch({ transactions: [] }); 128 | 129 | // create a new account for redeeming 130 | const account = Keypair.generate(); 131 | const createDestinationTransaction = new CreateTokenAccount( 132 | { feePayer: bidder }, 133 | { 134 | newAccountPubkey: account.publicKey, 135 | lamports: accountRentExempt, 136 | mint: tokenMint, 137 | }, 138 | ); 139 | txBatch.addSigner(account); 140 | txBatch.addTransaction(createDestinationTransaction); 141 | //// 142 | 143 | // create redeem bid 144 | const redeemBidTransaction = new RedeemFullRightsTransferBid( 145 | { feePayer: bidder }, 146 | { 147 | store, 148 | vault, 149 | auction, 150 | auctionManager, 151 | bidRedemption, 152 | bidMetadata, 153 | safetyDepositTokenStore, 154 | destination: account.publicKey, 155 | safetyDeposit, 156 | fractionMint, 157 | bidder, 158 | safetyDepositConfig, 159 | auctionExtended, 160 | transferAuthority, 161 | newAuthority: bidder, 162 | masterMetadata: metadata, 163 | }, 164 | ); 165 | txBatch.addTransaction(redeemBidTransaction); 166 | //// 167 | 168 | // update primary sale happened via token 169 | const updatePrimarySaleHappenedViaTokenTransaction = new UpdatePrimarySaleHappenedViaToken( 170 | { feePayer: bidder }, 171 | { 172 | metadata, 173 | owner: bidder, 174 | tokenAccount: account.publicKey, 175 | }, 176 | ); 177 | txBatch.addTransaction(updatePrimarySaleHappenedViaTokenTransaction); 178 | //// 179 | 180 | return txBatch; 181 | }; 182 | 183 | export const getBidRedemptionPDA = async (auction: PublicKey, bidderMeta: PublicKey) => { 184 | return ( 185 | await PublicKey.findProgramAddress( 186 | [Buffer.from(MetaplexProgram.PREFIX), auction.toBuffer(), bidderMeta.toBuffer()], 187 | MetaplexProgram.PUBKEY, 188 | ) 189 | )[0]; 190 | }; 191 | -------------------------------------------------------------------------------- /src/actions/redeemParticipationBidV3.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { PublicKey, TransactionSignature } from '@solana/web3.js'; 3 | import { Wallet } from '../wallet'; 4 | import { Connection } from '../Connection'; 5 | import { sendTransaction } from './transactions'; 6 | import { TransactionsBatch } from '../utils/transactions-batch'; 7 | import { createApproveTxs, createWrappedAccountTxs, prepareTokenAccountAndMintTxs } from './shared'; 8 | import { getBidRedemptionPDA } from './redeemFullRightsTransferBid'; 9 | import { 10 | ASSOCIATED_TOKEN_PROGRAM_ID, 11 | NATIVE_MINT, 12 | Token, 13 | TOKEN_PROGRAM_ID, 14 | } from '@solana/spl-token'; 15 | import { Auction, AuctionExtended, BidderMetadata } from '@metaplex-foundation/mpl-auction'; 16 | import { Vault } from '@metaplex-foundation/mpl-token-vault'; 17 | import { 18 | AuctionManager, 19 | NonWinningConstraint, 20 | ParticipationConfigV2, 21 | PrizeTrackingTicket, 22 | RedeemParticipationBidV3, 23 | SafetyDepositConfig, 24 | WinningConstraint, 25 | } from '@metaplex-foundation/mpl-metaplex'; 26 | import { 27 | Edition, 28 | EditionMarker, 29 | MasterEdition, 30 | Metadata, 31 | UpdatePrimarySaleHappenedViaToken, 32 | } from '@metaplex-foundation/mpl-token-metadata'; 33 | 34 | export interface RedeemParticipationBidV3Params { 35 | connection: Connection; 36 | wallet: Wallet; 37 | auction: PublicKey; 38 | store: PublicKey; 39 | } 40 | 41 | export interface RedeemParticipationBidV3Response { 42 | txIds: TransactionSignature[]; 43 | } 44 | 45 | export const redeemParticipationBidV3 = async ({ 46 | connection, 47 | wallet, 48 | store, 49 | auction, 50 | }: RedeemParticipationBidV3Params): Promise => { 51 | const txInitBatch = new TransactionsBatch({ transactions: [] }); 52 | const txMainBatch = new TransactionsBatch({ transactions: [] }); 53 | 54 | const bidder = wallet.publicKey; 55 | const { 56 | data: { bidState, tokenMint: auctionTokenMint }, 57 | } = await Auction.load(connection, auction); 58 | const auctionManagerPDA = await AuctionManager.getPDA(auction); 59 | const manager = await AuctionManager.load(connection, auctionManagerPDA); 60 | const vault = await Vault.load(connection, manager.data.vault); 61 | const auctionExtendedPDA = await AuctionExtended.getPDA(vault.pubkey); 62 | const [safetyDepositBox] = await vault.getSafetyDepositBoxes(connection); 63 | const originalMint = new PublicKey(safetyDepositBox.data.tokenMint); 64 | 65 | const safetyDepositTokenStore = new PublicKey(safetyDepositBox.data.store); 66 | const bidderMetaPDA = await BidderMetadata.getPDA(auction, bidder); 67 | const bidRedemptionPDA = await getBidRedemptionPDA(auction, bidderMetaPDA); 68 | const safetyDepositConfigPDA = await SafetyDepositConfig.getPDA( 69 | auctionManagerPDA, 70 | safetyDepositBox.pubkey, 71 | ); 72 | const { 73 | data: { 74 | participationConfig: { fixedPrice }, 75 | }, 76 | } = await SafetyDepositConfig.load(connection, safetyDepositConfigPDA); 77 | const acceptPaymentAccount = new PublicKey(manager.data.acceptPayment); 78 | 79 | const { mint, createMintTx, createAssociatedTokenAccountTx, mintToTx, recipient } = 80 | await prepareTokenAccountAndMintTxs(connection, wallet.publicKey); 81 | 82 | txInitBatch.addSigner(mint); 83 | txInitBatch.addTransaction(createMintTx); 84 | txInitBatch.addTransaction(createAssociatedTokenAccountTx); 85 | txInitBatch.addTransaction(mintToTx); 86 | 87 | const newMint = mint.publicKey; 88 | const newMetadataPDA = await Metadata.getPDA(newMint); 89 | const newEditionPDA = await Edition.getPDA(newMint); 90 | 91 | const metadataPDA = await Metadata.getPDA(originalMint); 92 | const masterEditionPDA = await MasterEdition.getPDA(originalMint); 93 | const masterEdition = await MasterEdition.load(connection, masterEditionPDA); 94 | 95 | const prizeTrackingTicketPDA = await PrizeTrackingTicket.getPDA(auctionManagerPDA, originalMint); 96 | const winIndex = bidState.getWinnerIndex(bidder.toBase58()); 97 | 98 | const desiredEdition = masterEdition.data.supply.add(new BN(1)); 99 | const editionMarkerPDA = await EditionMarker.getPDA(originalMint, desiredEdition); 100 | 101 | let tokenPaymentAccount: PublicKey; 102 | if (auctionTokenMint === NATIVE_MINT.toBase58()) { 103 | const { account, createTokenAccountTx, closeTokenAccountTx } = await createWrappedAccountTxs( 104 | connection, 105 | bidder, 106 | fixedPrice.toNumber(), 107 | ); 108 | tokenPaymentAccount = account.publicKey; 109 | txInitBatch.addTransaction(createTokenAccountTx); 110 | txInitBatch.addSigner(account); 111 | txMainBatch.addAfterTransaction(closeTokenAccountTx); 112 | } else { 113 | // TODO: find out what will happen if currency is not WSOL 114 | tokenPaymentAccount = await Token.getAssociatedTokenAddress( 115 | ASSOCIATED_TOKEN_PROGRAM_ID, 116 | TOKEN_PROGRAM_ID, 117 | new PublicKey(auctionTokenMint), 118 | bidder, 119 | ); 120 | } 121 | 122 | const { authority, createApproveTx, createRevokeTx } = createApproveTxs({ 123 | account: tokenPaymentAccount, 124 | owner: bidder, 125 | amount: fixedPrice.toNumber(), 126 | }); 127 | txMainBatch.addTransaction(createApproveTx); 128 | txMainBatch.addAfterTransaction(createRevokeTx); 129 | txMainBatch.addSigner(authority); 130 | 131 | const redeemParticipationBidV3Tx = new RedeemParticipationBidV3( 132 | { feePayer: bidder }, 133 | { 134 | store, 135 | vault: vault.pubkey, 136 | auction, 137 | auctionManager: auctionManagerPDA, 138 | bidRedemption: bidRedemptionPDA, 139 | bidMetadata: bidderMetaPDA, 140 | safetyDepositTokenStore, 141 | destination: recipient, 142 | safetyDeposit: safetyDepositBox.pubkey, 143 | bidder, 144 | safetyDepositConfig: safetyDepositConfigPDA, 145 | auctionExtended: auctionExtendedPDA, 146 | newMint, 147 | newEdition: newEditionPDA, 148 | newMetadata: newMetadataPDA, 149 | metadata: metadataPDA, 150 | masterEdition: masterEditionPDA, 151 | editionMark: editionMarkerPDA, 152 | prizeTrackingTicket: prizeTrackingTicketPDA, 153 | winIndex: winIndex !== null ? new BN(winIndex) : null, 154 | transferAuthority: authority.publicKey, 155 | tokenPaymentAccount, 156 | acceptPaymentAccount, 157 | }, 158 | ); 159 | txMainBatch.addTransaction(redeemParticipationBidV3Tx); 160 | 161 | const updatePrimarySaleHappenedViaTokenTx = new UpdatePrimarySaleHappenedViaToken( 162 | { feePayer: bidder }, 163 | { 164 | metadata: newMetadataPDA, 165 | owner: bidder, 166 | tokenAccount: recipient, 167 | }, 168 | ); 169 | txMainBatch.addTransaction(updatePrimarySaleHappenedViaTokenTx); 170 | 171 | const initTxId = await sendTransaction({ 172 | connection, 173 | wallet, 174 | txs: txInitBatch.toTransactions(), 175 | signers: txInitBatch.signers, 176 | }); 177 | 178 | // wait for all accounts to be created 179 | await connection.confirmTransaction(initTxId, 'finalized'); 180 | 181 | const mainTxId = await sendTransaction({ 182 | connection, 183 | wallet, 184 | txs: txMainBatch.toTransactions(), 185 | signers: txMainBatch.signers, 186 | }); 187 | 188 | return { txIds: [initTxId, mainTxId] }; 189 | }; 190 | 191 | export function isEligibleForParticipationPrize( 192 | winIndex: number, 193 | { nonWinningConstraint, winnerConstraint }: ParticipationConfigV2 = {} as ParticipationConfigV2, 194 | ) { 195 | const noWinnerConstraints = winnerConstraint !== WinningConstraint.NoParticipationPrize; 196 | const noNonWinnerConstraints = nonWinningConstraint !== NonWinningConstraint.NoParticipationPrize; 197 | return ( 198 | (winIndex === null && noNonWinnerConstraints) || (winIndex !== null && noWinnerConstraints) 199 | ); 200 | } 201 | -------------------------------------------------------------------------------- /src/actions/sendToken.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID, u64 } from '@solana/spl-token'; 3 | import { Wallet } from '../wallet'; 4 | import { Connection } from '../Connection'; 5 | import { sendTransaction } from './transactions'; 6 | import { Account, Transaction } from '@metaplex-foundation/mpl-core'; 7 | import { CreateAssociatedTokenAccount } from '../transactions/CreateAssociatedTokenAccount'; 8 | 9 | /** Parameters for {@link sendToken} **/ 10 | export interface SendTokenParams { 11 | connection: Connection; 12 | /** Source wallet address **/ 13 | wallet: Wallet; 14 | /** Source wallet's associated token account address **/ 15 | source: PublicKey; 16 | /** Destination wallet address **/ 17 | destination: PublicKey; 18 | /** Mint address of the tokento transfer **/ 19 | mint: PublicKey; 20 | /** Amount of tokens to transfer. One important nuance to remember is that each token mint has a different amount of decimals, which need to be accounted while specifying the amount. For instance, to send 1 token with a 0 decimal mint you would provide `1` as the amount, but for a token mint with 6 decimals you would provide `1000000` as the amount to transfer one whole token **/ 21 | amount: number | u64; 22 | } 23 | 24 | export interface SendTokenResponse { 25 | txId: string; 26 | } 27 | 28 | /** 29 | * Send a token to another account. 30 | * 31 | * This action will do the following: 32 | * 1. Check if the destination account has an associated token account for the SPL token at hand 33 | * 2. If the associated token account doesn't exist, it will be created 34 | * 3. The token will be transferred to the associated token account 35 | * 36 | * Please take into account that creating an account will [automatically allocate lamports for rent exemption](https://docs.solana.com/implemented-proposals/rent) which will make it a very expensive instruction to run in bulk 37 | */ 38 | export const sendToken = async ({ 39 | connection, 40 | wallet, 41 | source, 42 | destination, 43 | mint, 44 | amount, 45 | }: SendTokenParams): Promise => { 46 | const txs = []; 47 | const destAta = await Token.getAssociatedTokenAddress( 48 | ASSOCIATED_TOKEN_PROGRAM_ID, 49 | TOKEN_PROGRAM_ID, 50 | mint, 51 | destination, 52 | ); 53 | const transactionCtorFields = { 54 | feePayer: wallet.publicKey, 55 | }; 56 | 57 | try { 58 | // check if the account exists 59 | await Account.load(connection, destAta); 60 | } catch { 61 | txs.push( 62 | new CreateAssociatedTokenAccount(transactionCtorFields, { 63 | associatedTokenAddress: destAta, 64 | splTokenMintAddress: mint, 65 | walletAddress: destination, 66 | }), 67 | ); 68 | } 69 | 70 | txs.push( 71 | new Transaction(transactionCtorFields).add( 72 | Token.createTransferInstruction( 73 | TOKEN_PROGRAM_ID, 74 | source, 75 | destAta, 76 | wallet.publicKey, 77 | [], 78 | amount, 79 | ), 80 | ), 81 | ); 82 | 83 | const txId = await sendTransaction({ connection, wallet, txs }); 84 | 85 | return { txId }; 86 | }; 87 | -------------------------------------------------------------------------------- /src/actions/shared/approve.ts: -------------------------------------------------------------------------------- 1 | import { Token, TOKEN_PROGRAM_ID, u64 } from '@solana/spl-token'; 2 | import { Keypair, PublicKey, Transaction } from '@solana/web3.js'; 3 | import { Optional } from '../../types'; 4 | 5 | interface CreateApproveParams { 6 | authority: Keypair; 7 | account: PublicKey; 8 | owner: PublicKey; 9 | amount: number | u64; 10 | } 11 | 12 | export function createApproveTxs(args: Optional) { 13 | const { authority = Keypair.generate(), account, owner, amount } = args; 14 | 15 | const createApproveTx = new Transaction().add( 16 | Token.createApproveInstruction( 17 | TOKEN_PROGRAM_ID, 18 | account, 19 | authority.publicKey, 20 | owner, 21 | [], 22 | amount, 23 | ), 24 | ); 25 | const createRevokeTx = new Transaction().add( 26 | Token.createRevokeInstruction(TOKEN_PROGRAM_ID, account, owner, []), 27 | ); 28 | return { authority, createApproveTx, createRevokeTx }; 29 | } 30 | -------------------------------------------------------------------------------- /src/actions/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mint'; 2 | export * from './wrapped-account'; 3 | export * from './approve'; 4 | -------------------------------------------------------------------------------- /src/actions/shared/mint.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 2 | import { 3 | ASSOCIATED_TOKEN_PROGRAM_ID, 4 | MintLayout, 5 | Token, 6 | TOKEN_PROGRAM_ID, 7 | } from '@solana/spl-token'; 8 | import { CreateAssociatedTokenAccount, CreateMint, MintTo } from '../../transactions'; 9 | import { Transaction } from '@metaplex-foundation/mpl-core'; 10 | 11 | interface MintTxs { 12 | mint: Keypair; 13 | // recipient ATA 14 | recipient: PublicKey; 15 | createMintTx: Transaction; 16 | createAssociatedTokenAccountTx: Transaction; 17 | mintToTx: Transaction; 18 | } 19 | 20 | export async function prepareTokenAccountAndMintTxs( 21 | connection: Connection, 22 | owner: PublicKey, 23 | ): Promise { 24 | const mint = Keypair.generate(); 25 | const mintRent = await connection.getMinimumBalanceForRentExemption(MintLayout.span); 26 | const createMintTx = new CreateMint( 27 | { feePayer: owner }, 28 | { 29 | newAccountPubkey: mint.publicKey, 30 | lamports: mintRent, 31 | }, 32 | ); 33 | 34 | const recipient = await Token.getAssociatedTokenAddress( 35 | ASSOCIATED_TOKEN_PROGRAM_ID, 36 | TOKEN_PROGRAM_ID, 37 | mint.publicKey, 38 | owner, 39 | ); 40 | 41 | const createAssociatedTokenAccountTx = new CreateAssociatedTokenAccount( 42 | { feePayer: owner }, 43 | { 44 | associatedTokenAddress: recipient, 45 | splTokenMintAddress: mint.publicKey, 46 | }, 47 | ); 48 | 49 | const mintToTx = new MintTo( 50 | { feePayer: owner }, 51 | { 52 | mint: mint.publicKey, 53 | dest: recipient, 54 | amount: 1, 55 | }, 56 | ); 57 | 58 | return { mint, createMintTx, createAssociatedTokenAccountTx, mintToTx, recipient }; 59 | } 60 | -------------------------------------------------------------------------------- /src/actions/shared/wrapped-account.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { AccountLayout, NATIVE_MINT, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 4 | import { CreateTokenAccount } from '../../transactions/CreateTokenAccount'; 5 | 6 | interface WrappedAccountTxs { 7 | account: Keypair; 8 | createTokenAccountTx: Transaction; 9 | closeTokenAccountTx: Transaction; 10 | } 11 | 12 | export async function createWrappedAccountTxs( 13 | connection: Connection, 14 | owner: PublicKey, 15 | amount = 0, 16 | ): Promise { 17 | const account = Keypair.generate(); 18 | const accountRentExempt = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 19 | const createTokenAccountTx = new CreateTokenAccount( 20 | { feePayer: owner }, 21 | { 22 | newAccountPubkey: account.publicKey, 23 | lamports: amount + accountRentExempt, 24 | mint: NATIVE_MINT, 25 | }, 26 | ); 27 | const closeTokenAccountTx = new Transaction().add( 28 | Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, account.publicKey, owner, owner, []), 29 | ); 30 | return { account, createTokenAccountTx, closeTokenAccountTx }; 31 | } 32 | -------------------------------------------------------------------------------- /src/actions/signMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Metadata, SignMetadata } from '@metaplex-foundation/mpl-token-metadata'; 4 | import { sendTransaction } from './transactions'; 5 | 6 | /** 7 | * Parameters for {@link signMetadata} 8 | */ 9 | export interface SignMetadataParams { 10 | connection: Connection; 11 | /** Will be used as both the fee payer and {@link signer} if no separate {@link signer} was otherwise specified. If used as a signer, the wallet address must be included in the `creators` array. **/ 12 | wallet: Wallet; 13 | /** Mint address for the token associated with the {@link Metadata} account **/ 14 | editionMint: PublicKey; 15 | /** An optional signer. If specified, the signer address must be included in the `creators` array on the {@link Metadata} account data **/ 16 | signer?: Keypair; 17 | } 18 | 19 | /** 20 | * Sign a MetaData account that has the provided wallet as an unverified creator so that it is now verified. 21 | * @return This action returns the resulting transaction id once it has been executed 22 | */ 23 | export const signMetadata = async ( 24 | { connection, wallet, editionMint, signer } = {} as SignMetadataParams, 25 | ): Promise => { 26 | const metadata = await Metadata.getPDA(editionMint); 27 | const signTx = new SignMetadata( 28 | { feePayer: wallet.publicKey }, 29 | { 30 | metadata, 31 | creator: signer ? signer.publicKey : wallet.publicKey, 32 | }, 33 | ); 34 | return await sendTransaction({ 35 | connection, 36 | signers: signer ? [signer] : [], 37 | txs: [signTx], 38 | wallet, 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/actions/transactions.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, SendOptions } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { Connection } from '../Connection'; 4 | import { Transaction } from '@metaplex-foundation/mpl-core'; 5 | 6 | /** Parameters for {@link sendTransaction} **/ 7 | export interface SendTransactionParams { 8 | connection: Connection; 9 | wallet: Wallet; 10 | txs: Transaction[]; 11 | signers?: Keypair[]; 12 | options?: SendOptions; 13 | } 14 | 15 | /** 16 | * Sign and send transactions for validation 17 | * @return This action returns the resulting transaction id once it has been executed 18 | */ 19 | export const sendTransaction = async ({ 20 | connection, 21 | wallet, 22 | txs, 23 | signers = [], 24 | options, 25 | }: SendTransactionParams): Promise => { 26 | let tx = Transaction.fromCombined(txs, { feePayer: wallet.publicKey }); 27 | tx.recentBlockhash = (await connection.getRecentBlockhash()).blockhash; 28 | 29 | if (signers.length) { 30 | tx.partialSign(...signers); 31 | } 32 | tx = await wallet.signTransaction(tx); 33 | 34 | return connection.sendRawTransaction(tx.serialize(), options); 35 | }; 36 | -------------------------------------------------------------------------------- /src/actions/updateMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import { Wallet } from '../wallet'; 3 | import { 4 | Metadata, 5 | MetadataDataData, 6 | UpdateMetadata, 7 | } from '@metaplex-foundation/mpl-token-metadata'; 8 | import { sendTransaction } from './transactions'; 9 | 10 | /** Parameters for {@link updateMetadata} **/ 11 | export interface UpdateMetadataParams { 12 | connection: Connection; 13 | /** Must be the wallet of the current `updateAuthority` **/ 14 | wallet: Wallet; 15 | /** Mint address for the NFT token **/ 16 | editionMint: PublicKey; 17 | /** An optional new {@link MetadataDataData} object to replace the current data. This will completely overwrite the data so all fields must be set explicitly. **/ 18 | newMetadataData?: MetadataDataData; 19 | newUpdateAuthority?: PublicKey; 20 | /** This parameter can only be set to true once after which it can't be reverted to false **/ 21 | primarySaleHappened?: boolean; 22 | } 23 | 24 | /** 25 | * Can be used to update any of the following parameters: 26 | * 1. Data inside {@link Metadata} as long as it remains mutable (which is only possible for a {@link MasterEdition}) 27 | * 2. updateAuthority 28 | * 3. Whether the primary sale has happened (can only be set to true once after which it can't be reverted to false) 29 | */ 30 | export const updateMetadata = async ( 31 | { 32 | connection, 33 | wallet, 34 | editionMint, 35 | newMetadataData, 36 | newUpdateAuthority, 37 | primarySaleHappened, 38 | } = {} as UpdateMetadataParams, 39 | ): Promise => { 40 | const metadata = await Metadata.getPDA(editionMint); 41 | const updateTx = new UpdateMetadata( 42 | { feePayer: wallet.publicKey }, 43 | { 44 | metadata, 45 | updateAuthority: wallet.publicKey, 46 | metadataData: newMetadataData, 47 | newUpdateAuthority, 48 | primarySaleHappened, 49 | }, 50 | ); 51 | return sendTransaction({ 52 | connection, 53 | signers: [], 54 | txs: [updateTx], 55 | wallet, 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /src/actions/utility/closeVault.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { Transaction } from '@metaplex-foundation/mpl-core'; 3 | import { Keypair, PublicKey, TransactionSignature } from '@solana/web3.js'; 4 | import { AccountLayout, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 5 | import { ActivateVault, CombineVault, Vault } from '@metaplex-foundation/mpl-token-vault'; 6 | 7 | import { Wallet } from '../../wallet'; 8 | import { Connection } from '../../Connection'; 9 | import { sendTransaction } from '../transactions'; 10 | import { CreateTokenAccount } from '../../transactions'; 11 | import { TransactionsBatch } from '../../utils/transactions-batch'; 12 | 13 | interface CloseVaultParams { 14 | connection: Connection; 15 | wallet: Wallet; 16 | vault: PublicKey; 17 | priceMint: PublicKey; 18 | } 19 | 20 | interface CloseVaultResponse { 21 | txId: TransactionSignature; 22 | } 23 | 24 | // This command "closes" the vault, by activating & combining it in one go, handing it over to the auction manager 25 | // authority (that may or may not exist yet.) 26 | export const closeVault = async ({ 27 | connection, 28 | wallet, 29 | vault, 30 | priceMint, 31 | }: CloseVaultParams): Promise => { 32 | const accountRent = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 33 | 34 | const fractionMintAuthority = await Vault.getPDA(vault); 35 | 36 | const txBatch = new TransactionsBatch({ transactions: [] }); 37 | 38 | const txOptions = { feePayer: wallet.publicKey }; 39 | 40 | const { 41 | data: { fractionMint, fractionTreasury, redeemTreasury, pricingLookupAddress }, 42 | } = await Vault.load(connection, vault); 43 | 44 | const fractionMintKey = new PublicKey(fractionMint); 45 | const fractionTreasuryKey = new PublicKey(fractionTreasury); 46 | const redeemTreasuryKey = new PublicKey(redeemTreasury); 47 | const pricingLookupAddressKey = new PublicKey(pricingLookupAddress); 48 | 49 | const activateVaultTx = new ActivateVault(txOptions, { 50 | vault, 51 | numberOfShares: new BN(0), 52 | fractionMint: fractionMintKey, 53 | fractionTreasury: fractionTreasuryKey, 54 | fractionMintAuthority, 55 | vaultAuthority: wallet.publicKey, 56 | }); 57 | txBatch.addTransaction(activateVaultTx); 58 | 59 | const outstandingShareAccount = Keypair.generate(); 60 | const outstandingShareAccountTx = new CreateTokenAccount(txOptions, { 61 | newAccountPubkey: outstandingShareAccount.publicKey, 62 | lamports: accountRent, 63 | mint: fractionMintKey, 64 | owner: wallet.publicKey, 65 | }); 66 | txBatch.addTransaction(outstandingShareAccountTx); 67 | txBatch.addSigner(outstandingShareAccount); 68 | 69 | const payingTokenAccount = Keypair.generate(); 70 | const payingTokenAccountTx = new CreateTokenAccount(txOptions, { 71 | newAccountPubkey: payingTokenAccount.publicKey, 72 | lamports: accountRent, 73 | mint: priceMint, 74 | owner: wallet.publicKey, 75 | }); 76 | txBatch.addTransaction(payingTokenAccountTx); 77 | txBatch.addSigner(payingTokenAccount); 78 | 79 | const transferAuthority = Keypair.generate(); 80 | 81 | const createApproveTx = (account: Keypair) => 82 | new Transaction().add( 83 | Token.createApproveInstruction( 84 | TOKEN_PROGRAM_ID, 85 | account.publicKey, 86 | transferAuthority.publicKey, 87 | wallet.publicKey, 88 | [], 89 | 0, 90 | ), 91 | ); 92 | 93 | txBatch.addTransaction(createApproveTx(payingTokenAccount)); 94 | txBatch.addTransaction(createApproveTx(outstandingShareAccount)); 95 | txBatch.addSigner(transferAuthority); 96 | 97 | const combineVaultTx = new CombineVault(txOptions, { 98 | vault, 99 | outstandingShareTokenAccount: outstandingShareAccount.publicKey, 100 | payingTokenAccount: payingTokenAccount.publicKey, 101 | fractionMint: fractionMintKey, 102 | fractionTreasury: fractionTreasuryKey, 103 | redeemTreasury: redeemTreasuryKey, 104 | burnAuthority: fractionMintAuthority, 105 | externalPriceAccount: pricingLookupAddressKey, 106 | transferAuthority: transferAuthority.publicKey, 107 | vaultAuthority: wallet.publicKey, 108 | newVaultAuthority: wallet.publicKey, 109 | }); 110 | txBatch.addTransaction(combineVaultTx); 111 | 112 | const txId = await sendTransaction({ 113 | connection, 114 | signers: txBatch.signers, 115 | txs: txBatch.transactions, 116 | wallet, 117 | }); 118 | 119 | return { txId }; 120 | }; 121 | -------------------------------------------------------------------------------- /src/actions/utility/createExternalPriceAccount.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { 3 | ExternalPriceAccountData, 4 | Vault, 5 | VaultProgram, 6 | UpdateExternalPriceAccount, 7 | } from '@metaplex-foundation/mpl-token-vault'; 8 | import { 9 | Keypair, 10 | PublicKey, 11 | SystemProgram, 12 | TransactionCtorFields, 13 | TransactionSignature, 14 | } from '@solana/web3.js'; 15 | import { NATIVE_MINT } from '@solana/spl-token'; 16 | import { Transaction } from '@metaplex-foundation/mpl-core'; 17 | 18 | import { Wallet } from '../../wallet'; 19 | import { Connection } from '../../Connection'; 20 | import { sendTransaction } from '../transactions'; 21 | import { TransactionsBatch } from '../../utils/transactions-batch'; 22 | 23 | interface CreateExternalPriceAccountParams { 24 | connection: Connection; 25 | wallet: Wallet; 26 | } 27 | 28 | interface CreateExternalPriceAccountResponse { 29 | txId: TransactionSignature; 30 | externalPriceAccount: PublicKey; 31 | priceMint: PublicKey; 32 | } 33 | 34 | // This command creates the external pricing oracle 35 | export const createExternalPriceAccount = async ({ 36 | connection, 37 | wallet, 38 | }: CreateExternalPriceAccountParams): Promise => { 39 | const txBatch = new TransactionsBatch({ transactions: [] }); 40 | const txOptions: TransactionCtorFields = { feePayer: wallet.publicKey }; 41 | 42 | const epaRentExempt = await connection.getMinimumBalanceForRentExemption( 43 | Vault.MAX_EXTERNAL_ACCOUNT_SIZE, 44 | ); 45 | 46 | const externalPriceAccount = Keypair.generate(); 47 | 48 | const externalPriceAccountData = new ExternalPriceAccountData({ 49 | pricePerShare: new BN(0), 50 | priceMint: NATIVE_MINT.toBase58(), 51 | allowedToCombine: true, 52 | }); 53 | 54 | const uninitializedEPA = new Transaction().add( 55 | SystemProgram.createAccount({ 56 | fromPubkey: wallet.publicKey, 57 | newAccountPubkey: externalPriceAccount.publicKey, 58 | lamports: epaRentExempt, 59 | space: Vault.MAX_EXTERNAL_ACCOUNT_SIZE, 60 | programId: VaultProgram.PUBKEY, 61 | }), 62 | ); 63 | txBatch.addTransaction(uninitializedEPA); 64 | txBatch.addSigner(externalPriceAccount); 65 | 66 | const updateEPA = new UpdateExternalPriceAccount(txOptions, { 67 | externalPriceAccount: externalPriceAccount.publicKey, 68 | externalPriceAccountData, 69 | }); 70 | txBatch.addTransaction(updateEPA); 71 | 72 | const txId = await sendTransaction({ 73 | connection, 74 | signers: txBatch.signers, 75 | txs: txBatch.transactions, 76 | wallet, 77 | }); 78 | 79 | return { 80 | txId, 81 | externalPriceAccount: externalPriceAccount.publicKey, 82 | priceMint: NATIVE_MINT, 83 | }; 84 | }; 85 | -------------------------------------------------------------------------------- /src/actions/utility/createVault.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { AccountLayout, MintLayout, NATIVE_MINT } from '@solana/spl-token'; 3 | import { InitVault, Vault, VaultProgram } from '@metaplex-foundation/mpl-token-vault'; 4 | import { Keypair, PublicKey, SystemProgram, TransactionSignature } from '@solana/web3.js'; 5 | 6 | import { Wallet } from '../../wallet'; 7 | import { Connection } from '../../Connection'; 8 | import { sendTransaction } from '../transactions'; 9 | import { CreateMint, CreateTokenAccount } from '../../transactions'; 10 | import { TransactionsBatch } from '../../utils/transactions-batch'; 11 | 12 | interface CreateVaultParams { 13 | connection: Connection; 14 | wallet: Wallet; 15 | priceMint: PublicKey; 16 | externalPriceAccount: PublicKey; 17 | } 18 | 19 | interface CreateVaultResponse { 20 | txId: TransactionSignature; 21 | vault: PublicKey; 22 | fractionMint: PublicKey; 23 | redeemTreasury: PublicKey; 24 | fractionTreasury: PublicKey; 25 | } 26 | 27 | // This command creates the external pricing oracle a vault 28 | // This gets the vault ready for adding the tokens. 29 | export const createVault = async ({ 30 | connection, 31 | wallet, 32 | priceMint = NATIVE_MINT, 33 | externalPriceAccount, 34 | }: CreateVaultParams): Promise => { 35 | const accountRent = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 36 | 37 | const mintRent = await connection.getMinimumBalanceForRentExemption(MintLayout.span); 38 | 39 | const vaultRent = await connection.getMinimumBalanceForRentExemption(Vault.MAX_VAULT_SIZE); 40 | 41 | const vault = Keypair.generate(); 42 | 43 | const vaultAuthority = await Vault.getPDA(vault.publicKey); 44 | 45 | const txBatch = new TransactionsBatch({ transactions: [] }); 46 | 47 | const fractionMint = Keypair.generate(); 48 | const fractionMintTx = new CreateMint( 49 | { feePayer: wallet.publicKey }, 50 | { 51 | newAccountPubkey: fractionMint.publicKey, 52 | lamports: mintRent, 53 | owner: vaultAuthority, 54 | freezeAuthority: vaultAuthority, 55 | }, 56 | ); 57 | txBatch.addTransaction(fractionMintTx); 58 | txBatch.addSigner(fractionMint); 59 | 60 | const redeemTreasury = Keypair.generate(); 61 | const redeemTreasuryTx = new CreateTokenAccount( 62 | { feePayer: wallet.publicKey }, 63 | { 64 | newAccountPubkey: redeemTreasury.publicKey, 65 | lamports: accountRent, 66 | mint: priceMint, 67 | owner: vaultAuthority, 68 | }, 69 | ); 70 | txBatch.addTransaction(redeemTreasuryTx); 71 | txBatch.addSigner(redeemTreasury); 72 | 73 | const fractionTreasury = Keypair.generate(); 74 | const fractionTreasuryTx = new CreateTokenAccount( 75 | { feePayer: wallet.publicKey }, 76 | { 77 | newAccountPubkey: fractionTreasury.publicKey, 78 | lamports: accountRent, 79 | mint: fractionMint.publicKey, 80 | owner: vaultAuthority, 81 | }, 82 | ); 83 | txBatch.addTransaction(fractionTreasuryTx); 84 | txBatch.addSigner(fractionTreasury); 85 | 86 | const uninitializedVaultTx = new Transaction().add( 87 | SystemProgram.createAccount({ 88 | fromPubkey: wallet.publicKey, 89 | newAccountPubkey: vault.publicKey, 90 | lamports: vaultRent, 91 | space: Vault.MAX_VAULT_SIZE, 92 | programId: VaultProgram.PUBKEY, 93 | }), 94 | ); 95 | txBatch.addTransaction(uninitializedVaultTx); 96 | txBatch.addSigner(vault); 97 | 98 | const initVaultTx = new InitVault( 99 | { feePayer: wallet.publicKey }, 100 | { 101 | vault: vault.publicKey, 102 | vaultAuthority: wallet.publicKey, 103 | fractionalTreasury: fractionTreasury.publicKey, 104 | pricingLookupAddress: externalPriceAccount, 105 | redeemTreasury: redeemTreasury.publicKey, 106 | fractionalMint: fractionMint.publicKey, 107 | allowFurtherShareCreation: true, 108 | }, 109 | ); 110 | txBatch.addTransaction(initVaultTx); 111 | 112 | const txId = await sendTransaction({ 113 | connection, 114 | signers: txBatch.signers, 115 | txs: txBatch.transactions, 116 | wallet, 117 | }); 118 | 119 | return { 120 | txId, 121 | vault: vault.publicKey, 122 | fractionMint: fractionMint.publicKey, 123 | redeemTreasury: redeemTreasury.publicKey, 124 | fractionTreasury: fractionTreasury.publicKey, 125 | }; 126 | }; 127 | -------------------------------------------------------------------------------- /src/actions/utility/index.ts: -------------------------------------------------------------------------------- 1 | export * from './closeVault'; 2 | export * from './createExternalPriceAccount'; 3 | export * from './createVault'; 4 | export * from './initAuction'; 5 | -------------------------------------------------------------------------------- /src/actions/utility/initAuction.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { 3 | Auction, 4 | AuctionExtended, 5 | CreateAuction, 6 | CreateAuctionArgs, 7 | } from '@metaplex-foundation/mpl-auction'; 8 | import { PublicKey, TransactionSignature } from '@solana/web3.js'; 9 | 10 | import { Wallet } from '../../wallet'; 11 | import { Connection } from '../../Connection'; 12 | import { sendTransaction } from '../transactions'; 13 | 14 | interface MakeAuctionParams { 15 | connection: Connection; 16 | wallet: Wallet; 17 | vault: PublicKey; 18 | auctionSettings: Omit; 19 | } 20 | 21 | interface MakeAuctionResponse { 22 | txId: TransactionSignature; 23 | auction: PublicKey; 24 | } 25 | 26 | export const initAuction = async ({ 27 | connection, 28 | wallet, 29 | vault, 30 | auctionSettings, 31 | }: MakeAuctionParams): Promise => { 32 | const txOptions = { feePayer: wallet.publicKey }; 33 | 34 | const [auctionKey, auctionExtended] = await Promise.all([ 35 | Auction.getPDA(vault), 36 | AuctionExtended.getPDA(vault), 37 | ]); 38 | 39 | const fullSettings = new CreateAuctionArgs({ 40 | ...auctionSettings, 41 | authority: wallet.publicKey.toBase58(), 42 | resource: vault.toBase58(), 43 | }); 44 | 45 | const auctionTx: Transaction = new CreateAuction(txOptions, { 46 | args: fullSettings, 47 | auction: auctionKey, 48 | creator: wallet.publicKey, 49 | auctionExtended, 50 | }); 51 | 52 | const txId = await sendTransaction({ 53 | connection, 54 | signers: [], 55 | txs: [auctionTx], 56 | wallet, 57 | }); 58 | 59 | return { txId, auction: auctionKey }; 60 | }; 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './providers'; 3 | export * from './Connection'; 4 | export * from './wallet'; 5 | export * as utils from './utils'; 6 | export * as actions from './actions'; 7 | export * as programs from './programs'; 8 | export * as transactions from './transactions'; 9 | -------------------------------------------------------------------------------- /src/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export * as transactions from '../transactions'; 3 | 4 | export * as auction from '@metaplex-foundation/mpl-auction'; 5 | export * as core from '@metaplex-foundation/mpl-core'; 6 | export * as metaplex from '@metaplex-foundation/mpl-metaplex'; 7 | export * as metadata from '@metaplex-foundation/mpl-token-metadata'; 8 | export * as vault from '@metaplex-foundation/mpl-token-vault'; 9 | -------------------------------------------------------------------------------- /src/providers/conversion/Coingecko.ts: -------------------------------------------------------------------------------- 1 | import { ConversionRateProvider, Currency, ConversionRatePair } from './ConversionRateProvider'; 2 | import axios from 'axios'; 3 | 4 | /** 5 | * Provides currency rate converstion via CoinGecko API. 6 | */ 7 | export class Coingecko implements ConversionRateProvider { 8 | /** 9 | * Translates currency strings from the internal Currency enum to the format that Coingecko requires 10 | * @param currency 11 | * @returns The provided currency in a format that Coingecko API recognizes. For instance, {@link Currency.AR} becomes 'arweave' 12 | */ 13 | static translateCurrency(currency: Currency): string { 14 | switch (currency) { 15 | case Currency.AR: 16 | return 'arweave'; 17 | case Currency.SOL: 18 | return 'solana'; 19 | case Currency.USD: 20 | return 'usd'; 21 | case Currency.EUR: 22 | return 'eur'; 23 | default: 24 | throw new Error('Invalid currency supplied to Coingecko conversion rate provider'); 25 | } 26 | } 27 | 28 | /** 29 | * Provides conversion rates for each `from` currency into all the provided `to` currencies 30 | * @param from 31 | * @param to 32 | */ 33 | async getRate(from: Currency | Currency[], to: Currency | Currency[]) { 34 | const fromArray = typeof from === 'string' ? [from] : from; 35 | const toArray = typeof to === 'string' ? [to] : to; 36 | const fromIds = fromArray.map((currency) => Coingecko.translateCurrency(currency)).join(','); 37 | const toIds = toArray.map((currency) => Coingecko.translateCurrency(currency)).join(','); 38 | const url = `https://api.coingecko.com/api/v3/simple/price?ids=${fromIds}&vs_currencies=${toIds}`; 39 | const response = await axios(url); 40 | const data = await response.data; 41 | return fromArray.reduce((previousPairs, fromCurrency) => { 42 | return [ 43 | ...previousPairs, 44 | ...toArray.map((toCurrency) => ({ 45 | from: fromCurrency, 46 | to: toCurrency, 47 | rate: data[Coingecko.translateCurrency(fromCurrency)][ 48 | Coingecko.translateCurrency(toCurrency) 49 | ], 50 | })), 51 | ]; 52 | }, []); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/providers/conversion/ConversionRateProvider.ts: -------------------------------------------------------------------------------- 1 | // We are keeping an organized enum of all currencies to allow for easier refactoring should 2 | // we decide to change the way that metaplex names currencies internally 3 | export enum Currency { 4 | USD = 'usd', 5 | EUR = 'eur', 6 | AR = 'ar', 7 | SOL = 'sol', 8 | } 9 | 10 | export type ConversionRatePair = { 11 | from: Currency; 12 | to: Currency; 13 | rate: number; 14 | }; 15 | 16 | export interface ConversionRateProvider { 17 | getRate(from: Currency | Currency[], to: Currency | Currency[]): Promise; 18 | } 19 | -------------------------------------------------------------------------------- /src/providers/conversion/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ConversionRateProvider'; 2 | export * from './Coingecko'; 3 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './conversion'; 2 | export * from './storage'; 3 | -------------------------------------------------------------------------------- /src/providers/storage/Storage.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | 3 | export interface UploadResult { 4 | error?: string; 5 | } 6 | 7 | export abstract class Storage { 8 | getAssetCostToStore: ( 9 | files: Map, 10 | arweaveRate: number, 11 | solanaRate: number, 12 | ) => Promise; 13 | upload: (files: Map, mintKey: string, txid: string) => Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/providers/storage/arweave/ArweaveStorage.ts: -------------------------------------------------------------------------------- 1 | import { Storage, UploadResult } from '../Storage'; 2 | import { Buffer } from 'buffer'; 3 | import axios from 'axios'; 4 | import FormData from 'form-data'; 5 | 6 | const ARWEAVE_URL = 'https://arweave.net'; 7 | const LAMPORT_MULTIPLIER = 10 ** 9; 8 | const WINSTON_MULTIPLIER = 10 ** 12; 9 | 10 | export interface ArweaveUploadResult extends UploadResult { 11 | messages?: { 12 | filename: string; 13 | status: 'success' | 'fail'; 14 | transactionId?: string; 15 | error?: string; 16 | }[]; 17 | } 18 | 19 | export interface ArweaveStorageCtorFields { 20 | endpoint: string; 21 | env: 'mainnet-beta' | 'testnet' | 'devnet'; 22 | } 23 | 24 | export class ArweaveStorage implements Storage { 25 | readonly endpoint: string; 26 | readonly env: string; 27 | 28 | constructor({ endpoint, env }: ArweaveStorageCtorFields) { 29 | this.endpoint = endpoint; 30 | this.env = env; 31 | } 32 | 33 | async getAssetCostToStore(files: Map, arweaveRate: number, solanaRate: number) { 34 | const buffers = Array.from(files.values()); 35 | const totalBytes = buffers.reduce((sum, f) => (sum += f.byteLength), 0); 36 | const txnFeeInWinstons = parseInt(await (await axios(`${ARWEAVE_URL}/price/0`)).data); 37 | const byteCostInWinstons = parseInt( 38 | await ( 39 | await axios(`${ARWEAVE_URL}/price/${totalBytes.toString()}`) 40 | ).data, 41 | ); 42 | const totalArCost = 43 | (txnFeeInWinstons * buffers.length + byteCostInWinstons) / WINSTON_MULTIPLIER; 44 | // To figure out how many lamports are required, multiply ar byte cost by this number 45 | const arMultiplier = arweaveRate / solanaRate; 46 | // We also always make a manifest file, which, though tiny, needs payment. 47 | return LAMPORT_MULTIPLIER * totalArCost * arMultiplier * 1.1; 48 | } 49 | 50 | async upload( 51 | files: Map, 52 | mintKey: string, 53 | txid: string, 54 | ): Promise { 55 | const fileEntries = Array.from(files.entries()); 56 | const tags = fileEntries.reduce( 57 | (acc: Record>, [fileName]) => { 58 | acc[fileName] = [{ name: 'mint', value: mintKey }]; 59 | return acc; 60 | }, 61 | {}, 62 | ); 63 | 64 | const body = new FormData(); 65 | 66 | body.append('tags', JSON.stringify(tags)); 67 | body.append('transaction', txid); 68 | body.append('env', this.env); 69 | fileEntries.map(([, file]) => { 70 | body.append('file[]', file); 71 | }); 72 | 73 | // TODO: I hate to do this, but it seems to be like an upstream problem: 74 | // https://github.com/jimmywarting/FormData/issues/133 75 | // I'll make sure to track it. - Danny 76 | const response = await axios.post(this.endpoint, body); 77 | 78 | if (response.data.error) { 79 | return Promise.reject(new Error(response.data.error)); 80 | } 81 | 82 | return response.data; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/providers/storage/arweave/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ArweaveStorage'; 2 | -------------------------------------------------------------------------------- /src/providers/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './arweave'; 2 | export * as storage from './Storage'; 3 | -------------------------------------------------------------------------------- /src/transactions/CreateAssociatedTokenAccount.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { 4 | PublicKey, 5 | SystemProgram, 6 | SYSVAR_RENT_PUBKEY, 7 | TransactionCtorFields, 8 | TransactionInstruction, 9 | } from '@solana/web3.js'; 10 | import { Buffer } from 'buffer'; 11 | 12 | type CreateAssociatedTokenAccountParams = { 13 | associatedTokenAddress: PublicKey; 14 | walletAddress?: PublicKey; 15 | splTokenMintAddress: PublicKey; 16 | }; 17 | 18 | export class CreateAssociatedTokenAccount extends Transaction { 19 | constructor(options: TransactionCtorFields, params: CreateAssociatedTokenAccountParams) { 20 | const { feePayer } = options; 21 | const { associatedTokenAddress, walletAddress, splTokenMintAddress } = params; 22 | super(options); 23 | 24 | this.add( 25 | new TransactionInstruction({ 26 | keys: [ 27 | { 28 | pubkey: feePayer, 29 | isSigner: true, 30 | isWritable: true, 31 | }, 32 | { 33 | pubkey: associatedTokenAddress, 34 | isSigner: false, 35 | isWritable: true, 36 | }, 37 | { 38 | pubkey: walletAddress ?? feePayer, 39 | isSigner: false, 40 | isWritable: false, 41 | }, 42 | { 43 | pubkey: splTokenMintAddress, 44 | isSigner: false, 45 | isWritable: false, 46 | }, 47 | { 48 | pubkey: SystemProgram.programId, 49 | isSigner: false, 50 | isWritable: false, 51 | }, 52 | { 53 | pubkey: TOKEN_PROGRAM_ID, 54 | isSigner: false, 55 | isWritable: false, 56 | }, 57 | { 58 | pubkey: SYSVAR_RENT_PUBKEY, 59 | isSigner: false, 60 | isWritable: false, 61 | }, 62 | ], 63 | programId: ASSOCIATED_TOKEN_PROGRAM_ID, 64 | data: Buffer.from([]), 65 | }), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/transactions/CreateMint.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { MintLayout, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { PublicKey, SystemProgram, TransactionCtorFields } from '@solana/web3.js'; 4 | 5 | type CreateMintParams = { 6 | newAccountPubkey: PublicKey; 7 | lamports: number; 8 | decimals?: number; 9 | owner?: PublicKey; 10 | freezeAuthority?: PublicKey; 11 | }; 12 | 13 | export class CreateMint extends Transaction { 14 | constructor(options: TransactionCtorFields, params: CreateMintParams) { 15 | const { feePayer } = options; 16 | const { newAccountPubkey, lamports, decimals, owner, freezeAuthority } = params; 17 | 18 | super(options); 19 | 20 | this.add( 21 | SystemProgram.createAccount({ 22 | fromPubkey: feePayer, 23 | newAccountPubkey, 24 | lamports, 25 | space: MintLayout.span, 26 | programId: TOKEN_PROGRAM_ID, 27 | }), 28 | ); 29 | 30 | this.add( 31 | Token.createInitMintInstruction( 32 | TOKEN_PROGRAM_ID, 33 | newAccountPubkey, 34 | decimals ?? 0, 35 | owner ?? feePayer, 36 | freezeAuthority ?? feePayer, 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/transactions/CreateTokenAccount.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { AccountLayout, Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { PublicKey, SystemProgram, TransactionCtorFields } from '@solana/web3.js'; 4 | 5 | type CreateTokenAccountParams = { 6 | newAccountPubkey: PublicKey; 7 | lamports: number; 8 | mint: PublicKey; 9 | owner?: PublicKey; 10 | }; 11 | 12 | export class CreateTokenAccount extends Transaction { 13 | constructor(options: TransactionCtorFields, params: CreateTokenAccountParams) { 14 | const { feePayer } = options; 15 | const { newAccountPubkey, lamports, mint, owner } = params; 16 | 17 | super(options); 18 | 19 | this.add( 20 | SystemProgram.createAccount({ 21 | fromPubkey: feePayer, 22 | newAccountPubkey, 23 | lamports, 24 | space: AccountLayout.span, 25 | programId: TOKEN_PROGRAM_ID, 26 | }), 27 | ); 28 | 29 | this.add( 30 | Token.createInitAccountInstruction( 31 | TOKEN_PROGRAM_ID, 32 | mint, 33 | newAccountPubkey, 34 | owner ?? feePayer, 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/transactions/MintTo.ts: -------------------------------------------------------------------------------- 1 | import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 2 | import { PublicKey, TransactionCtorFields } from '@solana/web3.js'; 3 | import BN from 'bn.js'; 4 | import { Transaction } from '@metaplex-foundation/mpl-core'; 5 | 6 | type MintToParams = { 7 | mint: PublicKey; 8 | dest: PublicKey; 9 | amount: number | BN; 10 | authority?: PublicKey; 11 | }; 12 | 13 | export class MintTo extends Transaction { 14 | constructor(options: TransactionCtorFields, params: MintToParams) { 15 | const { feePayer } = options; 16 | const { mint, dest, authority, amount } = params; 17 | 18 | super(options); 19 | 20 | this.add( 21 | Token.createMintToInstruction( 22 | TOKEN_PROGRAM_ID, 23 | mint, 24 | dest, 25 | authority ?? feePayer, 26 | [], 27 | new BN(amount).toNumber(), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/transactions/PayForFiles.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@metaplex-foundation/mpl-core'; 2 | import { 3 | PublicKey, 4 | SystemProgram, 5 | TransactionCtorFields, 6 | TransactionInstruction, 7 | } from '@solana/web3.js'; 8 | import { config } from '@metaplex-foundation/mpl-core'; 9 | import { Buffer } from 'buffer'; 10 | 11 | type PayForFilesParams = { 12 | lamports: number; 13 | fileHashes: Buffer[]; 14 | arweaveWallet?: PublicKey; 15 | }; 16 | 17 | export class PayForFiles extends Transaction { 18 | constructor(options: TransactionCtorFields, params: PayForFilesParams) { 19 | const { feePayer } = options; 20 | const { lamports, fileHashes, arweaveWallet } = params; 21 | 22 | super(options); 23 | 24 | this.add( 25 | SystemProgram.transfer({ 26 | fromPubkey: feePayer, 27 | toPubkey: arweaveWallet ?? new PublicKey(config.arweaveWallet), 28 | lamports, 29 | }), 30 | ); 31 | 32 | fileHashes.forEach((data) => { 33 | this.add( 34 | new TransactionInstruction({ 35 | keys: [], 36 | programId: new PublicKey(config.programs.memo), 37 | data, 38 | }), 39 | ); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/transactions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PayForFiles'; 2 | export * from './CreateMint'; 3 | export * from './CreateTokenAccount'; 4 | export * from './CreateAssociatedTokenAccount'; 5 | export * from './MintTo'; 6 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { StringPublicKey } from '@metaplex-foundation/mpl-core'; 2 | 3 | export type MetaDataJsonCategory = 'image' | 'video' | 'audio' | 'vr' | 'html'; 4 | 5 | export type MetadataJsonAttribute = { 6 | trait_type: string; 7 | value: string; 8 | }; 9 | 10 | export type MetadataJsonCollection = { 11 | name: string; 12 | family: string; 13 | }; 14 | 15 | export type MetadataJsonFile = { 16 | uri: string; 17 | type: string; 18 | cdn?: boolean; 19 | }; 20 | 21 | export type MetadataJsonCreator = { 22 | address: StringPublicKey; 23 | verified: boolean; 24 | share: number; 25 | }; 26 | 27 | export type MetadataJsonProperties = { 28 | files: MetadataJsonFile[]; 29 | category: MetaDataJsonCategory; 30 | creators: MetadataJsonCreator[]; 31 | }; 32 | 33 | export type MetadataJson = { 34 | name: string; 35 | symbol: string; 36 | description: string; 37 | seller_fee_basis_points: number; 38 | image: string; 39 | animation_url?: string; 40 | external_url?: string; 41 | attributes?: MetadataJsonAttribute[]; 42 | collection?: MetadataJsonCollection; 43 | properties: MetadataJsonProperties; 44 | }; 45 | 46 | export type Optional = Pick, K> & Omit; 47 | -------------------------------------------------------------------------------- /src/utils/crypto.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from 'crypto-hash'; 2 | import { Buffer } from 'buffer'; 3 | 4 | export const getFileHash = async (file: Buffer) => Buffer.from(await sha256(file.toString())); 5 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * as Crypto from './crypto'; 2 | export * as metadata from './metadata'; 3 | -------------------------------------------------------------------------------- /src/utils/metadata.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from 'axios'; 2 | import { MetadataJson } from './../types'; 3 | 4 | export const lookup = async (url: string): Promise => { 5 | try { 6 | const { data } = await axios.get>(url); 7 | 8 | return data; 9 | } catch { 10 | throw new Error(`unable to get metadata json from url ${url}`); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/transactions-batch.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { Transaction } from '@metaplex-foundation/mpl-core'; 3 | 4 | interface TransactionsBatchParams { 5 | beforeTransactions?: Transaction[]; 6 | transactions: Transaction[]; 7 | afterTransactions?: Transaction[]; 8 | } 9 | 10 | export class TransactionsBatch { 11 | beforeTransactions: Transaction[]; 12 | transactions: Transaction[]; 13 | afterTransactions: Transaction[]; 14 | 15 | signers: Keypair[] = []; 16 | 17 | constructor({ 18 | beforeTransactions = [], 19 | transactions, 20 | afterTransactions = [], 21 | }: TransactionsBatchParams) { 22 | this.beforeTransactions = beforeTransactions; 23 | this.transactions = transactions; 24 | this.afterTransactions = afterTransactions; 25 | } 26 | 27 | addSigner(signer: Keypair) { 28 | this.signers.push(signer); 29 | } 30 | 31 | addBeforeTransaction(transaction: Transaction) { 32 | this.beforeTransactions.push(transaction); 33 | } 34 | 35 | addTransaction(transaction: Transaction) { 36 | this.transactions.push(transaction); 37 | } 38 | 39 | addAfterTransaction(transaction: Transaction) { 40 | this.afterTransactions.push(transaction); 41 | } 42 | 43 | toTransactions() { 44 | return [...this.beforeTransactions, ...this.transactions, ...this.afterTransactions]; 45 | } 46 | 47 | toInstructions() { 48 | return this.toTransactions().flatMap((t) => t.instructions); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/wallet/index.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey, Transaction } from '@solana/web3.js'; 2 | 3 | export interface Wallet { 4 | publicKey: PublicKey; 5 | signTransaction(tx: Transaction): Promise; 6 | signAllTransactions(txs: Transaction[]): Promise; 7 | } 8 | 9 | export class NodeWallet implements Wallet { 10 | constructor(readonly payer: Keypair) {} 11 | 12 | async signTransaction(tx: Transaction): Promise { 13 | tx.partialSign(this.payer); 14 | return tx; 15 | } 16 | 17 | async signAllTransactions(txs: Transaction[]): Promise { 18 | return txs.map((tx) => { 19 | tx.partialSign(this.payer); 20 | return tx; 21 | }); 22 | } 23 | 24 | get publicKey(): PublicKey { 25 | return this.payer.publicKey; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/actions/addTokensToVault.test.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { Transaction } from '@metaplex-foundation/mpl-core'; 3 | import { sendAndConfirmTransaction } from '@solana/web3.js'; 4 | 5 | import { generateConnectionAndWallet } from './shared'; 6 | import { 7 | addTokensToVault, 8 | prepareTokenAccountAndMintTxs, 9 | createExternalPriceAccount, 10 | createVault, 11 | } from '../../src/actions'; 12 | 13 | describe('addTokensToVault action', () => { 14 | test('creation and adding of multiple mint tokens to newly created vault', async () => { 15 | const TOKEN_AMOUNT = 2; 16 | 17 | const { connection, wallet, payer } = await generateConnectionAndWallet(); 18 | 19 | const externalPriceAccountData = await createExternalPriceAccount({ connection, wallet }); 20 | 21 | const { vault } = await createVault({ 22 | connection, 23 | wallet, 24 | ...externalPriceAccountData, 25 | }); 26 | 27 | const testNfts = []; 28 | 29 | for (let i = 0; i < TOKEN_AMOUNT; i++) { 30 | const { 31 | mint, 32 | recipient: tokenAccount, 33 | createAssociatedTokenAccountTx, 34 | createMintTx, 35 | mintToTx, 36 | } = await prepareTokenAccountAndMintTxs(connection, wallet.publicKey); 37 | 38 | await sendAndConfirmTransaction( 39 | connection, 40 | Transaction.fromCombined([createMintTx, createAssociatedTokenAccountTx, mintToTx]), 41 | [payer, mint, wallet.payer], 42 | ); 43 | 44 | testNfts.push({ 45 | tokenAccount, 46 | tokenMint: mint.publicKey, 47 | amount: new BN(1), 48 | }); 49 | } 50 | 51 | const { safetyDepositTokenStores } = await addTokensToVault({ 52 | connection, 53 | wallet, 54 | vault, 55 | nfts: testNfts, 56 | }); 57 | 58 | expect(safetyDepositTokenStores.length).toEqual(testNfts.length); 59 | expect(safetyDepositTokenStores.map(({ tokenMint }) => tokenMint).join(',')).toEqual( 60 | testNfts.map(({ tokenMint }) => tokenMint).join(','), 61 | ); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/actions/createMetadataAndME.test.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Connection, NodeWallet } from '../../src'; 4 | import { FEE_PAYER, NETWORK, sleep } from '../utils'; 5 | import { 6 | Creator, 7 | MasterEdition, 8 | Metadata, 9 | MetadataDataData, 10 | } from '@metaplex-foundation/mpl-token-metadata'; 11 | import { createMetadata } from '../../src/actions/createMetadata'; 12 | import { createMasterEdition } from '../../src/actions/createMasterEdition'; 13 | import { uri } from './shared'; 14 | import { Account } from '@metaplex-foundation/mpl-core'; 15 | 16 | // NOTE: testing the two together because latter effectively requires former 17 | describe('creatomg metadata and master edition PDAs', () => { 18 | const connection = new Connection(NETWORK); 19 | const wallet = new NodeWallet(FEE_PAYER); 20 | 21 | test('creates both successfully', async () => { 22 | const mint = await Token.createMint( 23 | connection, 24 | FEE_PAYER, 25 | wallet.publicKey, 26 | wallet.publicKey, 27 | 0, 28 | TOKEN_PROGRAM_ID, 29 | ); 30 | const tokenAddress = await mint.createAssociatedTokenAccount(wallet.publicKey); 31 | await mint.mintTo(tokenAddress, wallet.publicKey, [], 1); 32 | 33 | const metadataData = new MetadataDataData({ 34 | name: 'xyzname', 35 | symbol: 'xyz', 36 | uri, 37 | sellerFeeBasisPoints: 10, 38 | creators: [ 39 | new Creator({ 40 | address: wallet.publicKey.toBase58(), 41 | verified: false, 42 | share: 100, 43 | }), 44 | ], 45 | }); 46 | 47 | await createMetadata({ 48 | connection, 49 | wallet, 50 | editionMint: mint.publicKey, 51 | metadataData, 52 | }); 53 | 54 | await sleep(20000); 55 | 56 | const metadata = await Metadata.getPDA(mint.publicKey); 57 | const metadataInfo = await Account.getInfo(connection, metadata); 58 | const deserializedMetadataData = new Metadata(metadata, metadataInfo).data; 59 | expect(deserializedMetadataData.data.name).toEqual('xyzname'); 60 | 61 | await createMasterEdition({ 62 | connection, 63 | wallet, 64 | editionMint: mint.publicKey, 65 | maxSupply: new BN(100), 66 | }); 67 | 68 | // had to increase to 25s, or it was failing 69 | await sleep(25000); 70 | 71 | const edition = await MasterEdition.getPDA(mint.publicKey); 72 | const editionInfo = await Account.getInfo(connection, edition); 73 | const deserializedEditionData = new MasterEdition(edition, editionInfo).data; 74 | expect(deserializedEditionData.maxSupply.toString(10)).toEqual('100'); 75 | }, 150000); 76 | }); 77 | -------------------------------------------------------------------------------- /test/actions/initStore.test.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { Connection, NodeWallet } from '../../src'; 3 | import { initStore, initStoreV2 } from '../../src/actions'; 4 | import { FEE_PAYER, NETWORK } from '../utils'; 5 | import { Store } from '@metaplex-foundation/mpl-metaplex'; 6 | import { uri } from './shared'; 7 | 8 | describe('init Store', () => { 9 | const connection = new Connection(NETWORK); 10 | const wallet = new NodeWallet(FEE_PAYER); 11 | let mint: Keypair; 12 | 13 | beforeEach(() => { 14 | mint = Keypair.generate(); 15 | jest.spyOn(Keypair, 'generate').mockReturnValue(mint); 16 | }); 17 | 18 | test('creates store with initStore', async () => { 19 | const storeResponse = await initStore({ 20 | connection, 21 | wallet, 22 | isPublic: false, 23 | }); 24 | 25 | const storeId = await Store.getPDA(wallet.publicKey); 26 | 27 | expect(storeResponse).toMatchObject({ 28 | storeId, 29 | }); 30 | }); 31 | 32 | test('creates store with initStoreV2', async () => { 33 | const storeResponse = await initStoreV2({ 34 | connection, 35 | wallet, 36 | isPublic: false, 37 | settingsUri: uri, 38 | }); 39 | 40 | const storeId = await Store.getPDA(wallet.publicKey); 41 | 42 | expect(storeResponse).toMatchObject({ 43 | storeId, 44 | }); 45 | }); 46 | 47 | test('creates store with initStoreV2 without storeV2', async () => { 48 | const storeResponse = await initStoreV2({ 49 | connection, 50 | wallet, 51 | isPublic: false, 52 | }); 53 | 54 | const storeId = await Store.getPDA(wallet.publicKey); 55 | 56 | expect(storeResponse).toMatchObject({ 57 | storeId, 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/actions/metadata.test.ts: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Keypair, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js'; 4 | import { Connection } from '../../src'; 5 | 6 | import { 7 | Metadata, 8 | MetadataDataData, 9 | CreateMetadata, 10 | UpdateMetadata, 11 | MasterEdition, 12 | CreateMasterEdition, 13 | } from '@metaplex-foundation/mpl-token-metadata'; 14 | import { CreateMint, CreateAssociatedTokenAccount, MintTo } from '../../src/transactions'; 15 | import { FEE_PAYER, NETWORK } from '../utils'; 16 | import { Transaction } from '@metaplex-foundation/mpl-core'; 17 | 18 | describe.skip('Metaplex transactions', () => { 19 | let connection: Connection; 20 | let owner: Keypair; 21 | let mint: Keypair; 22 | 23 | jest.setTimeout(80000); 24 | 25 | beforeAll(() => { 26 | connection = new Connection(NETWORK); 27 | owner = Keypair.generate(); 28 | mint = Keypair.generate(); 29 | }); 30 | 31 | test('createMetadata', async () => { 32 | const metadataPDA = await Metadata.getPDA(mint.publicKey); 33 | 34 | const mintRent = await connection.getMinimumBalanceForRentExemption(MintLayout.span); 35 | const createMintTx = new CreateMint( 36 | { feePayer: FEE_PAYER.publicKey }, 37 | { 38 | newAccountPubkey: mint.publicKey, 39 | lamports: mintRent, 40 | }, 41 | ); 42 | const metadataData = new MetadataDataData({ 43 | name: 'Test', 44 | symbol: '', 45 | uri: '', 46 | sellerFeeBasisPoints: 300, 47 | creators: null, 48 | }); 49 | 50 | const tx = new CreateMetadata( 51 | { feePayer: FEE_PAYER.publicKey }, 52 | { 53 | metadata: metadataPDA, 54 | metadataData, 55 | updateAuthority: owner.publicKey, 56 | mint: mint.publicKey, 57 | mintAuthority: FEE_PAYER.publicKey, 58 | }, 59 | ); 60 | 61 | const txs = Transaction.fromCombined([createMintTx, tx]); 62 | 63 | await sendAndConfirmTransaction(connection, txs, [FEE_PAYER, mint, owner], { 64 | commitment: 'confirmed', 65 | }); 66 | }); 67 | 68 | test('updateMetadata', async () => { 69 | const metadataPDA = await Metadata.getPDA(mint.publicKey); 70 | const tx = new UpdateMetadata( 71 | { feePayer: FEE_PAYER.publicKey }, 72 | { 73 | metadata: metadataPDA, 74 | updateAuthority: owner.publicKey, 75 | primarySaleHappened: true, 76 | }, 77 | ); 78 | 79 | await sendAndConfirmTransaction(connection, tx, [FEE_PAYER, owner], { 80 | commitment: 'confirmed', 81 | }); 82 | }); 83 | 84 | test('createMasterEdition', async () => { 85 | const metadataPDA = await Metadata.getPDA(mint.publicKey); 86 | const editionPDA = await MasterEdition.getPDA(mint.publicKey); 87 | 88 | const [recipient] = await PublicKey.findProgramAddress( 89 | [FEE_PAYER.publicKey.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.publicKey.toBuffer()], 90 | ASSOCIATED_TOKEN_PROGRAM_ID, 91 | ); 92 | 93 | const createAssociatedTokenAccountTx = new CreateAssociatedTokenAccount( 94 | { feePayer: FEE_PAYER.publicKey }, 95 | { 96 | associatedTokenAddress: recipient, 97 | splTokenMintAddress: mint.publicKey, 98 | }, 99 | ); 100 | 101 | const mintToTx = new MintTo( 102 | { feePayer: FEE_PAYER.publicKey }, 103 | { 104 | mint: mint.publicKey, 105 | dest: recipient, 106 | amount: 1, 107 | }, 108 | ); 109 | 110 | const tx = new CreateMasterEdition( 111 | { feePayer: FEE_PAYER.publicKey }, 112 | { 113 | edition: editionPDA, 114 | metadata: metadataPDA, 115 | updateAuthority: owner.publicKey, 116 | mint: mint.publicKey, 117 | mintAuthority: FEE_PAYER.publicKey, 118 | }, 119 | ); 120 | 121 | const txs = Transaction.fromCombined([createAssociatedTokenAccountTx, mintToTx, tx]); 122 | 123 | await sendAndConfirmTransaction(connection, txs, [FEE_PAYER, owner], { 124 | commitment: 'confirmed', 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/actions/metaplex.test.ts: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { TupleNumericType, Transaction } from '@metaplex-foundation/mpl-core'; 3 | import { AccountLayout, NATIVE_MINT } from '@solana/spl-token'; 4 | import { Keypair, sendAndConfirmTransaction } from '@solana/web3.js'; 5 | import BN from 'bn.js'; 6 | import { Connection } from '../../src'; 7 | import { 8 | Store, 9 | SetStore, 10 | WhitelistedCreator, 11 | SetWhitelistedCreator, 12 | AuctionManager, 13 | AuctionWinnerTokenTypeTracker, 14 | InitAuctionManagerV2, 15 | StartAuction, 16 | } from '@metaplex-foundation/mpl-metaplex'; 17 | import { Auction } from '@metaplex-foundation/mpl-auction'; 18 | import { CreateTokenAccount } from '../../src/transactions'; 19 | import { FEE_PAYER, NETWORK, VAULT_PUBKEY } from '../utils'; 20 | 21 | describe.skip('Metaplex transactions', () => { 22 | let connection: Connection; 23 | let owner: Keypair; 24 | 25 | jest.setTimeout(80000); 26 | 27 | beforeAll(() => { 28 | connection = new Connection(NETWORK); 29 | owner = Keypair.generate(); 30 | }); 31 | 32 | test('setStore', async () => { 33 | const storeId = await Store.getPDA(owner.publicKey); 34 | 35 | const tx = new SetStore( 36 | { feePayer: FEE_PAYER.publicKey }, 37 | { 38 | admin: owner.publicKey, 39 | store: storeId, 40 | isPublic: true, 41 | }, 42 | ); 43 | 44 | await sendAndConfirmTransaction(connection, tx, [FEE_PAYER, owner], { 45 | commitment: 'confirmed', 46 | }); 47 | }); 48 | 49 | test('setWhitelistedCreator', async () => { 50 | const storeId = await Store.getPDA(owner.publicKey); 51 | const creator = owner.publicKey; 52 | const whitelistedCreatorPDA = await WhitelistedCreator.getPDA(storeId, creator); 53 | 54 | const tx = new SetWhitelistedCreator( 55 | { feePayer: FEE_PAYER.publicKey }, 56 | { 57 | admin: owner.publicKey, 58 | store: storeId, 59 | whitelistedCreatorPDA, 60 | creator, 61 | activated: true, 62 | }, 63 | ); 64 | 65 | await sendAndConfirmTransaction(connection, tx, [FEE_PAYER, owner], { 66 | commitment: 'confirmed', 67 | }); 68 | }); 69 | 70 | test('startAuction', async () => { 71 | const storeId = await Store.getPDA(owner.publicKey); 72 | const auctionPDA = await Auction.getPDA(VAULT_PUBKEY); 73 | const auctionManagerPDA = await AuctionManager.getPDA(auctionPDA); 74 | 75 | const tx = new StartAuction( 76 | { feePayer: FEE_PAYER.publicKey }, 77 | { 78 | store: storeId, 79 | auction: auctionPDA, 80 | auctionManager: auctionManagerPDA, 81 | auctionManagerAuthority: owner.publicKey, 82 | }, 83 | ); 84 | 85 | await sendAndConfirmTransaction(connection, tx, [FEE_PAYER, owner], { 86 | commitment: 'confirmed', 87 | }); 88 | }); 89 | 90 | test('initAuctionManagerV2', async () => { 91 | const storeId = await Store.getPDA(owner.publicKey); 92 | const auctionPDA = await Auction.getPDA(VAULT_PUBKEY); 93 | const auctionManagerPDA = await AuctionManager.getPDA(auctionPDA); 94 | const tokenTrackerPDA = await AuctionWinnerTokenTypeTracker.getPDA(auctionManagerPDA); 95 | 96 | const paymentAccount = Keypair.generate(); 97 | const mintRent = await connection.getMinimumBalanceForRentExemption(AccountLayout.span); 98 | const createTokenAccountTx = new CreateTokenAccount( 99 | { feePayer: FEE_PAYER.publicKey }, 100 | { 101 | newAccountPubkey: paymentAccount.publicKey, 102 | lamports: mintRent, 103 | mint: NATIVE_MINT, 104 | }, 105 | ); 106 | 107 | const tx = new InitAuctionManagerV2( 108 | { feePayer: FEE_PAYER.publicKey }, 109 | { 110 | store: storeId, 111 | vault: VAULT_PUBKEY, 112 | auction: auctionPDA, 113 | auctionManager: auctionManagerPDA, 114 | auctionManagerAuthority: owner.publicKey, 115 | acceptPaymentAccount: paymentAccount.publicKey, 116 | tokenTracker: tokenTrackerPDA, 117 | amountType: TupleNumericType.U8, 118 | lengthType: TupleNumericType.U8, 119 | maxRanges: new BN(10), 120 | }, 121 | ); 122 | 123 | const txs = Transaction.fromCombined([createTokenAccountTx, tx]); 124 | 125 | await sendAndConfirmTransaction(connection, txs, [FEE_PAYER, paymentAccount, owner], { 126 | commitment: 'confirmed', 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/actions/mintEditionFromMaster.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection, NodeWallet } from '../../src'; 2 | import { mintNFT } from '../../src/actions'; 3 | import { FEE_PAYER, NETWORK, sleep } from '../utils'; 4 | import { MasterEdition, Metadata } from '@metaplex-foundation/mpl-token-metadata'; 5 | import { mintEditionFromMaster } from '../../src/actions/mintEditionFromMaster'; 6 | import { mockAxios200, uri } from './shared'; 7 | 8 | jest.mock('axios'); 9 | jest.setTimeout(100000); 10 | 11 | describe('minting a limited edition from master', () => { 12 | const connection = new Connection(NETWORK); 13 | const wallet = new NodeWallet(FEE_PAYER); 14 | 15 | beforeEach(() => { 16 | mockAxios200(wallet); 17 | }); 18 | 19 | test('mints successfully', async () => { 20 | const masterMintResponse = await mintNFT({ 21 | connection, 22 | wallet, 23 | uri, 24 | maxSupply: 100, 25 | }); 26 | 27 | // unfortunately it takes some time for the master mint to propagate 28 | // empirically, I found anything below 20s to be unreliable 29 | await sleep(20000); 30 | 31 | const editionMintResponse = await mintEditionFromMaster({ 32 | connection, 33 | wallet, 34 | masterEditionMint: masterMintResponse.mint, 35 | }); 36 | 37 | const newEditionMint = editionMintResponse.mint; 38 | const metadata = await Metadata.getPDA(newEditionMint); 39 | const edition = await MasterEdition.getPDA(newEditionMint); 40 | expect(editionMintResponse.edition).toEqual(edition); 41 | expect(editionMintResponse.metadata).toEqual(metadata); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/actions/mintNFT.test.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { Connection, NodeWallet } from '../../src'; 3 | import { mintNFT, MintNFTParams } from '../../src/actions'; 4 | import { sleep } from '../utils'; 5 | import { MasterEdition, Metadata } from '@metaplex-foundation/mpl-token-metadata'; 6 | import { uri, generateConnectionAndWallet, mockAxios200, mockAxios404 } from './shared'; 7 | import { airdrop } from '@metaplex-foundation/amman'; 8 | 9 | jest.mock('axios'); 10 | 11 | describe('minting an NFT', () => { 12 | let connection: Connection; 13 | let wallet: NodeWallet; 14 | let payer: Keypair; 15 | 16 | beforeAll(async () => { 17 | const result = await generateConnectionAndWallet(); 18 | connection = result.connection; 19 | wallet = result.wallet; 20 | payer = result.payer; 21 | 22 | await airdrop(connection, payer.publicKey, 2); 23 | }); 24 | 25 | describe('when metadata json is found', () => { 26 | beforeAll(async () => { 27 | mockAxios200(wallet); // metadata json found 28 | }); 29 | 30 | const values = [0, 3, undefined]; 31 | for (const maxSupply of values) { 32 | describe(`when max supply is "${maxSupply}"`, () => { 33 | test('generates a unique mint, metadata & master editions from metadata URL', async () => { 34 | const arg: MintNFTParams = { 35 | connection, 36 | wallet, 37 | uri, 38 | }; 39 | 40 | if (maxSupply !== undefined) { 41 | arg.maxSupply = maxSupply; 42 | } 43 | 44 | const mintResponse = await mintNFT(arg); 45 | const { mint } = mintResponse; 46 | 47 | const metadata = await Metadata.getPDA(mint); 48 | const edition = await MasterEdition.getPDA(mint); 49 | 50 | expect(mintResponse).toMatchObject({ 51 | metadata, 52 | edition, 53 | }); 54 | 55 | await sleep(2000); // HACK 56 | 57 | const metadataEdition = (await Metadata.getEdition(connection, mint)) as MasterEdition; 58 | expect(metadataEdition.data?.maxSupply?.toNumber()).toBe(maxSupply); 59 | }); 60 | }); 61 | } 62 | }); 63 | 64 | describe('when metadata json not found', () => { 65 | beforeEach(() => { 66 | mockAxios404(); 67 | 68 | jest 69 | .spyOn(connection, 'sendRawTransaction') 70 | .mockResolvedValue( 71 | '64Tpr1DNj9UWg1P89Zss5Y4Mh2gGyRUMYZPNenZKY2hiNjsotrCDMBriDrsvhg5BJt3mY4hH6jcparNHCZGhAwf6', 72 | ); 73 | }); 74 | 75 | test('exits the action and throws an error', async () => { 76 | try { 77 | await mintNFT({ 78 | connection, 79 | wallet, 80 | uri, 81 | maxSupply: 0, 82 | }); 83 | } catch (e) { 84 | expect(e.message).toMatch(/unable to get metadata/); 85 | } 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/actions/redeemParticipationBidV3.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NonWinningConstraint, 3 | ParticipationConfigV2, 4 | WinningConstraint, 5 | } from '@metaplex-foundation/mpl-metaplex'; 6 | import { isEligibleForParticipationPrize } from '../../src/actions'; 7 | 8 | describe('redeem participation bid v3', () => { 9 | describe('isEligibleForParticipationPrize', () => { 10 | test('winIndex = null, winnerConstraint = NoParticipationPrize, nonWinningConstraint = NoParticipationPrize', () => { 11 | const result = isEligibleForParticipationPrize(null, { 12 | winnerConstraint: WinningConstraint.NoParticipationPrize, 13 | nonWinningConstraint: NonWinningConstraint.NoParticipationPrize, 14 | } as ParticipationConfigV2); 15 | expect(result).toBe(false); 16 | }); 17 | 18 | test('winIndex = null, winnerConstraint = ParticipationPrizeGiven, nonWinningConstraint = NoParticipationPrize', () => { 19 | const result = isEligibleForParticipationPrize(null, { 20 | winnerConstraint: WinningConstraint.ParticipationPrizeGiven, 21 | nonWinningConstraint: NonWinningConstraint.NoParticipationPrize, 22 | } as ParticipationConfigV2); 23 | expect(result).toBe(false); 24 | }); 25 | 26 | test('winIndex = null, winnerConstraint = NoParticipationPrize, nonWinningConstraint = GivenForFixedPrice', () => { 27 | const result = isEligibleForParticipationPrize(null, { 28 | winnerConstraint: WinningConstraint.NoParticipationPrize, 29 | nonWinningConstraint: NonWinningConstraint.GivenForFixedPrice, 30 | } as ParticipationConfigV2); 31 | expect(result).toBe(true); 32 | }); 33 | 34 | test('winIndex = null, winnerConstraint = ParticipationPrizeGiven, nonWinningConstraint = GivenForFixedPrice', () => { 35 | const result = isEligibleForParticipationPrize(null, { 36 | winnerConstraint: WinningConstraint.ParticipationPrizeGiven, 37 | nonWinningConstraint: NonWinningConstraint.GivenForFixedPrice, 38 | } as ParticipationConfigV2); 39 | expect(result).toBe(true); 40 | }); 41 | 42 | test('winIndex = 0, winnerConstraint = NoParticipationPrize, nonWinningConstraint = NoParticipationPrize', () => { 43 | const result = isEligibleForParticipationPrize(0, { 44 | winnerConstraint: WinningConstraint.NoParticipationPrize, 45 | nonWinningConstraint: NonWinningConstraint.NoParticipationPrize, 46 | } as ParticipationConfigV2); 47 | expect(result).toBe(false); 48 | }); 49 | 50 | test('winIndex = 0, winnerConstraint = ParticipationPrizeGiven, nonWinningConstraint = NoParticipationPrize', () => { 51 | const result = isEligibleForParticipationPrize(0, { 52 | winnerConstraint: WinningConstraint.ParticipationPrizeGiven, 53 | nonWinningConstraint: NonWinningConstraint.NoParticipationPrize, 54 | } as ParticipationConfigV2); 55 | expect(result).toBe(true); 56 | }); 57 | 58 | test('winIndex = 0, winnerConstraint = NoParticipationPrize, nonWinningConstraint = GivenForFixedPrice', () => { 59 | const result = isEligibleForParticipationPrize(0, { 60 | winnerConstraint: WinningConstraint.NoParticipationPrize, 61 | nonWinningConstraint: NonWinningConstraint.GivenForFixedPrice, 62 | } as ParticipationConfigV2); 63 | expect(result).toBe(false); 64 | }); 65 | 66 | test('winIndex = 0, winnerConstraint = ParticipationPrizeGiven, nonWinningConstraint = GivenForFixedPrice', () => { 67 | const result = isEligibleForParticipationPrize(null, { 68 | winnerConstraint: WinningConstraint.ParticipationPrizeGiven, 69 | nonWinningConstraint: NonWinningConstraint.GivenForFixedPrice, 70 | } as ParticipationConfigV2); 71 | expect(result).toBe(true); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/actions/shared/index.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import axios, { AxiosResponse } from 'axios'; 3 | import { airdrop, LOCALHOST } from '@metaplex-foundation/amman'; 4 | 5 | import { Connection, NodeWallet, Wallet } from '../../../src'; 6 | 7 | export const uri = 8 | 'https://bafkreibj4hjlhf3ehpugvfy6bzhhu2c7frvyhrykjqmoocsvdw24omfqga.ipfs.dweb.link'; 9 | 10 | export const mockAxios200 = (wallet: Wallet, secondSigner: Keypair | undefined = undefined) => { 11 | const mockedAxiosGet = axios.get as jest.MockedFunction; 12 | const mockedResponse: AxiosResponse = { 13 | data: { 14 | name: 'Holo Design (0)', 15 | symbol: '', 16 | description: 17 | 'A holo of some design in a lovely purple, pink, and yellow. Pulled from the Internet. Demo only.', 18 | seller_fee_basis_points: 100, 19 | image: uri, 20 | external_url: '', 21 | properties: { 22 | creators: [ 23 | { 24 | address: wallet.publicKey.toString(), 25 | verified: 1, 26 | share: 100, 27 | }, 28 | ], 29 | }, 30 | }, 31 | status: 200, 32 | statusText: 'OK', 33 | headers: {}, 34 | config: {}, 35 | }; 36 | if (secondSigner) { 37 | mockedResponse.data.properties.creators.push({ 38 | address: secondSigner.publicKey.toString(), 39 | verified: 0, 40 | share: 0, 41 | }); 42 | } 43 | mockedAxiosGet.mockResolvedValue(mockedResponse); 44 | }; 45 | 46 | export const mockAxios404 = () => { 47 | const mockedAxiosGet = axios.get as jest.MockedFunction; 48 | const mockedResponse: AxiosResponse = { 49 | data: {}, 50 | status: 404, 51 | statusText: 'NOT FOUND', 52 | headers: {}, 53 | config: {}, 54 | }; 55 | mockedAxiosGet.mockRejectedValue(mockedResponse); 56 | }; 57 | 58 | export const generateConnectionAndWallet = async () => { 59 | const payer = Keypair.generate(); 60 | const connection = new Connection(LOCALHOST, 'confirmed'); 61 | await airdrop(connection, payer.publicKey, 10); 62 | const wallet = new NodeWallet(payer); 63 | 64 | return { connection, wallet, payer }; 65 | }; 66 | -------------------------------------------------------------------------------- /test/actions/signMetadata.test.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { Connection, NodeWallet } from '../../src'; 3 | import { mintNFT } from '../../src/actions'; 4 | import { FEE_PAYER, NETWORK, sleep } from '../utils'; 5 | import { Metadata } from '@metaplex-foundation/mpl-token-metadata'; 6 | import { signMetadata } from '../../src/actions/signMetadata'; 7 | import { mockAxios200, uri } from './shared'; 8 | import { Account } from '@metaplex-foundation/mpl-core'; 9 | 10 | jest.mock('axios'); 11 | jest.setTimeout(100000); 12 | 13 | describe('signing metadata on a master edition', () => { 14 | const connection = new Connection(NETWORK); 15 | const wallet = new NodeWallet(FEE_PAYER); 16 | let secondSigner: Keypair; 17 | 18 | beforeEach(() => { 19 | secondSigner = Keypair.generate(); 20 | mockAxios200(wallet, secondSigner); 21 | }); 22 | 23 | test('signs successfully', async () => { 24 | const masterMintResponse = await mintNFT({ 25 | connection, 26 | wallet, 27 | uri, 28 | maxSupply: 100, 29 | }); 30 | 31 | // unfortunately it takes some time for the master mint to propagate 32 | // empirically, I found anything below 20s to be unreliable 33 | await sleep(20000); 34 | 35 | // before signing 36 | const metadata = await Metadata.getPDA(masterMintResponse.mint); 37 | let info = await Account.getInfo(connection, metadata); 38 | let metadataData = new Metadata(metadata, info).data; 39 | expect(metadataData.data.creators[1].verified).toEqual(0); 40 | 41 | await signMetadata({ 42 | connection, 43 | wallet, 44 | editionMint: masterMintResponse.mint, 45 | signer: secondSigner, 46 | }); 47 | 48 | await sleep(20000); 49 | 50 | //after signing 51 | info = await Account.getInfo(connection, metadata); 52 | metadataData = new Metadata(metadata, info).data; 53 | expect(metadataData.data.creators[1].verified).toEqual(1); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/actions/updateMetadata.test.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { Connection, NodeWallet } from '../../src'; 3 | import { mintNFT } from '../../src/actions'; 4 | import { FEE_PAYER, NETWORK, sleep } from '../utils'; 5 | import { Creator, Metadata, MetadataDataData } from '@metaplex-foundation/mpl-token-metadata'; 6 | import { updateMetadata } from '../../src/actions/updateMetadata'; 7 | import { mockAxios200, uri } from './shared'; 8 | import { Account } from '@metaplex-foundation/mpl-core'; 9 | 10 | jest.mock('axios'); 11 | jest.setTimeout(100000); 12 | 13 | describe('updating metadata on a master edition', () => { 14 | const connection = new Connection(NETWORK); 15 | const wallet = new NodeWallet(FEE_PAYER); 16 | 17 | beforeEach(() => { 18 | mockAxios200(wallet); 19 | }); 20 | 21 | test('updates successfully', async () => { 22 | const newAuthority = Keypair.generate(); 23 | 24 | const newMetadataData = new MetadataDataData({ 25 | name: 'xyzname', 26 | symbol: 'xyz', 27 | uri: 'https://gateway.pinata.cloud/ipfs/QmNQh8noRHn7e7zt9oYNfGWuxHgKWkNPducMZs1SiZaYw4', 28 | sellerFeeBasisPoints: 10, 29 | creators: [ 30 | new Creator({ 31 | address: Keypair.generate().publicKey.toBase58(), 32 | verified: false, 33 | share: 100, 34 | }), 35 | ], 36 | }); 37 | 38 | const masterMintResponse = await mintNFT({ 39 | connection, 40 | wallet, 41 | uri, 42 | maxSupply: 100, 43 | }); 44 | 45 | // unfortunately it takes some time for the master mint to propagate 46 | // empirically, I found anything below 20s to be unreliable 47 | await sleep(20000); 48 | 49 | // before update 50 | const metadata = await Metadata.getPDA(masterMintResponse.mint); 51 | let info = await Account.getInfo(connection, metadata); 52 | let metadataData = new Metadata(metadata, info).data; 53 | expect(metadataData.data.name).toEqual('Holo Design (0)'); 54 | expect(metadataData.updateAuthority).toEqual(wallet.publicKey.toBase58()); 55 | expect(metadataData.primarySaleHappened).toEqual(0); 56 | 57 | await updateMetadata({ 58 | connection, 59 | wallet, 60 | editionMint: masterMintResponse.mint, 61 | newMetadataData, 62 | newUpdateAuthority: newAuthority.publicKey, 63 | primarySaleHappened: true, 64 | }); 65 | 66 | await sleep(20000); 67 | 68 | //after update 69 | info = await Account.getInfo(connection, metadata); 70 | metadataData = new Metadata(metadata, info).data; 71 | expect(metadataData.data.name).toEqual('xyzname'); 72 | expect(metadataData.updateAuthority).toEqual(newAuthority.publicKey.toBase58()); 73 | expect(metadataData.primarySaleHappened).toEqual(1); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/actions/utility/closeVault.test.ts: -------------------------------------------------------------------------------- 1 | import { NATIVE_MINT } from '@solana/spl-token'; 2 | import { Vault, VaultState } from '@metaplex-foundation/mpl-token-vault'; 3 | 4 | import { sleep } from '../../utils'; 5 | import { generateConnectionAndWallet } from '../shared'; 6 | import { closeVault, createVault, createExternalPriceAccount } from '../../../src/actions/utility'; 7 | 8 | describe('closing a Vault', () => { 9 | describe('success', () => { 10 | test('closes vault', async () => { 11 | const { connection, wallet } = await generateConnectionAndWallet(); 12 | let vault; 13 | 14 | const externalPriceAccountData = await createExternalPriceAccount({ connection, wallet }); 15 | 16 | const vaultResponse = await createVault({ 17 | connection, 18 | wallet, 19 | ...externalPriceAccountData, 20 | }); 21 | 22 | await sleep(1000); 23 | 24 | vault = await Vault.load(connection, vaultResponse.vault); 25 | expect(vault).toHaveProperty('data'); 26 | expect(vault.data.state).toEqual(VaultState.Inactive); 27 | 28 | await closeVault({ 29 | connection, 30 | wallet, 31 | vault: vaultResponse.vault, 32 | priceMint: NATIVE_MINT, 33 | }); 34 | 35 | await sleep(1000); 36 | 37 | vault = await Vault.load(connection, vaultResponse.vault); 38 | expect(vault).toHaveProperty('data'); 39 | expect(vault.data.state).toEqual(VaultState.Combined); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/actions/utility/createExternalPriceAccountLocal.test.ts: -------------------------------------------------------------------------------- 1 | import { generateConnectionAndWallet } from '../shared'; 2 | import { createExternalPriceAccount } from '../../../src/actions/utility'; 3 | 4 | describe('creating an external price account', () => { 5 | describe('success', () => { 6 | test('creates EPA', async () => { 7 | const { connection, wallet } = await generateConnectionAndWallet(); 8 | 9 | const externalPriceAccount = await createExternalPriceAccount({ 10 | connection, 11 | wallet, 12 | }); 13 | 14 | expect(externalPriceAccount).toHaveProperty('externalPriceAccount'); 15 | expect(externalPriceAccount).toHaveProperty('priceMint'); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/actions/utility/createVault.test.ts: -------------------------------------------------------------------------------- 1 | import { Vault, VaultState } from '@metaplex-foundation/mpl-token-vault'; 2 | 3 | import { sleep } from '../../utils'; 4 | import { createVault, createExternalPriceAccount } from '../../../src/actions/utility'; 5 | import { generateConnectionAndWallet } from '../shared'; 6 | 7 | describe('creating a Vault', () => { 8 | describe('success', () => { 9 | test('generates vault', async () => { 10 | const { connection, wallet } = await generateConnectionAndWallet(); 11 | 12 | const externalPriceAccountData = await createExternalPriceAccount({ connection, wallet }); 13 | 14 | const vaultResponse = await createVault({ 15 | connection, 16 | wallet, 17 | ...externalPriceAccountData, 18 | }); 19 | 20 | await sleep(1000); 21 | const vault = await Vault.load(connection, vaultResponse.vault); 22 | expect(vault).toHaveProperty('data'); 23 | expect(vault.data.state).toEqual(VaultState.Inactive); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/actions/utility/initAuction.test.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { NATIVE_MINT } from '@solana/spl-token'; 3 | import { 4 | Auction, 5 | PriceFloor, 6 | PriceFloorType, 7 | WinnerLimit, 8 | WinnerLimitType, 9 | } from '@metaplex-foundation/mpl-auction'; 10 | 11 | import { sleep } from '../../utils'; 12 | import { generateConnectionAndWallet } from '../shared'; 13 | import { createExternalPriceAccount, createVault, initAuction } from '../../../src/actions/utility'; 14 | 15 | describe('initAuction action', () => { 16 | test('making an auction for newly created vault', async () => { 17 | const { connection, wallet } = await generateConnectionAndWallet(); 18 | 19 | const externalPriceAccountData = await createExternalPriceAccount({ connection, wallet }); 20 | 21 | const { vault } = await createVault({ 22 | connection, 23 | wallet, 24 | ...externalPriceAccountData, 25 | }); 26 | 27 | const auctionSettings = { 28 | instruction: 1, 29 | tickSize: null, 30 | auctionGap: null, 31 | endAuctionAt: null, 32 | gapTickSizePercentage: null, 33 | winners: new WinnerLimit({ 34 | type: WinnerLimitType.Capped, 35 | usize: new BN(1), 36 | }), 37 | tokenMint: NATIVE_MINT.toBase58(), 38 | priceFloor: new PriceFloor({ type: PriceFloorType.Minimum }), 39 | }; 40 | 41 | const { auction } = await initAuction({ 42 | connection, 43 | wallet, 44 | vault, 45 | auctionSettings, 46 | }); 47 | 48 | await sleep(1000); 49 | 50 | const auctionInstance = await Auction.load(connection, auction); 51 | 52 | expect(auctionInstance).toHaveProperty('data'); 53 | expect(auctionInstance.data.tokenMint).toEqual(NATIVE_MINT.toBase58()); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/auction.test.ts: -------------------------------------------------------------------------------- 1 | import { Auction, AuctionState, AuctionExtended } from '@metaplex-foundation/mpl-auction'; 2 | import { Connection } from '../src'; 3 | import { 4 | AUCTION_EXTENDED_PUBKEY, 5 | AUCTION_MANAGER_PUBKEY, 6 | AUCTION_PUBKEY, 7 | NETWORK, 8 | VAULT_PUBKEY, 9 | } from './utils'; 10 | 11 | describe('Auction', () => { 12 | let connection: Connection; 13 | 14 | beforeAll(() => { 15 | connection = new Connection(NETWORK); 16 | }); 17 | 18 | describe('Auction', () => { 19 | test('load', async () => { 20 | const auction = await Auction.load(connection, AUCTION_PUBKEY); 21 | 22 | expect(auction.pubkey).toEqual(AUCTION_PUBKEY); 23 | expect(auction.data.state).toEqual(AuctionState.Started); 24 | }); 25 | 26 | test('findMany', async () => { 27 | const auctions = await Auction.findMany(connection, { authority: AUCTION_MANAGER_PUBKEY }); 28 | expect(auctions[0].data.authority).toEqual(AUCTION_MANAGER_PUBKEY.toString()); 29 | }); 30 | 31 | test('getBidderPots', async () => { 32 | const auction = await Auction.load(connection, AUCTION_PUBKEY); 33 | const bidderPots = await auction.getBidderPots(connection); 34 | 35 | expect(bidderPots[0].data.auctionAct).toEqual(AUCTION_PUBKEY.toString()); 36 | }); 37 | 38 | test('getBidderMetadata', async () => { 39 | const auction = await Auction.load(connection, AUCTION_PUBKEY); 40 | const bidderMetadata = await auction.getBidderMetadata(connection); 41 | 42 | expect(bidderMetadata[0].data.auctionPubkey).toEqual(AUCTION_PUBKEY.toString()); 43 | }); 44 | }); 45 | 46 | describe('Auction Extended', () => { 47 | test('getPDA', async () => { 48 | const auctionExtendedPDA = await AuctionExtended.getPDA(VAULT_PUBKEY); 49 | 50 | expect(auctionExtendedPDA).toEqual(AUCTION_EXTENDED_PUBKEY); 51 | }); 52 | 53 | test('load', async () => { 54 | const auctionExtended = await AuctionExtended.load(connection, AUCTION_EXTENDED_PUBKEY); 55 | 56 | expect(auctionExtended.pubkey).toEqual(AUCTION_EXTENDED_PUBKEY); 57 | expect(auctionExtended.data.totalUncancelledBids).toBeDefined(); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/conversion.test.ts: -------------------------------------------------------------------------------- 1 | import { Coingecko, Currency } from '../src/providers/conversion'; 2 | import axios, { AxiosResponse } from 'axios'; 3 | 4 | jest.mock('axios'); 5 | 6 | const mockedAxios = axios as jest.MockedFunction; 7 | 8 | describe('Conversion', () => { 9 | let coingecko: Coingecko; 10 | 11 | beforeAll(() => { 12 | // why not just do a static method? well, even though our current Coingecko implementation 13 | // for ConversionRateProvider is fully public API. Someone else might want to create a 14 | // class that has API keys or secrets, and would need instantiation. We don't want to create 15 | // an inflexible interface/ 16 | coingecko = new Coingecko(); 17 | }); 18 | 19 | describe('Coingecko', () => { 20 | test('getRate single currency', async () => { 21 | const mockedResponse: AxiosResponse = { 22 | data: { 23 | arweave: { 24 | usd: 53.44, 25 | }, 26 | }, 27 | status: 200, 28 | statusText: 'OK', 29 | headers: {}, 30 | config: {}, 31 | }; 32 | mockedAxios.mockResolvedValue(mockedResponse); 33 | const result = await coingecko.getRate(Currency.AR, Currency.USD); 34 | expect(result[0].from).toEqual(Currency.AR); 35 | expect(result[0].to).toEqual(Currency.USD); 36 | expect(result[0].rate).toEqual(53.44); 37 | }); 38 | 39 | test('getRate multiple to single currency', async () => { 40 | const mockedResponse: AxiosResponse = { 41 | data: { 42 | arweave: { 43 | usd: 53.44, 44 | }, 45 | solana: { 46 | usd: 203.81, 47 | }, 48 | }, 49 | status: 200, 50 | statusText: 'OK', 51 | headers: {}, 52 | config: {}, 53 | }; 54 | mockedAxios.mockResolvedValue(mockedResponse); 55 | 56 | const result = await coingecko.getRate([Currency.AR, Currency.SOL], Currency.USD); 57 | expect(result[0].from).toEqual(Currency.AR); 58 | expect(result[0].to).toEqual(Currency.USD); 59 | expect(result[0].rate).toEqual(53.44); 60 | 61 | expect(result[1].from).toEqual(Currency.SOL); 62 | expect(result[1].to).toEqual(Currency.USD); 63 | expect(result[1].rate).toEqual(203.81); 64 | }); 65 | 66 | test('getRate single to multiple currencies', async () => { 67 | const mockedResponse: AxiosResponse = { 68 | data: { 69 | arweave: { 70 | usd: 53.44, 71 | eur: 46.05, 72 | }, 73 | }, 74 | status: 200, 75 | statusText: 'OK', 76 | headers: {}, 77 | config: {}, 78 | }; 79 | mockedAxios.mockResolvedValue(mockedResponse); 80 | 81 | const result = await coingecko.getRate(Currency.AR, [Currency.USD, Currency.EUR]); 82 | expect(result[0].from).toEqual(Currency.AR); 83 | expect(result[0].to).toEqual(Currency.USD); 84 | expect(result[0].rate).toEqual(53.44); 85 | 86 | expect(result[1].from).toEqual(Currency.AR); 87 | expect(result[1].to).toEqual(Currency.EUR); 88 | expect(result[1].rate).toEqual(46.05); 89 | }); 90 | 91 | test('getRate multiple to multiple currencies', async () => { 92 | const mockedResponse: AxiosResponse = { 93 | data: { 94 | arweave: { 95 | usd: 53.44, 96 | eur: 46.05, 97 | }, 98 | solana: { 99 | usd: 203.81, 100 | eur: 175.69, 101 | }, 102 | }, 103 | status: 200, 104 | statusText: 'OK', 105 | headers: {}, 106 | config: {}, 107 | }; 108 | mockedAxios.mockResolvedValue(mockedResponse); 109 | 110 | const result = await coingecko.getRate( 111 | [Currency.AR, Currency.SOL], 112 | [Currency.USD, Currency.EUR], 113 | ); 114 | 115 | expect(result[0].from).toEqual(Currency.AR); 116 | expect(result[0].to).toEqual(Currency.USD); 117 | expect(result[0].rate).toEqual(53.44); 118 | 119 | expect(result[1].from).toEqual(Currency.AR); 120 | expect(result[1].to).toEqual(Currency.EUR); 121 | expect(result[1].rate).toEqual(46.05); 122 | 123 | expect(result[2].from).toEqual(Currency.SOL); 124 | expect(result[2].to).toEqual(Currency.USD); 125 | expect(result[2].rate).toEqual(203.81); 126 | 127 | expect(result[3].from).toEqual(Currency.SOL); 128 | expect(result[3].to).toEqual(Currency.EUR); 129 | expect(result[3].rate).toEqual(175.69); 130 | }); 131 | 132 | test('translateCurrency test transformation', () => { 133 | expect(Coingecko.translateCurrency(Currency.AR)).toBe('arweave'); 134 | expect(Coingecko.translateCurrency(Currency.SOL)).toBe('solana'); 135 | expect(Coingecko.translateCurrency(Currency.USD)).toBe('usd'); 136 | expect(Coingecko.translateCurrency(Currency.EUR)).toBe('eur'); 137 | 138 | expect(() => { 139 | Coingecko.translateCurrency(null as unknown as Currency); 140 | }).toThrowError(); 141 | }); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/metadata.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '../src'; 2 | import { MASTER_EDITION_PUBKEY, METADATA_PUBKEY, NETWORK, STORE_OWNER_PUBKEY } from './utils'; 3 | import { Metadata, MetadataKey, MasterEdition } from '@metaplex-foundation/mpl-token-metadata'; 4 | 5 | describe.skip('Metadata', () => { 6 | let connection: Connection; 7 | 8 | beforeAll(() => { 9 | connection = new Connection(NETWORK); 10 | }); 11 | 12 | describe('Metadata', () => { 13 | test('load', async () => { 14 | const metadata = await Metadata.load(connection, METADATA_PUBKEY); 15 | 16 | expect(metadata.pubkey).toEqual(METADATA_PUBKEY); 17 | expect(metadata.data.key).toEqual(MetadataKey.MetadataV1); 18 | }); 19 | 20 | test('findMany', async () => { 21 | const metadata = await Metadata.findMany(connection, { 22 | creators: [STORE_OWNER_PUBKEY], 23 | }); 24 | 25 | expect(metadata[0].data.key).toBe(MetadataKey.MetadataV1); 26 | }, 10000); 27 | }); 28 | 29 | describe('Master edition', () => { 30 | test('getEditions', async () => { 31 | const masterEdition = await MasterEdition.load(connection, MASTER_EDITION_PUBKEY); 32 | const editions = await masterEdition.getEditions(connection); 33 | 34 | expect(editions[0].data.parent).toEqual(MASTER_EDITION_PUBKEY.toString()); 35 | }, 10000); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/metaplex.test.ts: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { Connection } from '../src'; 3 | import { Auction } from '@metaplex-foundation/mpl-auction'; 4 | import { 5 | Store, 6 | MetaplexKey, 7 | AuctionManager, 8 | PayoutTicket, 9 | } from '@metaplex-foundation/mpl-metaplex'; 10 | import { 11 | AUCTION_MANAGER_PUBKEY, 12 | AUCTION_PUBKEY, 13 | STORE_OWNER_PUBKEY, 14 | STORE_PUBKEY, 15 | VAULT_PUBKEY, 16 | } from './utils'; 17 | 18 | describe('Metaplex', () => { 19 | let connection: Connection; 20 | 21 | jest.setTimeout(80000); 22 | 23 | beforeAll(() => { 24 | connection = new Connection('devnet'); 25 | }); 26 | 27 | describe('Store', () => { 28 | test('getPDA', async () => { 29 | const storeId = await Store.getPDA(STORE_OWNER_PUBKEY); 30 | 31 | expect(storeId).toEqual(STORE_PUBKEY); 32 | }); 33 | 34 | test('load', async () => { 35 | const store = await Store.load(connection, STORE_PUBKEY); 36 | 37 | expect(store.data.key).toEqual(MetaplexKey.StoreV1); 38 | }); 39 | 40 | test('getAuctionManagers', async () => { 41 | const store = await Store.load(connection, STORE_PUBKEY); 42 | const auctionManagers = await store.getAuctionManagers(connection); 43 | 44 | expect(auctionManagers[0].data.store).toEqual(STORE_PUBKEY.toString()); 45 | }); 46 | }); 47 | 48 | describe('Auction Manager', () => { 49 | test('getPDA', async () => { 50 | const auctionPDA = await Auction.getPDA(VAULT_PUBKEY); 51 | const auctionManagerPDA = await AuctionManager.getPDA(auctionPDA); 52 | 53 | expect(auctionPDA).toEqual(AUCTION_PUBKEY); 54 | expect(auctionManagerPDA).toEqual(AUCTION_MANAGER_PUBKEY); 55 | }); 56 | 57 | test('load', async () => { 58 | const auctionManager = await AuctionManager.load(connection, AUCTION_MANAGER_PUBKEY); 59 | 60 | expect(auctionManager.data.key).toEqual(MetaplexKey.AuctionManagerV2); 61 | }); 62 | 63 | test('findMany', async () => { 64 | const auctionManagers = await AuctionManager.findMany(connection, { 65 | store: STORE_PUBKEY, 66 | authority: STORE_OWNER_PUBKEY, 67 | }); 68 | expect(auctionManagers[0].data.store).toEqual(STORE_PUBKEY.toString()); 69 | }); 70 | 71 | test('getAuction', async () => { 72 | const auctionManager = await AuctionManager.load(connection, AUCTION_MANAGER_PUBKEY); 73 | const auction = await auctionManager.getAuction(connection); 74 | 75 | expect(auction.pubkey).toEqual(AUCTION_PUBKEY); 76 | }); 77 | 78 | test('getBidRedemptionTickets', async () => { 79 | const auctionManager = await AuctionManager.load(connection, AUCTION_MANAGER_PUBKEY); 80 | await auctionManager.getBidRedemptionTickets(connection); 81 | }); 82 | }); 83 | 84 | describe('Payout Ticket', () => { 85 | test('load', async () => {}); 86 | 87 | test('getPayoutTickets', async () => { 88 | await PayoutTicket.getPayoutTicketsByRecipient(connection, STORE_OWNER_PUBKEY); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/setup/build.ts: -------------------------------------------------------------------------------- 1 | import stream from 'stream'; 2 | import path from 'path'; 3 | import { promisify } from 'util'; 4 | import { execSync } from 'child_process'; 5 | import { renameSync, rmSync, createWriteStream, mkdirSync } from 'fs'; 6 | import { tmpTestDir } from '../utils'; 7 | import axios from 'axios'; 8 | import StreamZip from 'node-stream-zip'; 9 | 10 | const rustGithubRepository = 'metaplex-program-library'; 11 | const repositoryDir = `${rustGithubRepository}-master`; 12 | const rustProgramsRepository = `https://github.com/metaplex-foundation/${rustGithubRepository}/archive/refs/heads/master.zip`; 13 | 14 | const isLocal = process.env.LOCAL_MPL === '1'; 15 | 16 | function outputAndExitError(error: Error): void { 17 | console.error(`${error.name}: ${error.message}`); 18 | process.exit(1); 19 | } 20 | 21 | async function build() { 22 | const programs: string[] = [ 23 | 'auction/program', 24 | 'token-metadata/program', 25 | 'token-vault/program', 26 | 'metaplex/program', 27 | ]; 28 | rmSync(tmpTestDir, { recursive: true, force: true }); 29 | mkdirSync(tmpTestDir); 30 | 31 | async function downloadFile(path: string, destinationPath: string): Promise { 32 | const done = promisify(stream.finished); 33 | const writer = createWriteStream(destinationPath); 34 | try { 35 | const response = await axios({ method: 'get', url: path, responseType: 'stream' }); 36 | response.data.pipe(writer); 37 | await done(writer); 38 | } catch (error) { 39 | outputAndExitError(error); 40 | } 41 | } 42 | 43 | if (!isLocal) { 44 | await downloadFile(rustProgramsRepository, `${tmpTestDir}/master.zip`); 45 | 46 | const zip = new StreamZip.async({ file: `${tmpTestDir}/master.zip` }); 47 | 48 | try { 49 | await zip.extract(null, tmpTestDir); 50 | await zip.close(); 51 | } catch (error) { 52 | outputAndExitError(error); 53 | } 54 | } 55 | 56 | const currentDir = process.cwd(); 57 | 58 | programs.forEach((directory) => { 59 | const dir = isLocal 60 | ? path.resolve(currentDir, `../metaplex-program-library/${directory}`) 61 | : `${tmpTestDir}/${repositoryDir}/${directory}`; 62 | process.chdir(dir); 63 | execSync(`cargo build-bpf`); 64 | }); 65 | 66 | if (!isLocal) { 67 | renameSync(`${tmpTestDir}/${repositoryDir}`, `${tmpTestDir}/rust`); 68 | } 69 | 70 | process.chdir(currentDir); 71 | } 72 | 73 | build(); 74 | -------------------------------------------------------------------------------- /test/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | import axios from 'axios'; 3 | import * as fs from 'fs'; 4 | import FormData from 'form-data'; 5 | 6 | import { ArweaveStorage, ArweaveUploadResult, ConversionRatePair, Currency } from '../src'; 7 | import { NETWORK } from './utils'; 8 | 9 | const UPLOAD_ENDPOINT = 'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFile4'; 10 | const ENV = NETWORK; 11 | 12 | const EXAMPLE_SUCCESSFUL_RESPONSE: ArweaveUploadResult = { 13 | messages: [ 14 | { 15 | filename: 'metaplex.jpg', 16 | status: 'success', 17 | transactionId: 'yX4klt1AjkEwSAIA5c123w7C1lyr-asdNZz8VfYmQhI', 18 | }, 19 | ], 20 | }; 21 | 22 | const EXAMPLE_FAILED_RESPONSE: ArweaveUploadResult = { 23 | messages: [ 24 | { 25 | filename: 'metaplex.jpg', 26 | status: 'fail', 27 | error: 'Failed', 28 | }, 29 | ], 30 | error: 'Failed', 31 | }; 32 | 33 | describe('Storage', () => { 34 | let files: Map; 35 | 36 | beforeAll(() => { 37 | files = new Map([['metaplex.jpg', fs.readFileSync('./test/uploads/metaplex.jpg')]]); 38 | }); 39 | 40 | describe('arweave', () => { 41 | test('getAssetCostToStore', async () => { 42 | const rates: ConversionRatePair[] = [ 43 | { from: Currency.AR, to: Currency.USD, rate: 55.46 }, 44 | { from: Currency.SOL, to: Currency.USD, rate: 159.8 }, 45 | ]; 46 | 47 | const storage = await new ArweaveStorage({ 48 | endpoint: UPLOAD_ENDPOINT, 49 | env: ENV, 50 | }); 51 | 52 | const lamports = await storage.getAssetCostToStore(files, rates[0].rate, rates[1].rate); 53 | expect(lamports).toEqual(expect.any(Number)); 54 | }); 55 | 56 | describe('upload', () => { 57 | test('successful', async () => { 58 | const storage = await new ArweaveStorage({ 59 | endpoint: UPLOAD_ENDPOINT, 60 | env: ENV, 61 | }); 62 | 63 | axios.post = jest.fn().mockResolvedValue({ data: EXAMPLE_SUCCESSFUL_RESPONSE }); 64 | 65 | const result = await storage.upload(files, 'mintKey', 'txId'); 66 | expect(axios.post).toBeCalledWith(UPLOAD_ENDPOINT, expect.any(FormData)); 67 | 68 | expect(result).toEqual(EXAMPLE_SUCCESSFUL_RESPONSE); 69 | }); 70 | 71 | test('failed', async () => { 72 | const storage = await new ArweaveStorage({ 73 | endpoint: UPLOAD_ENDPOINT, 74 | env: ENV, 75 | }); 76 | 77 | axios.post = jest.fn().mockResolvedValue({ data: EXAMPLE_FAILED_RESPONSE }); 78 | 79 | await expect(storage.upload(files, 'mintKey', 'txId')).rejects.toEqual( 80 | new Error(EXAMPLE_FAILED_RESPONSE.error), 81 | ); 82 | }); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/transactions/__snapshots__/auction.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Auction transactions CreateAuction 1`] = `"{\\"type\\":\\"Buffer\\",\\"data\\":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,3,7,93,135,29,120,250,13,109,171,35,122,77,213,58,178,208,153,32,156,150,192,153,14,177,253,172,197,7,247,216,61,254,175,187,209,148,182,34,149,175,173,192,85,175,252,231,130,76,40,175,177,44,111,250,168,3,236,149,34,236,19,46,9,66,138,155,76,202,43,34,141,168,115,82,176,65,170,75,45,110,185,97,234,236,43,234,0,144,234,95,255,33,107,30,38,145,153,130,131,135,32,113,60,44,78,85,97,231,146,60,57,129,72,71,229,99,140,205,42,175,85,79,60,2,195,134,224,132,55,6,167,213,23,25,44,92,81,33,140,201,76,61,74,241,127,88,218,238,8,155,161,253,68,227,219,217,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,175,169,191,10,132,153,239,245,177,12,199,221,36,59,224,20,103,70,219,249,150,85,157,145,130,193,93,188,24,246,87,131,79,219,30,88,36,153,69,200,48,40,136,227,246,197,102,213,141,122,4,186,57,98,62,139,154,145,152,59,13,211,149,1,6,5,1,2,3,4,5,168,1,1,1,1,0,0,0,0,0,0,0,1,195,115,146,97,0,0,0,0,1,30,0,0,0,0,0,0,0,113,178,77,47,3,206,165,188,130,225,172,175,199,64,162,55,188,104,159,223,218,116,88,247,167,60,131,209,170,44,208,98,70,65,21,141,143,89,143,146,210,57,211,217,223,24,20,153,223,130,92,14,226,188,44,87,44,70,212,74,67,174,21,67,151,236,219,224,54,47,112,197,46,29,246,29,105,42,66,214,178,206,61,219,206,132,245,159,159,124,100,29,253,0,131,115,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,0,0,0,0,0,0,1,1]}"`; 4 | 5 | exports[`Auction transactions CreateAuctionV2 1`] = `"{\\"type\\":\\"Buffer\\",\\"data\\":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,3,7,93,135,29,120,250,13,109,171,35,122,77,213,58,178,208,153,32,156,150,192,153,14,177,253,172,197,7,247,216,61,254,175,187,209,148,182,34,149,175,173,192,85,175,252,231,130,76,40,175,177,44,111,250,168,3,236,149,34,236,19,46,9,66,138,155,76,202,43,34,141,168,115,82,176,65,170,75,45,110,185,97,234,236,43,234,0,144,234,95,255,33,107,30,38,145,153,130,131,135,32,113,60,44,78,85,97,231,146,60,57,129,72,71,229,99,140,205,42,175,85,79,60,2,195,134,224,132,55,6,167,213,23,25,44,92,81,33,140,201,76,61,74,241,127,88,218,238,8,155,161,253,68,227,219,217,138,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,175,169,191,10,132,153,239,245,177,12,199,221,36,59,224,20,103,70,219,249,150,85,157,145,130,193,93,188,24,246,87,131,79,219,30,88,36,153,69,200,48,40,136,227,246,197,102,213,141,122,4,186,57,98,62,139,154,145,152,59,13,211,149,1,6,5,1,2,3,4,5,178,1,7,1,1,0,0,0,0,0,0,0,1,195,115,146,97,0,0,0,0,1,30,0,0,0,0,0,0,0,113,178,77,47,3,206,165,188,130,225,172,175,199,64,162,55,188,104,159,223,218,116,88,247,167,60,131,209,170,44,208,98,70,65,21,141,143,89,143,146,210,57,211,217,223,24,20,153,223,130,92,14,226,188,44,87,44,70,212,74,67,174,21,67,151,236,219,224,54,47,112,197,46,29,246,29,105,42,66,214,178,206,61,219,206,132,245,159,159,124,100,29,253,0,131,115,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,0,0,0,0,0,0,1,1,1,0,202,154,59,0,0,0,0,0]}"`; 6 | -------------------------------------------------------------------------------- /test/transactions/__snapshots__/metaplex.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Metaplex transactions EndAuction(reveal = null) 1`] = `"{\\"type\\":\\"Buffer\\",\\"data\\":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,5,9,93,135,29,120,250,13,109,171,35,122,77,213,58,178,208,153,32,156,150,192,153,14,177,253,172,197,7,247,216,61,254,175,207,140,94,235,253,88,52,215,223,250,161,132,68,41,246,210,18,149,185,76,29,184,100,232,184,10,144,98,156,162,27,90,233,202,105,2,153,20,72,22,139,175,230,106,27,70,0,106,112,120,169,110,223,61,168,80,141,241,227,242,31,218,180,236,155,76,202,43,34,141,168,115,82,176,65,170,75,45,110,185,97,234,236,43,234,0,144,234,95,255,33,107,30,38,145,153,130,131,135,32,113,60,44,78,85,97,231,146,60,57,129,72,71,229,99,140,205,42,175,85,79,60,2,195,134,224,132,55,183,200,146,174,179,202,255,59,237,71,198,14,93,80,130,36,243,239,156,123,105,168,4,229,71,114,217,106,120,80,194,62,8,175,169,191,10,132,153,239,245,177,12,199,221,36,59,224,20,103,70,219,249,150,85,157,145,130,193,93,188,24,246,87,6,167,213,23,24,199,116,201,40,86,99,152,105,29,94,182,139,94,184,163,155,75,109,92,115,85,91,33,0,0,0,0,12,11,9,188,163,72,31,129,1,245,97,110,85,233,233,212,126,91,245,121,77,102,76,9,137,66,49,240,175,59,106,23,131,79,219,30,88,36,153,69,200,48,40,136,227,246,197,102,213,141,122,4,186,57,98,62,139,154,145,152,59,13,211,149,1,8,7,2,3,4,1,5,6,7,2,20,0]}"`; 4 | 5 | exports[`Metaplex transactions RedeemPrintingV2Bid 1`] = `"{\\"type\\":\\"Buffer\\",\\"data\\":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,12,25,93,135,29,120,250,13,109,171,35,122,77,213,58,178,208,153,32,156,150,192,153,14,177,253,172,197,7,247,216,61,254,175,155,76,202,43,34,141,168,115,82,176,65,170,75,45,110,185,97,234,236,43,234,0,144,234,95,255,33,107,30,38,145,153,47,149,134,43,4,86,112,205,34,142,120,147,252,26,2,86,248,99,18,46,101,201,146,251,42,49,244,157,217,16,26,10,56,14,227,34,118,68,35,153,43,122,125,228,180,23,227,117,141,24,61,49,174,102,137,121,161,240,55,57,17,202,119,9,47,149,134,43,4,86,112,205,34,142,120,147,252,26,2,86,248,99,18,46,101,201,146,251,42,49,244,157,217,16,28,79,101,93,28,19,203,90,149,199,215,78,46,179,89,233,42,199,254,72,173,157,164,132,109,200,23,214,238,28,156,187,225,122,151,236,219,224,54,47,112,197,46,29,246,29,105,42,66,214,178,206,61,219,206,132,245,159,159,124,100,29,253,0,131,115,91,40,146,183,9,11,253,128,231,91,83,123,247,28,235,55,17,175,45,47,10,130,125,182,211,90,32,101,147,100,233,93,70,65,21,141,143,89,143,146,210,57,211,217,223,24,20,153,223,130,92,14,226,188,44,87,44,70,198,23,191,63,49,227,47,149,70,38,70,6,80,167,253,84,157,123,255,6,98,232,148,71,177,174,211,232,200,92,171,149,91,23,240,14,230,10,201,96,29,68,155,188,208,7,192,116,93,255,212,132,172,139,142,121,64,99,119,55,85,243,102,210,136,30,72,187,160,175,113,178,77,47,3,206,165,188,130,225,172,175,199,64,162,55,188,104,159,223,218,116,88,247,167,60,131,209,170,44,208,98,91,40,146,183,9,11,253,128,231,91,83,123,247,28,234,83,143,113,239,17,57,201,33,165,148,134,206,82,76,100,233,93,47,149,70,38,70,6,80,167,253,84,157,123,255,6,98,232,148,71,177,174,211,232,200,91,74,216,112,157,217,16,26,10,171,212,12,59,44,148,107,164,115,140,56,141,54,163,71,180,121,31,117,158,149,162,185,74,82,241,134,179,82,183,83,189,187,209,148,182,34,149,175,173,192,85,175,252,231,130,76,40,175,177,44,111,250,168,3,236,149,34,236,19,46,9,66,138,6,221,246,225,215,101,161,147,217,203,225,70,206,235,121,172,28,180,133,237,95,91,55,145,58,140,245,133,126,255,0,169,13,186,28,52,26,119,115,94,210,96,195,36,182,190,250,187,9,244,245,52,7,50,47,49,172,28,41,212,233,209,175,49,11,112,101,177,227,209,124,69,56,157,82,127,107,4,195,205,88,184,108,115,26,160,253,181,73,182,209,188,3,248,41,70,183,200,146,174,179,202,255,59,237,71,198,14,93,80,130,36,243,239,156,123,105,168,4,229,71,114,217,106,120,80,194,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,167,213,23,25,44,92,81,33,140,201,76,61,74,241,127,88,218,238,8,155,161,253,68,227,219,217,138,0,0,0,0,171,212,12,59,44,148,107,164,115,140,35,199,105,0,159,210,184,40,107,188,31,1,100,108,207,75,166,17,226,183,83,189,130,131,135,32,113,60,44,78,85,97,231,146,60,57,129,72,71,229,99,140,205,42,175,85,79,60,2,195,134,224,132,55,12,11,9,188,163,72,31,129,1,245,97,110,85,233,233,212,126,91,245,121,77,102,76,9,137,66,49,240,175,59,106,23,131,79,219,30,88,36,153,69,200,48,40,136,227,246,197,102,213,141,122,4,186,57,98,62,139,154,145,152,59,13,211,149,1,24,26,1,2,3,4,5,6,13,1,14,15,0,16,17,18,19,20,21,7,8,9,10,11,12,0,22,23,17,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"`; 6 | -------------------------------------------------------------------------------- /test/transactions/auction.test.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { LAMPORTS_PER_SOL } from '@solana/web3.js'; 3 | import { 4 | AUCTION_EXTENDED_PUBKEY, 5 | AUCTION_PUBKEY, 6 | FEE_PAYER, 7 | mockTransaction, 8 | NEW_AUTHORITY_PUBKEY, 9 | serializeConfig, 10 | TOKEN_MINT_PUBKEY, 11 | VAULT_PUBKEY, 12 | } from '../utils'; 13 | import { 14 | CreateAuctionV2, 15 | CreateAuctionV2Args, 16 | CreateAuction, 17 | CreateAuctionArgs, 18 | PriceFloor, 19 | PriceFloorType, 20 | WinnerLimit, 21 | WinnerLimitType, 22 | } from '@metaplex-foundation/mpl-auction'; 23 | 24 | describe('Auction transactions', () => { 25 | test('CreateAuction', async () => { 26 | const data = new CreateAuction(mockTransaction, { 27 | auction: AUCTION_PUBKEY, 28 | auctionExtended: AUCTION_EXTENDED_PUBKEY, 29 | creator: FEE_PAYER.publicKey, 30 | args: new CreateAuctionArgs({ 31 | winners: new WinnerLimit({ type: WinnerLimitType.Capped, usize: new BN(1) }), 32 | endAuctionAt: new BN(1636987843), 33 | auctionGap: new BN(30), 34 | tokenMint: TOKEN_MINT_PUBKEY.toString(), 35 | authority: NEW_AUTHORITY_PUBKEY.toString(), 36 | resource: VAULT_PUBKEY.toString(), 37 | priceFloor: new PriceFloor({ type: PriceFloorType.Minimum }), 38 | tickSize: new BN(10), 39 | gapTickSizePercentage: 1, 40 | }), 41 | }); 42 | 43 | const serializedData = data.serialize(serializeConfig); 44 | expect(JSON.stringify(serializedData)).toMatchSnapshot(); 45 | }); 46 | 47 | test('CreateAuctionV2', async () => { 48 | const data = new CreateAuctionV2(mockTransaction, { 49 | auction: AUCTION_PUBKEY, 50 | auctionExtended: AUCTION_EXTENDED_PUBKEY, 51 | creator: FEE_PAYER.publicKey, 52 | args: new CreateAuctionV2Args({ 53 | winners: new WinnerLimit({ type: WinnerLimitType.Capped, usize: new BN(1) }), 54 | endAuctionAt: new BN(1636987843), 55 | auctionGap: new BN(30), 56 | tokenMint: TOKEN_MINT_PUBKEY.toString(), 57 | authority: NEW_AUTHORITY_PUBKEY.toString(), 58 | resource: VAULT_PUBKEY.toString(), 59 | priceFloor: new PriceFloor({ type: PriceFloorType.Minimum }), 60 | tickSize: new BN(10), 61 | gapTickSizePercentage: 1, 62 | instantSalePrice: new BN(LAMPORTS_PER_SOL), 63 | name: null, 64 | }), 65 | }); 66 | 67 | const serializedData = data.serialize(serializeConfig); 68 | expect(JSON.stringify(serializedData)).toMatchSnapshot(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/transactions/metaplex.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AUCTION_EXTENDED_PUBKEY, 3 | AUCTION_MANAGER_PUBKEY, 4 | AUCTION_PUBKEY, 5 | BID_METADATA_PUBKEY, 6 | BID_REDEMPTION_PUBKEY, 7 | CURRENT_AUTHORITY_PUBKEY, 8 | EDITION_MARK_PUBKEY, 9 | FEE_PAYER, 10 | MASTER_EDITION_PUBKEY, 11 | METADATA_PUBKEY, 12 | mockTransaction, 13 | NEW_EDITION_PUBKEY, 14 | NEW_METADATA_PUBKEY, 15 | PRIZE_TRACKING_TICKET_PUBKEY, 16 | SAFETY_DEPOSIT_BOX_PUBKEY, 17 | SAFETY_DEPOSIT_CONFIG_PUBKEY, 18 | SAFETY_DEPOSIT_TOKEN_STORE_PUBKEY, 19 | serializeConfig, 20 | STORE_PUBKEY, 21 | TOKEN_ACCOUNT_PUBKEY, 22 | TOKEN_MINT_PUBKEY, 23 | VAULT_PUBKEY, 24 | } from '../utils'; 25 | import { EndAuction, RedeemPrintingV2Bid } from '@metaplex-foundation/mpl-metaplex'; 26 | import BN from 'bn.js'; 27 | 28 | describe('Metaplex transactions', () => { 29 | test('EndAuction(reveal = null)', async () => { 30 | const data = new EndAuction(mockTransaction, { 31 | auctionManager: AUCTION_MANAGER_PUBKEY, 32 | auction: AUCTION_PUBKEY, 33 | auctionExtended: AUCTION_EXTENDED_PUBKEY, 34 | store: STORE_PUBKEY, 35 | auctionManagerAuthority: CURRENT_AUTHORITY_PUBKEY, 36 | reveal: null, 37 | }); 38 | 39 | const serializedData = data.serialize(serializeConfig); 40 | expect(JSON.stringify(serializedData)).toMatchSnapshot(); 41 | }); 42 | 43 | // TODO: find out how to correctly define schema for (u64, u64) Rust type 44 | // test('EndAuction(reveal = BN[])', async () => { 45 | // const data = new EndAuction(mockTransaction, { 46 | // auctionManager: AUCTION_MANAGER_PUBKEY, 47 | // auction: AUCTION_PUBKEY, 48 | // auctionExtended: AUCTION_EXTENDED_PUBKEY, 49 | // store: STORE_PUBKEY, 50 | // auctionManagerAuthority: CURRENT_AUTHORITY_PUBKEY, 51 | // reveal: [new BN(1), new BN(1)], 52 | // }); 53 | 54 | // const serializedData = data.serialize(serializeConfig); 55 | // expect(JSON.stringify(serializedData)).toMatchSnapshot(); 56 | // }); 57 | 58 | test('RedeemPrintingV2Bid', async () => { 59 | const data = new RedeemPrintingV2Bid(mockTransaction, { 60 | store: STORE_PUBKEY, 61 | vault: VAULT_PUBKEY, 62 | auction: AUCTION_PUBKEY, 63 | auctionManager: AUCTION_PUBKEY, 64 | bidRedemption: BID_REDEMPTION_PUBKEY, 65 | bidMetadata: BID_METADATA_PUBKEY, 66 | safetyDepositTokenStore: SAFETY_DEPOSIT_TOKEN_STORE_PUBKEY, 67 | destination: TOKEN_ACCOUNT_PUBKEY, 68 | safetyDeposit: SAFETY_DEPOSIT_BOX_PUBKEY, 69 | bidder: FEE_PAYER.publicKey, 70 | safetyDepositConfig: SAFETY_DEPOSIT_CONFIG_PUBKEY, 71 | auctionExtended: AUCTION_EXTENDED_PUBKEY, 72 | newMint: TOKEN_MINT_PUBKEY, 73 | newEdition: NEW_EDITION_PUBKEY, 74 | newMetadata: NEW_METADATA_PUBKEY, 75 | metadata: METADATA_PUBKEY, 76 | masterEdition: MASTER_EDITION_PUBKEY, 77 | editionMark: EDITION_MARK_PUBKEY, 78 | prizeTrackingTicket: PRIZE_TRACKING_TICKET_PUBKEY, 79 | winIndex: new BN(0), 80 | editionOffset: new BN(0), 81 | }); 82 | 83 | const serializedData = data.serialize(serializeConfig); 84 | expect(JSON.stringify(serializedData)).toMatchSnapshot(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/uploads/metaplex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaplex-foundation/js-deprecated/7e4a3ff8c606ebaab21ad157c8b06b0676ce0a37/test/uploads/metaplex.jpg -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Keypair, PublicKey, TransactionCtorFields } from '@solana/web3.js'; 3 | import { tmpdir } from 'os'; 4 | import { readFileSync } from 'fs'; 5 | 6 | export const NETWORK = 'devnet'; 7 | 8 | // Devnet fee payer 9 | export const FEE_PAYER = Keypair.fromSecretKey( 10 | new Uint8Array([ 11 | 225, 60, 117, 68, 123, 252, 1, 200, 41, 251, 54, 121, 6, 167, 204, 18, 140, 168, 206, 74, 254, 12 | 156, 230, 10, 212, 124, 162, 85, 120, 78, 122, 106, 187, 209, 148, 182, 34, 149, 175, 173, 192, 13 | 85, 175, 252, 231, 130, 76, 40, 175, 177, 44, 111, 250, 168, 3, 236, 149, 34, 236, 19, 46, 9, 14 | 66, 138, 15 | ]), 16 | ); 17 | 18 | export const STORE_OWNER_PUBKEY = new PublicKey('7hKMAoCYJuBnBLmVTjswu7m6jcwyE8MYAP5hPijUT6nd'); 19 | export const STORE_PUBKEY = new PublicKey('DNQzo4Aggw8PneX7BGY7niEkB8wfNJwx6DpV9BLBUUFF'); 20 | export const AUCTION_MANAGER_PUBKEY = new PublicKey('Gjd1Mo8KLEgywxMKaDRhaoD2Fu8bzoVLZ8H7v761XXkf'); 21 | export const AUCTION_PUBKEY = new PublicKey('BTE7AqJn4aG2MKZnaSTEgbQ4aCgPTDmphs5uxDnuDqvQ'); 22 | export const AUCTION_EXTENDED_PUBKEY = new PublicKey( 23 | '9nUKpweEWpk5mQiBzxYWB62dhQR5NtaZDShccqsWnPGa', 24 | ); 25 | export const VAULT_PUBKEY = new PublicKey('BE43QppYzwWVVSobScMbPgqKogPHsHQoogXgLH3ZTFtW'); 26 | export const METADATA_PUBKEY = new PublicKey('CZkFeERacU42qjApPyjamS13fNtz7y1wYLu5jyLpN1WL'); 27 | export const MASTER_EDITION_PUBKEY = new PublicKey('EZ5xB174dcz982WXV2aNr4zSW5ywAH3gP5Lbj8CuRMw4'); 28 | export const PACKSET_PUBKEY = new PublicKey('AN1bTGLCLSoSTJMeBPa7KGRDF5BpbfvD7oA42ZMq27Ru'); 29 | export const PACKCARD_PUBKEY = new PublicKey('BVrBhek71ZDDPQ8tYucBiEKxYmmtQLioF3eJJnhH6md4'); 30 | export const PACKVOUCHER_PUBKEY = new PublicKey('Ah6ngnNfhzKFfMPtiK5BeQ4mF5Nzwv1bwtbAEgMYH6Dp'); 31 | export const PROVING_PROCESS_PUBKEY = new PublicKey('HCzXvi3L1xdkFp5jafadFGzbirua9U4r4ePiXvcqH81R'); 32 | 33 | export const VAULT_AUTHORITY_PUBKEY = new PublicKey('AHsj4FffgTUYUVDjwBRGmiAsJcpWxX3YXZB8qsuew79t'); 34 | export const FRACTIONAL_MINT_PUBKEY = new PublicKey('1Qyhk9Pm1XktCN8RptQrwe9KnQLmiD7E58LpvqcnSV8'); 35 | export const REDEEM_TREASURY_PUBKEY = new PublicKey('vkXyVgeQaivApFsUXLMcQcVK7FGzkhAyR73FJ5YGnnb'); 36 | export const FRACTIONAL_TREASURY_PUBKEY = new PublicKey( 37 | '5nxC9KnUSqr5dNQoPN7xhKfmzS48znM3zfNqcgdKYXrh', 38 | ); 39 | export const PRICING_LOOKUP_ADDRESS_PUBKEY = new PublicKey( 40 | '7csPZBkT87N7x7aVrALjLBeDjCs7vcymazXVxoS3fmSf', 41 | ); 42 | export const FRACTION_MINT_PUBKEY = new PublicKey('BjrKGZgGL7sqv5aAUxp6ZafsWGKku9UZcMioQqASSaFB'); 43 | export const FRACTION_TREASURY_PUBKEY = new PublicKey( 44 | '3SZUEd9qtoKCyLW8uT8z25k7kZnMmwtdLQYjQAYRZHRp', 45 | ); 46 | export const FRACTION_MINT_AUTHORITY_PUBKEY = new PublicKey( 47 | '693Dn6MCsBYS4SkpSAXdhEvWNDooUHyF8KvvniEy4aBM', 48 | ); 49 | export const TOKEN_MINT_PUBKEY = new PublicKey('8epm7eTwoEpw36QF1puxkzsRzkVp45paRXnohbPemjmK'); 50 | export const TOKEN_ACCOUNT_PUBKEY = new PublicKey('4mpzrMQo8wgBtUHDVZLPeR9i58mbHVFNT9ef8sSzUohS'); 51 | export const TOKEN_STORE_ACCOUNT_PUBKEY = new PublicKey( 52 | 'B8ZH2ndZk1ueJvu56UNThAdjGXkfs1PyC5DwupWbwfZ3', 53 | ); 54 | export const TRANSFER_AUTHORITY_PUBKEY = new PublicKey( 55 | '8azYDQNycrRkv2r7amatTy3dyD6RrSw3zZsJvymzWE3E', 56 | ); 57 | export const SAFETY_DEPOSIT_BOX_PUBKEY = new PublicKey( 58 | '7pgXQDqVpiuj7TqbJKn9bW7ipg8U2uG5kY7kXNdNiTQd', 59 | ); 60 | export const OUTSTANDING_SHARE_TOKEN_ACCOUNT_PUBKEY = new PublicKey( 61 | '5Q9THrE74FsopHjASfj7RLqR36RgYbKhD1shoPoDDCCZ', 62 | ); 63 | export const OUTSTANDING_SHARES_ACCOUNT_PUBKEY = new PublicKey( 64 | '4Q2A27cS5DihPnerndsFZ6MxSTzjzfsjuQtS2hV3VXTT', 65 | ); 66 | export const BURN_AUTHORITY_PUBKEY = new PublicKey('4d4xRorridzBRApmoprSaB74Tgv4TN7TqhqfUeQB2dvw'); 67 | export const NEW_VAULT_AUTHORITY_PUBKEY = new PublicKey( 68 | 'Hi4wFQcmHKioVKvsL3NeYy9gANkZF9RQ9ZvTU7FdHP9s', 69 | ); 70 | export const EXTERNAL_PRICE_ACCOUNT_PUBKEY = new PublicKey( 71 | '78qz3gehg9YqktdaYt6o56DSUPFQ41tLMACHpnFjdYdS', 72 | ); 73 | export const PAYING_TOKEN_ACCOUNT_PUBKEY = new PublicKey( 74 | '8e6FHYEx7rfv1weRKrerjwuDzVn89LSjDsfXZcvWYDYW', 75 | ); 76 | export const CURRENT_AUTHORITY_PUBKEY = new PublicKey( 77 | 'EyBYD5b1A2xQAHJ8nUn11nHY8VrPV3Scg4mXZyjCB61f', 78 | ); 79 | export const NEW_AUTHORITY_PUBKEY = new PublicKey('5jF6nAQ5GTK8rsdzW8hGCEsWjY9YCV2jXCwZ854BPsWz'); 80 | export const RECENT_ISH_BLOCKHASH = '9qb2wMGnvBgVdp2dhJdeo5hgko9nLHxXg7GqXPgAFYCU'; 81 | export const PROCEEDS_ACCOUNT_PUBKEY = new PublicKey( 82 | 'GvJVHbk8pEzHwaeHeaoUrnBbsaUcDHHRVjKqP15UcShf', 83 | ); 84 | export const SOURCE_PUBKEY = new PublicKey('4CkQJBxhU8EZ2UjhigbtdaPbpTe6mqf811fipYBFbSYN'); 85 | export const DESTINATION_PUBKEY = new PublicKey('CZXESU6tu9m4YDs2wfQFbXmjbaDtJKBgurgYzGmeoArh'); 86 | 87 | export const VAULT_EXTENRNAL_PRICE_ACCOUNT = new PublicKey( 88 | '58S2MNcuS79ncBc5xi1T8jdS98jcXJbXqM5UvGvgmwcr', 89 | ); 90 | 91 | export const mockTransaction: TransactionCtorFields = { 92 | feePayer: new PublicKey('7J6QvJGCB22vDvYB33ikrWCXRBRsFY74ntAArSK4KJUn'), 93 | recentBlockhash: RECENT_ISH_BLOCKHASH, 94 | }; 95 | export const BID_METADATA_PUBKEY = new PublicKey('CZkFeERacU42qjGTPyjamS13fNtz7y1wYLu5jyLpN1WL'); 96 | export const BID_REDEMPTION_PUBKEY = new PublicKey('4CkQJBxhU8EZ1UjhfgbtdaPbpTe6mqf811fipYBFbSYN'); 97 | export const SAFETY_DEPOSIT_TOKEN_STORE_PUBKEY = new PublicKey( 98 | '4CkQJBxhU8EZ1UjhfgbtdaPbpTe6mqf811fipYBFbSNM', 99 | ); 100 | export const SAFETY_DEPOSIT_CONFIG_PUBKEY = new PublicKey( 101 | '4CkBUBxhU8EZ1UjhfgbtdaPbpTe6mqf811fipYBFbSNM', 102 | ); 103 | export const NEW_EDITION_PUBKEY = new PublicKey('4CkBUBxhU8EZ1UjhfgbtdaPbpTe6mqf822fipYBFbSNM'); 104 | export const NEW_METADATA_PUBKEY = new PublicKey('5jF6nAQ5GTK8rsdzW8hGCEsWjY9YCV2jXCwZ111BPsWz'); 105 | export const EDITION_MARK_PUBKEY = new PublicKey('78qz3gehg9YqktdaYt6o71DSUPFQ41tLMACHpnFjdYdS'); 106 | export const PRIZE_TRACKING_TICKET_PUBKEY = new PublicKey( 107 | '78qz3gehg9YqktdaYt6o99DSUPFQ41tLMACHpnFjdYdS', 108 | ); 109 | 110 | export const projectRoot = path.resolve(__dirname, '..', '..'); 111 | export const tmpTestDir = path.resolve(tmpdir(), 'test'); 112 | 113 | export const serializeConfig = { verifySignatures: false, requireAllSignatures: false }; 114 | export async function sleep(ms: number) { 115 | return new Promise((resolve) => setTimeout(resolve, ms)); 116 | } 117 | 118 | export function getUserKeypairFromFile(keypairPath) { 119 | const arr = readFileSync(path.resolve(keypairPath), { 120 | encoding: 'utf-8', 121 | }); 122 | const u8Array = Uint8Array.from(JSON.parse(arr)); 123 | return Keypair.fromSecretKey(u8Array); 124 | } 125 | -------------------------------------------------------------------------------- /test/vault.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '../src'; 2 | import { NETWORK, VAULT_PUBKEY } from './utils'; 3 | import { Vault, VaultKey } from '@metaplex-foundation/mpl-token-vault'; 4 | 5 | describe('Vault', () => { 6 | let connection: Connection; 7 | 8 | beforeAll(() => { 9 | connection = new Connection(NETWORK); 10 | }); 11 | 12 | describe('Vault', () => { 13 | test('load', async () => { 14 | const vault = await Vault.load(connection, VAULT_PUBKEY); 15 | 16 | expect(vault.data.key).toEqual(VaultKey.VaultV1); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "es6", 5 | "declaration": true, 6 | "removeComments": true, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "downlevelIteration": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "baseUrl": ".", 13 | "skipLibCheck": true, 14 | "module": "commonjs", 15 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }] 16 | }, 17 | "include": ["./src"], 18 | "exclude": ["node_modules", "dist", "build", "lib"] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "types": ["jest"] 6 | }, 7 | "include": [ 8 | "./src", 9 | "test" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "src/" 4 | ], 5 | "excludeInternal": true, 6 | "excludePrivate": true, 7 | "out": "./docs" 8 | } 9 | --------------------------------------------------------------------------------