From 489b4472ca54fbecb7d9101062f0a6868fbb8747 Mon Sep 17 00:00:00 2001 From: hana <81144685+2501babe@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:22:44 -0700 Subject: [PATCH] single-pool-js: double client (#5183) implement a single-pool client in web3-experimental and replace the existing web3-based client with a wrapper on the aforementioned --- .github/dependabot.yml | 11 +- .../workflows/pull-request-single-pool.yml | 4 +- ci/js-test-single-pool.sh | 6 + single-pool/js/LICENSE | 202 +++++++ single-pool/js/package.json | 33 -- .../js/{ => packages/classic}/.eslintignore | 0 .../js/{ => packages/classic}/.eslintrc.cjs | 0 .../js/{ => packages/classic}/.prettierrc.cjs | 0 single-pool/js/packages/classic/README.md | 11 + single-pool/js/packages/classic/package.json | 41 ++ .../js/packages/classic/src/addresses.ts | 45 ++ .../js/{ => packages/classic}/src/index.ts | 10 +- .../js/packages/classic/src/instructions.ts | 76 +++ .../js/packages/classic/src/internal.ts | 71 +++ .../classic}/src/mpl_metadata.ts | 0 .../js/packages/classic/src/transactions.ts | 111 ++++ .../tests/fixtures/mpl_token_metadata.so | 1 + .../fixtures/spl_single_validator_pool.so | 1 + .../classic}/tests/transactions.test.ts | 68 ++- .../classic}/tests/vote_account.json | 0 .../js/{ => packages/classic}/tsconfig.json | 1 - single-pool/js/packages/modern/.eslintignore | 4 + single-pool/js/packages/modern/.eslintrc.cjs | 21 + .../js/packages/modern/.prettierrc.cjs | 7 + single-pool/js/packages/modern/README.md | 11 + single-pool/js/packages/modern/package.json | 40 ++ .../js/packages/modern/src/addresses.ts | 125 ++++ single-pool/js/packages/modern/src/index.ts | 19 + .../js/packages/modern/src/instructions.ts | 382 +++++++++++++ .../js/packages/modern/src/internal.ts | 3 + .../js/packages/modern/src/quarantine.ts | 261 +++++++++ .../js/packages/modern/src/transactions.ts | 346 +++++++++++ single-pool/js/packages/modern/tsconfig.json | 22 + single-pool/js/pnpm-lock.yaml | 539 +++++++++++------- single-pool/js/pnpm-workspace.yaml | 2 + single-pool/js/src/addresses.ts | 46 -- single-pool/js/src/instructions.ts | 209 ------- single-pool/js/src/internal.ts | 106 ---- single-pool/js/src/transactions.ts | 299 ---------- .../js/tests/fixtures/mpl_token_metadata.so | 1 - .../fixtures/spl_single_validator_pool.so | 1 - 41 files changed, 2223 insertions(+), 913 deletions(-) create mode 100644 single-pool/js/LICENSE delete mode 100644 single-pool/js/package.json rename single-pool/js/{ => packages/classic}/.eslintignore (100%) rename single-pool/js/{ => packages/classic}/.eslintrc.cjs (100%) rename single-pool/js/{ => packages/classic}/.prettierrc.cjs (100%) create mode 100644 single-pool/js/packages/classic/README.md create mode 100644 single-pool/js/packages/classic/package.json create mode 100644 single-pool/js/packages/classic/src/addresses.ts rename single-pool/js/{ => packages/classic}/src/index.ts (53%) create mode 100644 single-pool/js/packages/classic/src/instructions.ts create mode 100644 single-pool/js/packages/classic/src/internal.ts rename single-pool/js/{ => packages/classic}/src/mpl_metadata.ts (100%) create mode 100644 single-pool/js/packages/classic/src/transactions.ts create mode 120000 single-pool/js/packages/classic/tests/fixtures/mpl_token_metadata.so create mode 120000 single-pool/js/packages/classic/tests/fixtures/spl_single_validator_pool.so rename single-pool/js/{ => packages/classic}/tests/transactions.test.ts (81%) rename single-pool/js/{ => packages/classic}/tests/vote_account.json (100%) rename single-pool/js/{ => packages/classic}/tsconfig.json (94%) create mode 100644 single-pool/js/packages/modern/.eslintignore create mode 100644 single-pool/js/packages/modern/.eslintrc.cjs create mode 100644 single-pool/js/packages/modern/.prettierrc.cjs create mode 100644 single-pool/js/packages/modern/README.md create mode 100644 single-pool/js/packages/modern/package.json create mode 100644 single-pool/js/packages/modern/src/addresses.ts create mode 100644 single-pool/js/packages/modern/src/index.ts create mode 100644 single-pool/js/packages/modern/src/instructions.ts create mode 100644 single-pool/js/packages/modern/src/internal.ts create mode 100644 single-pool/js/packages/modern/src/quarantine.ts create mode 100644 single-pool/js/packages/modern/src/transactions.ts create mode 100644 single-pool/js/packages/modern/tsconfig.json create mode 100644 single-pool/js/pnpm-workspace.yaml delete mode 100644 single-pool/js/src/addresses.ts delete mode 100644 single-pool/js/src/instructions.ts delete mode 100644 single-pool/js/src/internal.ts delete mode 100644 single-pool/js/src/transactions.ts delete mode 120000 single-pool/js/tests/fixtures/mpl_token_metadata.so delete mode 120000 single-pool/js/tests/fixtures/spl_single_validator_pool.so diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ea473698f35..c72cf994640 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -75,7 +75,16 @@ updates: labels: - "automerge" - package-ecosystem: npm - directory: "/single-pool/js" + directory: "/single-pool/js/packages/classic" + schedule: + interval: daily + time: "05:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 3 + labels: + - "automerge" +- package-ecosystem: npm + directory: "/single-pool/js/packages/modern" schedule: interval: daily time: "05:00" diff --git a/.github/workflows/pull-request-single-pool.yml b/.github/workflows/pull-request-single-pool.yml index d71559a7d6b..38f6d32c058 100644 --- a/.github/workflows/pull-request-single-pool.yml +++ b/.github/workflows/pull-request-single-pool.yml @@ -118,12 +118,12 @@ jobs: js-test: runs-on: ubuntu-latest env: - NODE_VERSION: 18.x + NODE_VERSION: 20 needs: cargo-test-sbf steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - uses: pnpm/action-setup@v2 diff --git a/ci/js-test-single-pool.sh b/ci/js-test-single-pool.sh index 55feafd74f7..34f94777e1f 100755 --- a/ci/js-test-single-pool.sh +++ b/ci/js-test-single-pool.sh @@ -6,6 +6,12 @@ source ./ci/solana-version.sh install cd single-pool/js pnpm install + +cd packages/modern +pnpm run lint +pnpm build + +cd ../classic pnpm run lint pnpm build pnpm test diff --git a/single-pool/js/LICENSE b/single-pool/js/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/single-pool/js/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/single-pool/js/package.json b/single-pool/js/package.json deleted file mode 100644 index 119f8a29a14..00000000000 --- a/single-pool/js/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "scripts": { - "build": "pnpm tsc", - "lint": "eslint --max-warnings 0 .", - "lint:fix": "eslint . --fix", - "test": "ava" - }, - "type": "module", - "devDependencies": { - "@ava/typescript": "^4.1.0", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "ava": "^5.3.1", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", - "solana-bankrun": "^0.2.0", - "ts-node": "^10.9.1", - "tsx": "^3.13.0", - "typescript": "^5.2.2" - }, - "dependencies": { - "@solana/buffer-layout": "^4.0.1", - "@solana/spl-token": "^0.3.8", - "@solana/web3.js": "^1.87.1" - }, - "ava": { - "extensions": { - "ts": "module" - }, - "nodeArguments": [ - "--loader=tsx" - ] - } -} diff --git a/single-pool/js/.eslintignore b/single-pool/js/packages/classic/.eslintignore similarity index 100% rename from single-pool/js/.eslintignore rename to single-pool/js/packages/classic/.eslintignore diff --git a/single-pool/js/.eslintrc.cjs b/single-pool/js/packages/classic/.eslintrc.cjs similarity index 100% rename from single-pool/js/.eslintrc.cjs rename to single-pool/js/packages/classic/.eslintrc.cjs diff --git a/single-pool/js/.prettierrc.cjs b/single-pool/js/packages/classic/.prettierrc.cjs similarity index 100% rename from single-pool/js/.prettierrc.cjs rename to single-pool/js/packages/classic/.prettierrc.cjs diff --git a/single-pool/js/packages/classic/README.md b/single-pool/js/packages/classic/README.md new file mode 100644 index 00000000000..f5416d3e10c --- /dev/null +++ b/single-pool/js/packages/classic/README.md @@ -0,0 +1,11 @@ +# `@solana/spl-single-pool-classic` + +A TypeScript library for interacting with the SPL Single-Validator Stake Pool program, targeting `@solana/web3.js` 1.x. +**If you are working on the new, bleeding-edge web3.js, you want `@solana/spl-single-pool`.** + +For information on installation and usage, see [SPL docs](https://spl.solana.com/single-pool). + +For support, please ask questions on the [Solana Stack Exchange](https://solana.stackexchange.com). + +If you've found a bug or you'd like to request a feature, please +[open an issue](https://github.com/solana-labs/solana-program-library/issues/new). diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json new file mode 100644 index 00000000000..61a3db5cf5c --- /dev/null +++ b/single-pool/js/packages/classic/package.json @@ -0,0 +1,41 @@ +{ + "name": "@solana/single-pool-classic", + "version": "1.0.0", + "scripts": { + "build": "pnpm tsc", + "lint": "eslint --max-warnings 0 .", + "lint:fix": "eslint . --fix", + "test": "NODE_OPTIONS='--loader=tsx' ava" + }, + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "exports": { + "require": "./dist/index.js", + "import": "./dist/index.js" + }, + "devDependencies": { + "@ava/typescript": "^4.1.0", + "@typescript-eslint/eslint-plugin": "^6.4.1", + "ava": "^5.3.1", + "eslint": "^8.49.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "solana-bankrun": "^0.1.1", + "ts-node": "^10.9.1", + "tsx": "^3.12.7", + "typescript": "^5.2.2" + }, + "dependencies": { + "@solana/web3.js": "^1.78.4", + "@solana/single-pool": "workspace:*" + }, + "ava": { + "extensions": { + "ts": "module" + }, + "nodeArguments": [ + "--loader=tsx" + ] + } +} diff --git a/single-pool/js/packages/classic/src/addresses.ts b/single-pool/js/packages/classic/src/addresses.ts new file mode 100644 index 00000000000..b547661d606 --- /dev/null +++ b/single-pool/js/packages/classic/src/addresses.ts @@ -0,0 +1,45 @@ +import { PublicKey } from '@solana/web3.js'; +import { + findPoolAddress as findPoolModern, + findPoolStakeAddress as findStakeModern, + findPoolMintAddress as findMintModern, + findPoolStakeAuthorityAddress as findStakeAuthorityModern, + findPoolMintAuthorityAddress as findMintAuthorityModern, + findPoolMplAuthorityAddress as findMplAuthorityModern, + findDefaultDepositAccountAddress as findDefaultDepositModern, +} from '@solana/single-pool'; + +export async function findPoolAddress(programId: PublicKey, voteAccountAddress: PublicKey) { + return new PublicKey(await findPoolModern(programId.toBase58(), voteAccountAddress.toBase58())); +} + +export async function findPoolStakeAddress(programId: PublicKey, poolAddress: PublicKey) { + return new PublicKey(await findStakeModern(programId.toBase58(), poolAddress.toBase58())); +} + +export async function findPoolMintAddress(programId: PublicKey, poolAddress: PublicKey) { + return new PublicKey(await findMintModern(programId.toBase58(), poolAddress.toBase58())); +} + +export async function findPoolStakeAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { + return new PublicKey( + await findStakeAuthorityModern(programId.toBase58(), poolAddress.toBase58()), + ); +} + +export async function findPoolMintAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { + return new PublicKey(await findMintAuthorityModern(programId.toBase58(), poolAddress.toBase58())); +} + +export async function findPoolMplAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { + return new PublicKey(await findMplAuthorityModern(programId.toBase58(), poolAddress.toBase58())); +} + +export async function findDefaultDepositAccountAddress( + poolAddress: PublicKey, + userWallet: PublicKey, +) { + return new PublicKey( + await findDefaultDepositModern(poolAddress.toBase58(), userWallet.toBase58()), + ); +} diff --git a/single-pool/js/src/index.ts b/single-pool/js/packages/classic/src/index.ts similarity index 53% rename from single-pool/js/src/index.ts rename to single-pool/js/packages/classic/src/index.ts index 0c2641683cf..415bdcec605 100644 --- a/single-pool/js/src/index.ts +++ b/single-pool/js/packages/classic/src/index.ts @@ -1,4 +1,7 @@ import { Connection, PublicKey } from '@solana/web3.js'; +import { getVoteAccountAddressForPool as getVoteModern } from '@solana/single-pool'; + +import { rpc } from './internal'; export * from './mpl_metadata'; export * from './addresses'; @@ -6,10 +9,7 @@ export * from './instructions'; export * from './transactions'; export async function getVoteAccountAddressForPool(connection: Connection, poolAddress: PublicKey) { - const poolAccount = await connection.getAccountInfo(poolAddress); - if (!(poolAccount && poolAccount.data[0] === 1)) { - throw 'invalid pool address'; - } + const voteAccountModern = await getVoteModern(rpc(connection), poolAddress.toBase58()); - return new PublicKey(poolAccount.data.slice(1)); + return new PublicKey(voteAccountModern); } diff --git a/single-pool/js/packages/classic/src/instructions.ts b/single-pool/js/packages/classic/src/instructions.ts new file mode 100644 index 00000000000..e783a8b5311 --- /dev/null +++ b/single-pool/js/packages/classic/src/instructions.ts @@ -0,0 +1,76 @@ +import { PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { SinglePoolInstruction as PoolInstructionModern } from '@solana/single-pool'; + +import { modernInstructionToLegacy } from './internal'; + +export class SinglePoolInstruction { + static async initializePool(voteAccount: PublicKey): Promise { + const instruction = await PoolInstructionModern.initializePool(voteAccount.toBase58()); + return modernInstructionToLegacy(instruction); + } + + static async reactivatePoolStake(voteAccount: PublicKey): Promise { + const instruction = await PoolInstructionModern.reactivatePoolStake(voteAccount.toBase58()); + return modernInstructionToLegacy(instruction); + } + + static async depositStake( + pool: PublicKey, + userStakeAccount: PublicKey, + userTokenAccount: PublicKey, + userLamportAccount: PublicKey, + ): Promise { + const instruction = await PoolInstructionModern.depositStake( + pool.toBase58(), + userStakeAccount.toBase58(), + userTokenAccount.toBase58(), + userLamportAccount.toBase58(), + ); + return modernInstructionToLegacy(instruction); + } + + static async withdrawStake( + pool: PublicKey, + userStakeAccount: PublicKey, + userStakeAuthority: PublicKey, + userTokenAccount: PublicKey, + tokenAmount: number | bigint, + ): Promise { + const instruction = await PoolInstructionModern.withdrawStake( + pool.toBase58(), + userStakeAccount.toBase58(), + userStakeAuthority.toBase58(), + userTokenAccount.toBase58(), + BigInt(tokenAmount), + ); + return modernInstructionToLegacy(instruction); + } + + static async createTokenMetadata( + pool: PublicKey, + payer: PublicKey, + ): Promise { + const instruction = await PoolInstructionModern.createTokenMetadata( + pool.toBase58(), + payer.toBase58(), + ); + return modernInstructionToLegacy(instruction); + } + + static async updateTokenMetadata( + voteAccount: PublicKey, + authorizedWithdrawer: PublicKey, + tokenName: string, + tokenSymbol: string, + tokenUri?: string, + ): Promise { + const instruction = await PoolInstructionModern.updateTokenMetadata( + voteAccount.toBase58(), + authorizedWithdrawer.toBase58(), + tokenName, + tokenSymbol, + tokenUri, + ); + return modernInstructionToLegacy(instruction); + } +} diff --git a/single-pool/js/packages/classic/src/internal.ts b/single-pool/js/packages/classic/src/internal.ts new file mode 100644 index 00000000000..e1628ee76a8 --- /dev/null +++ b/single-pool/js/packages/classic/src/internal.ts @@ -0,0 +1,71 @@ +import { Connection, Transaction, TransactionInstruction, PublicKey } from '@solana/web3.js'; +import { Buffer } from 'buffer'; + +export function rpc(connection: Connection) { + return { + getAccountInfo(address: string) { + return { + async send() { + const pubkey = new PublicKey(address); + return await connection.getAccountInfo(pubkey); + }, + }; + }, + getMinimumBalanceForRentExemption(size: bigint) { + return { + async send() { + return BigInt(await connection.getMinimumBalanceForRentExemption(Number(size))); + }, + }; + }, + getStakeMinimumDelegation() { + return { + async send() { + const minimumDelegation = await connection.getStakeMinimumDelegation(); + return { value: BigInt(minimumDelegation.value) }; + }, + }; + }, + }; +} + +export function modernInstructionToLegacy(modernInstruction: any): TransactionInstruction { + const keys = []; + for (const account of modernInstruction.accounts) { + keys.push({ + pubkey: new PublicKey(account.address), + isSigner: !!(account.role & 2), + isWritable: !!(account.role & 1), + }); + } + + return new TransactionInstruction({ + programId: new PublicKey(modernInstruction.programAddress), + keys, + data: Buffer.from(modernInstruction.data), + }); +} + +export function modernTransactionToLegacy(modernTransaction: any): Transaction { + const legacyTransaction = new Transaction(); + legacyTransaction.add(...modernTransaction.instructions.map(modernInstructionToLegacy)); + + return legacyTransaction; +} + +export function paramsToModern(params: any) { + const modernParams = {} as any; + for (const k of Object.keys(params)) { + if (k == 'connection') { + modernParams.rpc = rpc(params[k]); + } else if (params[k] instanceof PublicKey || params[k].constructor.name == 'PublicKey') { + modernParams[k] = params[k].toBase58(); + } else if (typeof params[k] == 'number') { + modernParams[k] = BigInt(params[k]); + } else { + modernParams[k] = params[k]; + } + } + + return modernParams; +} diff --git a/single-pool/js/src/mpl_metadata.ts b/single-pool/js/packages/classic/src/mpl_metadata.ts similarity index 100% rename from single-pool/js/src/mpl_metadata.ts rename to single-pool/js/packages/classic/src/mpl_metadata.ts diff --git a/single-pool/js/packages/classic/src/transactions.ts b/single-pool/js/packages/classic/src/transactions.ts new file mode 100644 index 00000000000..f422c8fa004 --- /dev/null +++ b/single-pool/js/packages/classic/src/transactions.ts @@ -0,0 +1,111 @@ +import { PublicKey, Connection } from '@solana/web3.js'; +import { SinglePoolProgram as PoolProgramModern } from '@solana/single-pool'; + +import { paramsToModern, modernTransactionToLegacy, rpc } from './internal'; + +interface DepositParams { + connection: Connection; + pool: PublicKey; + userWallet: PublicKey; + userStakeAccount?: PublicKey; + depositFromDefaultAccount?: boolean; + userTokenAccount?: PublicKey; + userLamportAccount?: PublicKey; + userWithdrawAuthority?: PublicKey; +} + +interface WithdrawParams { + connection: Connection; + pool: PublicKey; + userWallet: PublicKey; + userStakeAccount: PublicKey; + tokenAmount: number | bigint; + createStakeAccount?: boolean; + userStakeAuthority?: PublicKey; + userTokenAccount?: PublicKey; + userTokenAuthority?: PublicKey; +} + +export class SinglePoolProgram { + static programId: PublicKey = new PublicKey(PoolProgramModern.programAddress); + static space: number = Number(PoolProgramModern.space); + + static async initialize( + connection: Connection, + voteAccount: PublicKey, + payer: PublicKey, + skipMetadata = false, + ) { + const modernTransaction = await PoolProgramModern.initialize( + rpc(connection), + voteAccount.toBase58(), + payer.toBase58(), + skipMetadata, + ); + + return modernTransactionToLegacy(modernTransaction); + } + + static async reactivatePoolStake(connection: Connection, voteAccount: PublicKey) { + const modernTransaction = await PoolProgramModern.reactivatePoolStake(voteAccount.toBase58()); + + return modernTransactionToLegacy(modernTransaction); + } + + static async deposit(params: DepositParams) { + const modernParams = paramsToModern(params); + const modernTransaction = await PoolProgramModern.deposit(modernParams); + + return modernTransactionToLegacy(modernTransaction); + } + + static async withdraw(params: WithdrawParams) { + const modernParams = paramsToModern(params); + const modernTransaction = await PoolProgramModern.withdraw(modernParams); + + return modernTransactionToLegacy(modernTransaction); + } + + static async createTokenMetadata(pool: PublicKey, payer: PublicKey) { + const modernTransaction = await PoolProgramModern.createTokenMetadata( + pool.toBase58(), + payer.toBase58(), + ); + + return modernTransactionToLegacy(modernTransaction); + } + + static async updateTokenMetadata( + voteAccount: PublicKey, + authorizedWithdrawer: PublicKey, + name: string, + symbol: string, + uri?: string, + ) { + const modernTransaction = await PoolProgramModern.updateTokenMetadata( + voteAccount.toBase58(), + authorizedWithdrawer.toBase58(), + name, + symbol, + uri, + ); + + return modernTransactionToLegacy(modernTransaction); + } + + static async createAndDelegateUserStake( + connection: Connection, + voteAccount: PublicKey, + userWallet: PublicKey, + stakeAmount: number | bigint, + ) { + const modernTransaction = await PoolProgramModern.createAndDelegateUserStake( + rpc(connection), + voteAccount.toBase58(), + userWallet.toBase58(), + BigInt(stakeAmount), + ); + + return modernTransactionToLegacy(modernTransaction); + } +} diff --git a/single-pool/js/packages/classic/tests/fixtures/mpl_token_metadata.so b/single-pool/js/packages/classic/tests/fixtures/mpl_token_metadata.so new file mode 120000 index 00000000000..df4d35160ee --- /dev/null +++ b/single-pool/js/packages/classic/tests/fixtures/mpl_token_metadata.so @@ -0,0 +1 @@ +../../../../../../stake-pool/program/tests/fixtures/mpl_token_metadata.so \ No newline at end of file diff --git a/single-pool/js/packages/classic/tests/fixtures/spl_single_validator_pool.so b/single-pool/js/packages/classic/tests/fixtures/spl_single_validator_pool.so new file mode 120000 index 00000000000..8b7604523ff --- /dev/null +++ b/single-pool/js/packages/classic/tests/fixtures/spl_single_validator_pool.so @@ -0,0 +1 @@ +../../../../../../target/deploy/spl_single_validator_pool.so \ No newline at end of file diff --git a/single-pool/js/tests/transactions.test.ts b/single-pool/js/packages/classic/tests/transactions.test.ts similarity index 81% rename from single-pool/js/tests/transactions.test.ts rename to single-pool/js/packages/classic/tests/transactions.test.ts index 6fbc667090a..8043af940ef 100644 --- a/single-pool/js/tests/transactions.test.ts +++ b/single-pool/js/packages/classic/tests/transactions.test.ts @@ -139,7 +139,7 @@ test('initialize', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); // initialize pool const transaction = await SinglePoolProgram.initialize( @@ -152,12 +152,46 @@ test('initialize', async (t) => { t.truthy(await client.getAccount(poolAddress), 'pool has been created'); t.truthy( await client.getAccount( - findMplMetadataAddress(findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), + findMplMetadataAddress(await findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), ), 'metadata has been created', ); }); +test('reactivate pool stake', async (t) => { + const context = await startWithContext(); + const client = context.banksClient; + const payer = context.payer; + const connection = new BanksConnection(client, payer); + + const voteAccountAddress = new PublicKey(voteAccount.pubkey); + + // initialize pool + let transaction = await SinglePoolProgram.initialize( + connection, + voteAccountAddress, + payer.publicKey, + ); + await processTransaction(context, transaction); + + const slot = await client.getSlot(); + context.warpToSlot(slot + SLOTS_PER_EPOCH); + + // reactivate pool stake + transaction = await SinglePoolProgram.reactivatePoolStake(connection, voteAccountAddress); + + // setting up the validator state for this to succeed is very annoying + // we test success in program tests; here we just confirm we submit a well-formed transaction + let message = ''; + try { + await processTransaction(context, transaction); + } catch (e) { + message = e.message; + } finally { + t.true(message.includes('custom program error: 0xc'), 'got expected stake mismatch error'); + } +}); + test('deposit', async (t) => { const context = await startWithContext(); const client = context.banksClient; @@ -165,8 +199,8 @@ test('deposit', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); - const poolStakeAddress = findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolStakeAddress = await findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); const userStakeAccount = await createAndDelegateStakeAccount(context, voteAccountAddress); // initialize pool @@ -202,8 +236,8 @@ test('deposit from default', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); - const poolStakeAddress = findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolStakeAddress = await findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); // create default account const minimumDelegation = (await connection.getStakeMinimumDelegation()).value; @@ -243,8 +277,8 @@ test('withdraw', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); - const poolStakeAddress = findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolStakeAddress = await findPoolStakeAddress(SinglePoolProgram.programId, poolAddress); const depositAccount = await createAndDelegateStakeAccount(context, voteAccountAddress); // initialize pool @@ -295,7 +329,7 @@ test('create metadata', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); // initialize pool without metadata let transaction = await SinglePoolProgram.initialize( @@ -309,18 +343,18 @@ test('create metadata', async (t) => { t.truthy(await client.getAccount(poolAddress), 'pool has been created'); t.falsy( await client.getAccount( - findMplMetadataAddress(findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), + findMplMetadataAddress(await findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), ), 'metadata has not been created', ); // create metadata - transaction = SinglePoolProgram.createTokenMetadata(poolAddress, payer.publicKey); + transaction = await SinglePoolProgram.createTokenMetadata(poolAddress, payer.publicKey); await processTransaction(context, transaction); t.truthy( await client.getAccount( - findMplMetadataAddress(findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), + findMplMetadataAddress(await findPoolMintAddress(SinglePoolProgram.programId, poolAddress)), ), 'metadata has been created', ); @@ -335,8 +369,8 @@ test('update metadata', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); - const poolMintAddress = findPoolMintAddress(SinglePoolProgram.programId, poolAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolMintAddress = await findPoolMintAddress(SinglePoolProgram.programId, poolAddress); const poolMetadataAddress = findMplMetadataAddress(poolMintAddress); // initialize pool @@ -349,7 +383,7 @@ test('update metadata', async (t) => { // update metadata const newName = 'hana wuz here'; - transaction = SinglePoolProgram.updateTokenMetadata( + transaction = await SinglePoolProgram.updateTokenMetadata( voteAccountAddress, authorizedWithdrawer.publicKey, newName, @@ -371,7 +405,7 @@ test('get vote account address', async (t) => { const connection = new BanksConnection(client, payer); const voteAccountAddress = new PublicKey(voteAccount.pubkey); - const poolAddress = findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); + const poolAddress = await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress); // initialize pool const transaction = await SinglePoolProgram.initialize( @@ -391,7 +425,7 @@ test('default account address', async (t) => { const expectedDefault = new PublicKey('AcQyHnPczCxFj3EoyyapubjjsLy9bX9kYkcRNerreKvA'); const actualDefault = await findDefaultDepositAccountAddress( - findPoolAddress(SinglePoolProgram.programId, voteAccountAddress), + await findPoolAddress(SinglePoolProgram.programId, voteAccountAddress), owner, ); diff --git a/single-pool/js/tests/vote_account.json b/single-pool/js/packages/classic/tests/vote_account.json similarity index 100% rename from single-pool/js/tests/vote_account.json rename to single-pool/js/packages/classic/tests/vote_account.json diff --git a/single-pool/js/tsconfig.json b/single-pool/js/packages/classic/tsconfig.json similarity index 94% rename from single-pool/js/tsconfig.json rename to single-pool/js/packages/classic/tsconfig.json index 4bbdbf560fe..e2d884ce607 100644 --- a/single-pool/js/tsconfig.json +++ b/single-pool/js/packages/classic/tsconfig.json @@ -6,7 +6,6 @@ "outDir": "dist", "declaration": true, "declarationDir": "dist", - "emitDeclarationOnly": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, diff --git a/single-pool/js/packages/modern/.eslintignore b/single-pool/js/packages/modern/.eslintignore new file mode 100644 index 00000000000..58542507948 --- /dev/null +++ b/single-pool/js/packages/modern/.eslintignore @@ -0,0 +1,4 @@ +dist +node_modules +.vscode +.idea diff --git a/single-pool/js/packages/modern/.eslintrc.cjs b/single-pool/js/packages/modern/.eslintrc.cjs new file mode 100644 index 00000000000..63db7b8405f --- /dev/null +++ b/single-pool/js/packages/modern/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { + es6: true, + node: true, + jest: true, + }, + extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + plugins: ['@typescript-eslint/eslint-plugin'], + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + }, +}; diff --git a/single-pool/js/packages/modern/.prettierrc.cjs b/single-pool/js/packages/modern/.prettierrc.cjs new file mode 100644 index 00000000000..8446d684477 --- /dev/null +++ b/single-pool/js/packages/modern/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + endOfLine: 'lf', + semi: true, +}; diff --git a/single-pool/js/packages/modern/README.md b/single-pool/js/packages/modern/README.md new file mode 100644 index 00000000000..2cd1ff2c225 --- /dev/null +++ b/single-pool/js/packages/modern/README.md @@ -0,0 +1,11 @@ +# `@solana/spl-single-pool` + +A TypeScript library for interacting with the SPL Single-Validator Stake Pool program, targeting `@solana/web3.js` 2.0. +**If you are working on the legacy web3.js (if you're not sure, you probably are!), you want `@solana/spl-single-pool-classic`.** + +For information on installation and usage, see [SPL docs](https://spl.solana.com/single-pool). + +For support, please ask questions on the [Solana Stack Exchange](https://solana.stackexchange.com). + +If you've found a bug or you'd like to request a feature, please +[open an issue](https://github.com/solana-labs/solana-program-library/issues/new). diff --git a/single-pool/js/packages/modern/package.json b/single-pool/js/packages/modern/package.json new file mode 100644 index 00000000000..2a5142676c4 --- /dev/null +++ b/single-pool/js/packages/modern/package.json @@ -0,0 +1,40 @@ +{ + "name": "@solana/single-pool", + "version": "1.0.0", + "scripts": { + "build": "pnpm tsc", + "lint": "eslint --max-warnings 0 .", + "lint:fix": "eslint . --fix", + "test": "NODE_OPTIONS='--loader=tsx' ava" + }, + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "exports": { + "require": "./dist/index.js", + "import": "./dist/index.js" + }, + "devDependencies": { + "@ava/typescript": "^4.1.0", + "@typescript-eslint/eslint-plugin": "^6.4.1", + "ava": "^5.3.1", + "eslint": "^8.49.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "solana-bankrun": "^0.1.1", + "ts-node": "^10.9.1", + "tsx": "^3.12.7", + "typescript": "^5.2.2" + }, + "dependencies": { + "@solana/web3.js": "2.0.0-experimental.21e994f" + }, + "ava": { + "extensions": { + "ts": "module" + }, + "nodeArguments": [ + "--loader=tsx" + ] + } +} diff --git a/single-pool/js/packages/modern/src/addresses.ts b/single-pool/js/packages/modern/src/addresses.ts new file mode 100644 index 00000000000..1488dd3f4d5 --- /dev/null +++ b/single-pool/js/packages/modern/src/addresses.ts @@ -0,0 +1,125 @@ +import { + address, + getAddressCodec, + Base58EncodedAddress, + getProgramDerivedAddress, + createAddressWithSeed, +} from '@solana/web3.js'; + +import { MPL_METADATA_PROGRAM_ID } from './internal'; +import { STAKE_PROGRAM_ID } from './quarantine'; + +export const SINGLE_POOL_PROGRAM_ID = address('3cqnsMsT6LE96pxv7GR4di5rLqHDZZbR3FbeSUeRLFqY'); + +export type VoteAccountAddress = + Base58EncodedAddress & { + readonly __voteAccountAddress: unique symbol; + }; + +export type PoolAddress = Base58EncodedAddress & { + readonly __poolAddress: unique symbol; +}; + +export type PoolStakeAddress = Base58EncodedAddress & { + readonly __poolStakeAddress: unique symbol; +}; + +export type PoolMintAddress = Base58EncodedAddress & { + readonly __poolMintAddress: unique symbol; +}; + +export type PoolStakeAuthorityAddress = + Base58EncodedAddress & { + readonly __poolStakeAuthorityAddress: unique symbol; + }; + +export type PoolMintAuthorityAddress = + Base58EncodedAddress & { + readonly __poolMintAuthorityAddress: unique symbol; + }; + +export type PoolMplAuthorityAddress = + Base58EncodedAddress & { + readonly __poolMplAuthorityAddress: unique symbol; + }; + +export async function findPoolAddress( + programId: Base58EncodedAddress, + voteAccountAddress: VoteAccountAddress, +): Promise { + return (await findPda(programId, voteAccountAddress, 'pool')) as PoolAddress; +} + +export async function findPoolStakeAddress( + programId: Base58EncodedAddress, + poolAddress: PoolAddress, +): Promise { + return (await findPda(programId, poolAddress, 'stake')) as PoolStakeAddress; +} + +export async function findPoolMintAddress( + programId: Base58EncodedAddress, + poolAddress: PoolAddress, +): Promise { + return (await findPda(programId, poolAddress, 'mint')) as PoolMintAddress; +} + +export async function findPoolStakeAuthorityAddress( + programId: Base58EncodedAddress, + poolAddress: PoolAddress, +): Promise { + return (await findPda(programId, poolAddress, 'stake_authority')) as PoolStakeAuthorityAddress; +} + +export async function findPoolMintAuthorityAddress( + programId: Base58EncodedAddress, + poolAddress: PoolAddress, +): Promise { + return (await findPda(programId, poolAddress, 'mint_authority')) as PoolMintAuthorityAddress; +} + +export async function findPoolMplAuthorityAddress( + programId: Base58EncodedAddress, + poolAddress: PoolAddress, +): Promise { + return (await findPda(programId, poolAddress, 'mpl_authority')) as PoolMplAuthorityAddress; +} + +async function findPda( + programId: Base58EncodedAddress, + baseAddress: Base58EncodedAddress, + prefix: string, +) { + const { serialize } = getAddressCodec(); + const [pda] = await getProgramDerivedAddress({ + programAddress: programId, + seeds: [prefix, serialize(baseAddress)], + }); + + return pda; +} + +export async function findDefaultDepositAccountAddress( + poolAddress: PoolAddress, + userWallet: Base58EncodedAddress, +) { + return createAddressWithSeed({ + baseAddress: userWallet, + seed: defaultDepositAccountSeed(poolAddress), + programAddress: STAKE_PROGRAM_ID, + }); +} + +export function defaultDepositAccountSeed(poolAddress: PoolAddress): string { + return 'svsp' + poolAddress.slice(0, 28); +} + +export async function findMplMetadataAddress(poolMintAddress: PoolMintAddress) { + const { serialize } = getAddressCodec(); + const [pda] = await getProgramDerivedAddress({ + programAddress: MPL_METADATA_PROGRAM_ID, + seeds: ['metadata', serialize(MPL_METADATA_PROGRAM_ID), serialize(poolMintAddress)], + }); + + return pda; +} diff --git a/single-pool/js/packages/modern/src/index.ts b/single-pool/js/packages/modern/src/index.ts new file mode 100644 index 00000000000..f8697e33160 --- /dev/null +++ b/single-pool/js/packages/modern/src/index.ts @@ -0,0 +1,19 @@ +import { getAddressCodec } from '@solana/web3.js'; + +import { PoolAddress, VoteAccountAddress } from './addresses'; + +export * from './addresses'; +export * from './instructions'; +export * from './transactions'; + +export async function getVoteAccountAddressForPool( + rpc: any, // XXX not exported: Rpc, + poolAddress: PoolAddress, + abortSignal?: AbortSignal, +): Promise { + const poolAccount = await rpc.getAccountInfo(poolAddress).send(abortSignal); + if (!(poolAccount && poolAccount.data[0] === 1)) { + throw 'invalid pool address'; + } + return getAddressCodec().deserialize(poolAccount.data.slice(1))[0] as VoteAccountAddress; +} diff --git a/single-pool/js/packages/modern/src/instructions.ts b/single-pool/js/packages/modern/src/instructions.ts new file mode 100644 index 00000000000..3317355dbd2 --- /dev/null +++ b/single-pool/js/packages/modern/src/instructions.ts @@ -0,0 +1,382 @@ +import { + getAddressCodec, + Base58EncodedAddress, + ReadonlySignerAccount, + ReadonlyAccount, + IInstructionWithAccounts, + IInstructionWithData, + WritableAccount, + WritableSignerAccount, + IInstruction, + AccountRole, +} from '@solana/web3.js'; + +import { + PoolMintAuthorityAddress, + PoolMintAddress, + PoolMplAuthorityAddress, + PoolStakeAuthorityAddress, + PoolStakeAddress, + findMplMetadataAddress, + findPoolMplAuthorityAddress, + findPoolAddress, + VoteAccountAddress, + PoolAddress, + findPoolStakeAddress, + findPoolMintAddress, + findPoolMintAuthorityAddress, + findPoolStakeAuthorityAddress, + SINGLE_POOL_PROGRAM_ID, +} from './addresses'; +import { MPL_METADATA_PROGRAM_ID } from './internal'; +import { + SYSTEM_PROGRAM_ID, + SYSVAR_RENT_ID, + SYSVAR_CLOCK_ID, + STAKE_PROGRAM_ID, + SYSVAR_STAKE_HISTORY_ID, + STAKE_CONFIG_ID, + TOKEN_PROGRAM_ID, + u32, + u64, +} from './quarantine'; + +type InitializePoolInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + WritableAccount, + WritableAccount, + WritableAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ] + > & + IInstructionWithData; + +type ReactivatePoolStakeInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + ReadonlyAccount, + WritableAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ] + > & + IInstructionWithData; + +type DepositStakeInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + WritableAccount, + WritableAccount, + ReadonlyAccount, + ReadonlyAccount, + WritableAccount, // user stake + WritableAccount, // user token + WritableAccount, // user lamport + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ] + > & + IInstructionWithData; + +type WithdrawStakeInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + WritableAccount, + WritableAccount, + ReadonlyAccount, + ReadonlyAccount, + WritableAccount, // user stake + WritableAccount, // user token + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ] + > & + IInstructionWithData; + +type CreateTokenMetadataInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + WritableSignerAccount, // mpl payer + WritableAccount, // mpl account + ReadonlyAccount, + ReadonlyAccount, + ] + > & + IInstructionWithData; + +type UpdateTokenMetadataInstruction = IInstruction & + IInstructionWithAccounts< + [ + ReadonlyAccount, + ReadonlyAccount, + ReadonlyAccount, + ReadonlySignerAccount, // authorized withdrawer + WritableAccount, // mpl account + ReadonlyAccount, + ] + > & + IInstructionWithData; + +const enum SinglePoolInstructionType { + InitializePool = 0, + ReactivatePoolStake, + DepositStake, + WithdrawStake, + CreateTokenMetadata, + UpdateTokenMetadata, +} + +export const SinglePoolInstruction = { + initializePool: initializePoolInstruction, + reactivatePoolStake: reactivatePoolStakeInstruction, + depositStake: depositStakeInstruction, + withdrawStake: withdrawStakeInstruction, + createTokenMetadata: createTokenMetadataInstruction, + updateTokenMetadata: updateTokenMetadataInstruction, +}; + +export async function initializePoolInstruction( + voteAccount: VoteAccountAddress, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + const pool = await findPoolAddress(programAddress, voteAccount); + const [stake, mint, stakeAuthority, mintAuthority] = await Promise.all([ + findPoolStakeAddress(programAddress, pool), + findPoolMintAddress(programAddress, pool), + findPoolStakeAuthorityAddress(programAddress, pool), + findPoolMintAuthorityAddress(programAddress, pool), + ]); + + const data = new Uint8Array([SinglePoolInstructionType.InitializePool]); + + return { + data, + accounts: [ + { address: voteAccount, role: AccountRole.READONLY }, + { address: pool, role: AccountRole.WRITABLE }, + { address: stake, role: AccountRole.WRITABLE }, + { address: mint, role: AccountRole.WRITABLE }, + { address: stakeAuthority, role: AccountRole.READONLY }, + { address: mintAuthority, role: AccountRole.READONLY }, + { address: SYSVAR_RENT_ID, role: AccountRole.READONLY }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: SYSVAR_STAKE_HISTORY_ID, role: AccountRole.READONLY }, + { address: STAKE_CONFIG_ID, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ID, role: AccountRole.READONLY }, + { address: TOKEN_PROGRAM_ID, role: AccountRole.READONLY }, + { address: STAKE_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} + +export async function reactivatePoolStakeInstruction( + voteAccount: VoteAccountAddress, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + const pool = await findPoolAddress(programAddress, voteAccount); + const [stake, stakeAuthority] = await Promise.all([ + findPoolStakeAddress(programAddress, pool), + findPoolStakeAuthorityAddress(programAddress, pool), + ]); + + const data = new Uint8Array([SinglePoolInstructionType.ReactivatePoolStake]); + + return { + data, + accounts: [ + { address: voteAccount, role: AccountRole.READONLY }, + { address: pool, role: AccountRole.READONLY }, + { address: stake, role: AccountRole.WRITABLE }, + { address: stakeAuthority, role: AccountRole.READONLY }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: SYSVAR_STAKE_HISTORY_ID, role: AccountRole.READONLY }, + { address: STAKE_CONFIG_ID, role: AccountRole.READONLY }, + { address: STAKE_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} + +export async function depositStakeInstruction( + pool: PoolAddress, + userStakeAccount: Base58EncodedAddress, + userTokenAccount: Base58EncodedAddress, + userLamportAccount: Base58EncodedAddress, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + const [stake, mint, stakeAuthority, mintAuthority] = await Promise.all([ + findPoolStakeAddress(programAddress, pool), + findPoolMintAddress(programAddress, pool), + findPoolStakeAuthorityAddress(programAddress, pool), + findPoolMintAuthorityAddress(programAddress, pool), + ]); + + const data = new Uint8Array([SinglePoolInstructionType.DepositStake]); + + return { + data, + accounts: [ + { address: pool, role: AccountRole.READONLY }, + { address: stake, role: AccountRole.WRITABLE }, + { address: mint, role: AccountRole.WRITABLE }, + { address: stakeAuthority, role: AccountRole.READONLY }, + { address: mintAuthority, role: AccountRole.READONLY }, + { address: userStakeAccount, role: AccountRole.WRITABLE }, + { address: userTokenAccount, role: AccountRole.WRITABLE }, + { address: userLamportAccount, role: AccountRole.WRITABLE }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: SYSVAR_STAKE_HISTORY_ID, role: AccountRole.READONLY }, + { address: TOKEN_PROGRAM_ID, role: AccountRole.READONLY }, + { address: STAKE_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} + +export async function withdrawStakeInstruction( + pool: PoolAddress, + userStakeAccount: Base58EncodedAddress, + userStakeAuthority: Base58EncodedAddress, + userTokenAccount: Base58EncodedAddress, + tokenAmount: bigint, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + const [stake, mint, stakeAuthority, mintAuthority] = await Promise.all([ + findPoolStakeAddress(programAddress, pool), + findPoolMintAddress(programAddress, pool), + findPoolStakeAuthorityAddress(programAddress, pool), + findPoolMintAuthorityAddress(programAddress, pool), + ]); + + const { serialize } = getAddressCodec(); + const data = new Uint8Array([ + SinglePoolInstructionType.WithdrawStake, + ...serialize(userStakeAuthority), + ...u64(tokenAmount), + ]); + + return { + data, + accounts: [ + { address: pool, role: AccountRole.READONLY }, + { address: stake, role: AccountRole.WRITABLE }, + { address: mint, role: AccountRole.WRITABLE }, + { address: stakeAuthority, role: AccountRole.READONLY }, + { address: mintAuthority, role: AccountRole.READONLY }, + { address: userStakeAccount, role: AccountRole.WRITABLE }, + { address: userTokenAccount, role: AccountRole.WRITABLE }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: TOKEN_PROGRAM_ID, role: AccountRole.READONLY }, + { address: STAKE_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} + +export async function createTokenMetadataInstruction( + pool: PoolAddress, + payer: Base58EncodedAddress, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + const mint = await findPoolMintAddress(programAddress, pool); + const [mintAuthority, mplAuthority, mplMetadata] = await Promise.all([ + findPoolMintAuthorityAddress(programAddress, pool), + findPoolMplAuthorityAddress(programAddress, pool), + findMplMetadataAddress(mint), + ]); + + const data = new Uint8Array([SinglePoolInstructionType.CreateTokenMetadata]); + + return { + data, + accounts: [ + { address: pool, role: AccountRole.READONLY }, + { address: mint, role: AccountRole.READONLY }, + { address: mintAuthority, role: AccountRole.READONLY }, + { address: mplAuthority, role: AccountRole.READONLY }, + { address: payer, role: AccountRole.WRITABLE_SIGNER }, + { address: mplMetadata, role: AccountRole.WRITABLE }, + { address: MPL_METADATA_PROGRAM_ID, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} + +export async function updateTokenMetadataInstruction( + voteAccount: VoteAccountAddress, + authorizedWithdrawer: Base58EncodedAddress, + tokenName: string, + tokenSymbol: string, + tokenUri?: string, +): Promise { + const programAddress = SINGLE_POOL_PROGRAM_ID; + tokenUri = tokenUri || ''; + + if (tokenName.length > 32) { + throw 'maximum token name length is 32 characters'; + } + + if (tokenSymbol.length > 10) { + throw 'maximum token symbol length is 10 characters'; + } + + if (tokenUri.length > 200) { + throw 'maximum token uri length is 200 characters'; + } + + const pool = await findPoolAddress(programAddress, voteAccount); + const [mint, mplAuthority] = await Promise.all([ + findPoolMintAddress(programAddress, pool), + findPoolMplAuthorityAddress(programAddress, pool), + ]); + const mplMetadata = await findMplMetadataAddress(mint); + + const text = new TextEncoder(); + const data = new Uint8Array([ + SinglePoolInstructionType.UpdateTokenMetadata, + ...u32(tokenName.length), + ...text.encode(tokenName), + ...u32(tokenSymbol.length), + ...text.encode(tokenSymbol), + ...u32(tokenUri.length), + ...text.encode(tokenUri), + ]); + + return { + data, + accounts: [ + { address: voteAccount, role: AccountRole.READONLY }, + { address: pool, role: AccountRole.READONLY }, + { address: mplAuthority, role: AccountRole.READONLY }, + { address: authorizedWithdrawer, role: AccountRole.READONLY_SIGNER }, + { address: mplMetadata, role: AccountRole.WRITABLE }, + { address: MPL_METADATA_PROGRAM_ID, role: AccountRole.READONLY }, + ], + programAddress, + }; +} diff --git a/single-pool/js/packages/modern/src/internal.ts b/single-pool/js/packages/modern/src/internal.ts new file mode 100644 index 00000000000..cdea7d11bae --- /dev/null +++ b/single-pool/js/packages/modern/src/internal.ts @@ -0,0 +1,3 @@ +import { address } from '@solana/web3.js'; + +export const MPL_METADATA_PROGRAM_ID = address('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'); diff --git a/single-pool/js/packages/modern/src/quarantine.ts b/single-pool/js/packages/modern/src/quarantine.ts new file mode 100644 index 00000000000..be688b48156 --- /dev/null +++ b/single-pool/js/packages/modern/src/quarantine.ts @@ -0,0 +1,261 @@ +import { + address, + getAddressCodec, + Base58EncodedAddress, + AccountRole, + getProgramDerivedAddress, +} from '@solana/web3.js'; + +// HERE BE DRAGONS +// this is all the stuff that shouldnt be in our library once we can import from elsewhere + +export const SYSTEM_PROGRAM_ID = address('11111111111111111111111111111111'); +export const STAKE_PROGRAM_ID = address('Stake11111111111111111111111111111111111111'); +export const SYSVAR_RENT_ID = address('SysvarRent111111111111111111111111111111111'); +export const SYSVAR_CLOCK_ID = address('SysvarC1ock11111111111111111111111111111111'); +export const SYSVAR_STAKE_HISTORY_ID = address('SysvarStakeHistory1111111111111111111111111'); +export const STAKE_CONFIG_ID = address('StakeConfig11111111111111111111111111111111'); +export const STAKE_ACCOUNT_SIZE = 200n; + +export const TOKEN_PROGRAM_ID = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); +export const ATOKEN_PROGRAM_ID = address('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); +export const MINT_SIZE = 82n; + +export function u32(n: number): Uint8Array { + const bns = Uint32Array.from([n]); + return new Uint8Array(bns.buffer); +} + +export function u64(n: bigint): Uint8Array { + const bns = BigUint64Array.from([n]); + return new Uint8Array(bns.buffer); +} + +export class SystemInstruction { + static createAccount(params: { + from: Base58EncodedAddress; + newAccount: Base58EncodedAddress; + lamports: bigint; + space: bigint; + programAddress: Base58EncodedAddress; + }) { + const { serialize } = getAddressCodec(); + const data = new Uint8Array([ + ...u32(0), + ...u64(params.lamports), + ...u64(params.space), + ...serialize(params.programAddress), + ]); + + const accounts = [ + { address: params.from, role: AccountRole.WRITABLE_SIGNER }, + { address: params.newAccount, role: AccountRole.WRITABLE_SIGNER }, + ]; + + return { + data, + accounts, + programAddress: SYSTEM_PROGRAM_ID, + }; + } + + static transfer(params: { + from: Base58EncodedAddress; + to: Base58EncodedAddress; + lamports: bigint; + }) { + const data = new Uint8Array([...u32(2), ...u64(params.lamports)]); + + const accounts = [ + { address: params.from, role: AccountRole.WRITABLE_SIGNER }, + { address: params.to, role: AccountRole.WRITABLE }, + ]; + + return { + data, + accounts, + programAddress: SYSTEM_PROGRAM_ID, + }; + } + + static createAccountWithSeed(params: { + from: Base58EncodedAddress; + newAccount: Base58EncodedAddress; + base: Base58EncodedAddress; + seed: string; + lamports: bigint; + space: bigint; + programAddress: Base58EncodedAddress; + }) { + const { serialize } = getAddressCodec(); + const data = new Uint8Array([ + ...u32(3), + ...serialize(params.base), + ...u64(BigInt(params.seed.length)), + ...new TextEncoder().encode(params.seed), + ...u64(params.lamports), + ...u64(params.space), + ...serialize(params.programAddress), + ]); + + const accounts = [ + { address: params.from, role: AccountRole.WRITABLE_SIGNER }, + { address: params.newAccount, role: AccountRole.WRITABLE }, + ]; + if (params.base != params.from) { + accounts.push({ address: params.base, role: AccountRole.READONLY_SIGNER }); + } + + return { + data, + accounts, + programAddress: SYSTEM_PROGRAM_ID, + }; + } +} + +export class TokenInstruction { + static approve(params: { + account: Base58EncodedAddress; + delegate: Base58EncodedAddress; + owner: Base58EncodedAddress; + amount: bigint; + }) { + const data = new Uint8Array([...u32(4), ...u64(params.amount)]); + + const accounts = [ + { address: params.account, role: AccountRole.WRITABLE }, + { address: params.delegate, role: AccountRole.READONLY }, + { address: params.owner, role: AccountRole.READONLY_SIGNER }, + ]; + + return { + data, + accounts, + programAddress: TOKEN_PROGRAM_ID, + }; + } + + static createAssociatedTokenAccount(params: { + payer: Base58EncodedAddress; + associatedAccount: Base58EncodedAddress; + owner: Base58EncodedAddress; + mint: Base58EncodedAddress; + }) { + const data = new Uint8Array([0]); + + const accounts = [ + { address: params.payer, role: AccountRole.WRITABLE_SIGNER }, + { address: params.associatedAccount, role: AccountRole.WRITABLE }, + { address: params.owner, role: AccountRole.READONLY }, + { address: params.mint, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ID, role: AccountRole.READONLY }, + { address: TOKEN_PROGRAM_ID, role: AccountRole.READONLY }, + ]; + + return { + data, + accounts, + programAddress: ATOKEN_PROGRAM_ID, + }; + } +} + +export enum StakeAuthorizationType { + Staker, + Withdrawer, +} + +export class StakeInstruction { + // idc about doing it right unless this goes in a lib + static initialize(params: { + stakeAccount: Base58EncodedAddress; + staker: Base58EncodedAddress; + withdrawer: Base58EncodedAddress; + }) { + const { serialize } = getAddressCodec(); + const data = new Uint8Array([ + ...u32(0), + ...serialize(params.staker), + ...serialize(params.withdrawer), + ...Array(48).fill(0), + ]); + + const accounts = [ + { address: params.stakeAccount, role: AccountRole.WRITABLE }, + { address: SYSVAR_RENT_ID, role: AccountRole.READONLY }, + ]; + + return { + data, + accounts, + programAddress: STAKE_PROGRAM_ID, + }; + } + + static authorize(params: { + stakeAccount: Base58EncodedAddress; + authorized: Base58EncodedAddress; + newAuthorized: Base58EncodedAddress; + authorizationType: StakeAuthorizationType; + custodian?: Base58EncodedAddress; + }) { + const { serialize } = getAddressCodec(); + const data = new Uint8Array([ + ...u32(1), + ...serialize(params.newAuthorized), + ...u32(params.authorizationType), + ]); + + const accounts = [ + { address: params.stakeAccount, role: AccountRole.WRITABLE }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: params.authorized, role: AccountRole.READONLY_SIGNER }, + ]; + if (params.custodian) { + accounts.push({ address: params.custodian, role: AccountRole.READONLY }); + } + + return { + data, + accounts, + programAddress: STAKE_PROGRAM_ID, + }; + } + + static delegate(params: { + stakeAccount: Base58EncodedAddress; + authorized: Base58EncodedAddress; + voteAccount: Base58EncodedAddress; + }) { + const data = new Uint8Array(u32(2)); + + const accounts = [ + { address: params.stakeAccount, role: AccountRole.WRITABLE }, + { address: params.voteAccount, role: AccountRole.READONLY }, + { address: SYSVAR_CLOCK_ID, role: AccountRole.READONLY }, + { address: SYSVAR_STAKE_HISTORY_ID, role: AccountRole.READONLY }, + { address: STAKE_CONFIG_ID, role: AccountRole.READONLY }, + { address: params.authorized, role: AccountRole.READONLY_SIGNER }, + ]; + + return { + data, + accounts, + programAddress: STAKE_PROGRAM_ID, + }; + } +} + +export async function getAssociatedTokenAddress( + mint: Base58EncodedAddress, + owner: Base58EncodedAddress, +) { + const { serialize } = getAddressCodec(); + const [pda] = await getProgramDerivedAddress({ + programAddress: ATOKEN_PROGRAM_ID, + seeds: [serialize(owner), serialize(TOKEN_PROGRAM_ID), serialize(mint)], + }); + + return pda; +} diff --git a/single-pool/js/packages/modern/src/transactions.ts b/single-pool/js/packages/modern/src/transactions.ts new file mode 100644 index 00000000000..72b6255bf4d --- /dev/null +++ b/single-pool/js/packages/modern/src/transactions.ts @@ -0,0 +1,346 @@ +import { + appendTransactionInstruction, + Transaction, + TransactionVersion, + Base58EncodedAddress, +} from '@solana/web3.js'; + +import { + findPoolAddress, + VoteAccountAddress, + PoolAddress, + findPoolStakeAddress, + findPoolMintAddress, + defaultDepositAccountSeed, + findDefaultDepositAccountAddress, + findPoolMintAuthorityAddress, + findPoolStakeAuthorityAddress, + SINGLE_POOL_PROGRAM_ID, +} from './addresses'; +import { + initializePoolInstruction, + reactivatePoolStakeInstruction, + depositStakeInstruction, + withdrawStakeInstruction, + createTokenMetadataInstruction, + updateTokenMetadataInstruction, +} from './instructions'; +import { + STAKE_PROGRAM_ID, + STAKE_ACCOUNT_SIZE, + MINT_SIZE, + StakeInstruction, + SystemInstruction, + TokenInstruction, + StakeAuthorizationType, + getAssociatedTokenAddress, +} from './quarantine'; + +interface DepositParams { + rpc: any; // XXX Rpc + pool: PoolAddress; + userWallet: Base58EncodedAddress; + userStakeAccount?: Base58EncodedAddress; + depositFromDefaultAccount?: boolean; + userTokenAccount?: Base58EncodedAddress; + userLamportAccount?: Base58EncodedAddress; + userWithdrawAuthority?: Base58EncodedAddress; +} + +interface WithdrawParams { + rpc: any; // XXX Rpc + pool: PoolAddress; + userWallet: Base58EncodedAddress; + userStakeAccount: Base58EncodedAddress; + tokenAmount: bigint; + createStakeAccount?: boolean; + userStakeAuthority?: Base58EncodedAddress; + userTokenAccount?: Base58EncodedAddress; + userTokenAuthority?: Base58EncodedAddress; +} + +export const SINGLE_POOL_ACCOUNT_SIZE = 33n; + +export const SinglePoolProgram = { + programAddress: SINGLE_POOL_PROGRAM_ID, + space: SINGLE_POOL_ACCOUNT_SIZE, + initialize: initializeTransaction, + reactivatePoolStake: reactivatePoolStakeTransaction, + deposit: depositTransaction, + withdraw: withdrawTransaction, + createTokenMetadata: createTokenMetadataTransaction, + updateTokenMetadata: updateTokenMetadataTransaction, + createAndDelegateUserStake: createAndDelegateUserStakeTransaction, +}; + +export async function initializeTransaction( + rpc: any, // XXX not exported: Rpc, + voteAccount: VoteAccountAddress, + payer: Base58EncodedAddress, + skipMetadata = false, +): Promise { + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + + const pool = await findPoolAddress(SINGLE_POOL_PROGRAM_ID, voteAccount); + const [stake, mint, poolRent, stakeRent, mintRent, minimumDelegationObj] = await Promise.all([ + findPoolStakeAddress(SINGLE_POOL_PROGRAM_ID, pool), + findPoolMintAddress(SINGLE_POOL_PROGRAM_ID, pool), + rpc.getMinimumBalanceForRentExemption(SINGLE_POOL_ACCOUNT_SIZE).send(), + rpc.getMinimumBalanceForRentExemption(STAKE_ACCOUNT_SIZE).send(), + rpc.getMinimumBalanceForRentExemption(MINT_SIZE).send(), + rpc.getStakeMinimumDelegation().send(), + ]); + const minimumDelegation = minimumDelegationObj.value; + + transaction = appendTransactionInstruction( + SystemInstruction.transfer({ + from: payer, + to: pool, + lamports: poolRent, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + SystemInstruction.transfer({ + from: payer, + to: stake, + lamports: stakeRent + minimumDelegation, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + SystemInstruction.transfer({ + from: payer, + to: mint, + lamports: mintRent, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + await initializePoolInstruction(voteAccount), + transaction, + ); + + if (!skipMetadata) { + transaction = appendTransactionInstruction( + await createTokenMetadataInstruction(pool, payer), + transaction, + ); + } + + return transaction; +} + +export async function reactivatePoolStakeTransaction( + voteAccount: VoteAccountAddress, +): Promise { + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + transaction = appendTransactionInstruction( + await reactivatePoolStakeInstruction(voteAccount), + transaction, + ); + + return transaction; +} + +export async function depositTransaction(params: DepositParams) { + const { rpc, pool, userWallet } = params; + + // note this is just xnor + if (!params.userStakeAccount == !params.depositFromDefaultAccount) { + throw 'must either provide userStakeAccount or true depositFromDefaultAccount'; + } + + const userStakeAccount = ( + params.depositFromDefaultAccount + ? await findDefaultDepositAccountAddress(pool, userWallet) + : params.userStakeAccount + ) as Base58EncodedAddress; + + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + + const [mint, poolStakeAuthority] = await Promise.all([ + findPoolMintAddress(SINGLE_POOL_PROGRAM_ID, pool), + findPoolStakeAuthorityAddress(SINGLE_POOL_PROGRAM_ID, pool), + ]); + + const userAssociatedTokenAccount = await getAssociatedTokenAddress(mint, userWallet); + const userTokenAccount = params.userTokenAccount || userAssociatedTokenAccount; + const userLamportAccount = params.userLamportAccount || userWallet; + const userWithdrawAuthority = params.userWithdrawAuthority || userWallet; + + if ( + userTokenAccount == userAssociatedTokenAccount && + (await rpc.getAccountInfo(userAssociatedTokenAccount).send()) == null + ) { + transaction = appendTransactionInstruction( + TokenInstruction.createAssociatedTokenAccount({ + payer: userWallet, + associatedAccount: userAssociatedTokenAccount, + owner: userWallet, + mint, + }), + transaction, + ); + } + + transaction = appendTransactionInstruction( + StakeInstruction.authorize({ + stakeAccount: userStakeAccount, + authorized: userWithdrawAuthority, + newAuthorized: poolStakeAuthority, + authorizationType: StakeAuthorizationType.Staker, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + StakeInstruction.authorize({ + stakeAccount: userStakeAccount, + authorized: userWithdrawAuthority, + newAuthorized: poolStakeAuthority, + authorizationType: StakeAuthorizationType.Withdrawer, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + await depositStakeInstruction(pool, userStakeAccount, userTokenAccount, userLamportAccount), + transaction, + ); + + return transaction; +} + +export async function withdrawTransaction(params: WithdrawParams) { + const { rpc, pool, userWallet, userStakeAccount, tokenAmount, createStakeAccount } = params; + + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + + const poolMintAuthority = await findPoolMintAuthorityAddress(SINGLE_POOL_PROGRAM_ID, pool); + + const userStakeAuthority = params.userStakeAuthority || userWallet; + const userTokenAccount = + params.userTokenAccount || + (await getAssociatedTokenAddress( + await findPoolMintAddress(SINGLE_POOL_PROGRAM_ID, pool), + userWallet, + )); + const userTokenAuthority = params.userTokenAuthority || userWallet; + + if (createStakeAccount) { + transaction = appendTransactionInstruction( + SystemInstruction.createAccount({ + from: userWallet, + lamports: await rpc.getMinimumBalanceForRentExemption(STAKE_ACCOUNT_SIZE).send(), + newAccount: userStakeAccount, + programAddress: STAKE_PROGRAM_ID, + space: STAKE_ACCOUNT_SIZE, + }), + transaction, + ); + } + + transaction = appendTransactionInstruction( + TokenInstruction.approve({ + account: userTokenAccount, + delegate: poolMintAuthority, + owner: userTokenAuthority, + amount: tokenAmount, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + await withdrawStakeInstruction( + pool, + userStakeAccount, + userStakeAuthority, + userTokenAccount, + tokenAmount, + ), + transaction, + ); + + return transaction; +} + +export async function createTokenMetadataTransaction( + pool: PoolAddress, + payer: Base58EncodedAddress, +): Promise { + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + transaction = appendTransactionInstruction( + await createTokenMetadataInstruction(pool, payer), + transaction, + ); + + return transaction; +} + +export async function updateTokenMetadataTransaction( + voteAccount: VoteAccountAddress, + authorizedWithdrawer: Base58EncodedAddress, + name: string, + symbol: string, + uri?: string, +): Promise { + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + transaction = appendTransactionInstruction( + await updateTokenMetadataInstruction(voteAccount, authorizedWithdrawer, name, symbol, uri), + transaction, + ); + + return transaction; +} + +export async function createAndDelegateUserStakeTransaction( + rpc: any, // XXX not exported: Rpc, + voteAccount: VoteAccountAddress, + userWallet: Base58EncodedAddress, + stakeAmount: bigint, +): Promise { + let transaction = { instructions: [] as any, version: 'legacy' as TransactionVersion }; + + const pool = await findPoolAddress(SINGLE_POOL_PROGRAM_ID, voteAccount); + const [stakeAccount, stakeRent] = await Promise.all([ + findDefaultDepositAccountAddress(pool, userWallet), + await rpc.getMinimumBalanceForRentExemption(STAKE_ACCOUNT_SIZE).send(), + ]); + + transaction = appendTransactionInstruction( + SystemInstruction.createAccountWithSeed({ + base: userWallet, + from: userWallet, + lamports: stakeAmount + stakeRent, + newAccount: stakeAccount, + programAddress: STAKE_PROGRAM_ID, + seed: defaultDepositAccountSeed(pool), + space: STAKE_ACCOUNT_SIZE, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + StakeInstruction.initialize({ + stakeAccount, + staker: userWallet, + withdrawer: userWallet, + }), + transaction, + ); + + transaction = appendTransactionInstruction( + StakeInstruction.delegate({ + stakeAccount, + authorized: userWallet, + voteAccount, + }), + transaction, + ); + + return transaction; +} diff --git a/single-pool/js/packages/modern/tsconfig.json b/single-pool/js/packages/modern/tsconfig.json new file mode 100644 index 00000000000..c3b7b616df8 --- /dev/null +++ b/single-pool/js/packages/modern/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es2020", + "baseUrl": "./src", + "outDir": "dist", + "declaration": true, + "declarationDir": "dist", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "lib": ["DOM"], + "skipLibCheck": true // needed to avoid re-export errors from borsh + }, + "include": ["src/**/*.ts"] +} diff --git a/single-pool/js/pnpm-lock.yaml b/single-pool/js/pnpm-lock.yaml index cb89d52eed2..86ebe7b1dd0 100644 --- a/single-pool/js/pnpm-lock.yaml +++ b/single-pool/js/pnpm-lock.yaml @@ -4,45 +4,84 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - '@solana/buffer-layout': - specifier: ^4.0.1 - version: 4.0.1 - '@solana/spl-token': - specifier: ^0.3.8 - version: 0.3.8(@solana/web3.js@1.87.1) - '@solana/web3.js': - specifier: ^1.87.1 - version: 1.87.1 - -devDependencies: - '@ava/typescript': - specifier: ^4.1.0 - version: 4.1.0 - '@typescript-eslint/eslint-plugin': - specifier: ^6.7.5 - version: 6.7.5(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.2.2) - ava: - specifier: ^5.3.1 - version: 5.3.1(@ava/typescript@4.1.0) - eslint-config-prettier: - specifier: ^9.0.0 - version: 9.0.0(eslint@8.47.0) - eslint-plugin-prettier: - specifier: ^5.0.1 - version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.47.0)(prettier@3.0.2) - solana-bankrun: - specifier: ^0.2.0 - version: 0.2.0 - ts-node: - specifier: ^10.9.1 - version: 10.9.1(@types/node@20.5.0)(typescript@5.2.2) - tsx: - specifier: ^3.13.0 - version: 3.13.0 - typescript: - specifier: ^5.2.2 - version: 5.2.2 +importers: + + packages/classic: + dependencies: + '@solana/single-pool': + specifier: workspace:* + version: link:../modern + '@solana/web3.js': + specifier: ^1.78.4 + version: 1.87.1 + devDependencies: + '@ava/typescript': + specifier: ^4.1.0 + version: 4.1.0 + '@typescript-eslint/eslint-plugin': + specifier: ^6.4.1 + version: 6.4.1(@typescript-eslint/parser@6.7.5)(eslint@8.49.0)(typescript@5.2.2) + ava: + specifier: ^5.3.1 + version: 5.3.1(@ava/typescript@4.1.0) + eslint: + specifier: ^8.49.0 + version: 8.49.0 + eslint-config-prettier: + specifier: ^8.8.0 + version: 8.8.0(eslint@8.49.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.0.0(eslint-config-prettier@8.8.0)(eslint@8.49.0)(prettier@3.0.3) + solana-bankrun: + specifier: ^0.1.1 + version: 0.1.1 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.8.4)(typescript@5.2.2) + tsx: + specifier: ^3.12.7 + version: 3.12.7 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + + packages/modern: + dependencies: + '@solana/web3.js': + specifier: 2.0.0-experimental.21e994f + version: 2.0.0-experimental.21e994f(node-fetch@2.7.0)(ws@8.14.2) + devDependencies: + '@ava/typescript': + specifier: ^4.1.0 + version: 4.1.0 + '@typescript-eslint/eslint-plugin': + specifier: ^6.4.1 + version: 6.4.1(@typescript-eslint/parser@6.7.5)(eslint@8.49.0)(typescript@5.2.2) + ava: + specifier: ^5.3.1 + version: 5.3.1(@ava/typescript@4.1.0) + eslint: + specifier: ^8.49.0 + version: 8.49.0 + eslint-config-prettier: + specifier: ^8.8.0 + version: 8.8.0(eslint@8.49.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.0.0(eslint-config-prettier@8.8.0)(eslint@8.49.0)(prettier@3.0.3) + solana-bankrun: + specifier: ^0.1.1 + version: 0.1.1 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.8.4)(typescript@5.2.2) + tsx: + specifier: ^3.12.7 + version: 3.12.7 + typescript: + specifier: ^5.2.2 + version: 5.2.2 packages: @@ -59,8 +98,8 @@ packages: execa: 7.2.0 dev: true - /@babel/runtime@7.22.10: - resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + /@babel/runtime@7.22.11: + resolution: {integrity: sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 @@ -72,6 +111,27 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@esbuild-kit/cjs-loader@2.4.2: + resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} + dependencies: + '@esbuild-kit/core-utils': 3.2.2 + get-tsconfig: 4.7.0 + dev: true + + /@esbuild-kit/core-utils@3.2.2: + resolution: {integrity: sha512-Ub6LaRaAgF80dTSzUdXpFLM1pVDdmEVB9qb5iAzSpyDlX/mfJTFGOnZ516O05p5uWWteNviMKi4PAyEuRxI5gA==} + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.5.5: + resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} + dependencies: + '@esbuild-kit/core-utils': 3.2.2 + get-tsconfig: 4.7.0 + dev: true + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -270,18 +330,18 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.47.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.49.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.47.0 + eslint: 8.49.0 eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/regexpp@4.6.2: - resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + /@eslint-community/regexpp@4.8.0: + resolution: {integrity: sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true @@ -302,13 +362,13 @@ packages: - supports-color dev: true - /@eslint/js@8.47.0: - resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==} + /@eslint/js@8.49.0: + resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@humanwhocodes/config-array@0.11.10: - resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + /@humanwhocodes/config-array@0.11.11: + resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -343,6 +403,42 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@metaplex-foundation/umi-options@0.8.9: + resolution: {integrity: sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A==} + dev: false + + /@metaplex-foundation/umi-public-keys@0.8.9: + resolution: {integrity: sha512-CxMzN7dgVGOq9OcNCJe2casKUpJ3RmTVoOvDFyeoTQuK+vkZ1YSSahbqC1iGuHEtKTLSjtWjKvUU6O7zWFTw3Q==} + dependencies: + '@metaplex-foundation/umi-serializers-encodings': 0.8.9 + dev: false + + /@metaplex-foundation/umi-serializers-core@0.8.9: + resolution: {integrity: sha512-WT82tkiYJ0Qmscp7uTj1Hz6aWQPETwaKLAENAUN5DeWghkuBKtuxyBKVvEOuoXerJSdhiAk0e8DWA4cxcTTQ/w==} + dev: false + + /@metaplex-foundation/umi-serializers-encodings@0.8.9: + resolution: {integrity: sha512-N3VWLDTJ0bzzMKcJDL08U3FaqRmwlN79FyE4BHj6bbAaJ9LEHjDQ9RJijZyWqTm0jE7I750fU7Ow5EZL38Xi6Q==} + dependencies: + '@metaplex-foundation/umi-serializers-core': 0.8.9 + dev: false + + /@metaplex-foundation/umi-serializers-numbers@0.8.9: + resolution: {integrity: sha512-NtBf1fnVNQJHFQjLFzRu2i9GGnigb9hOm/Gfrk628d0q0tRJB7BOM3bs5C61VAs7kJs4yd+pDNVAERJkknQ7Lg==} + dependencies: + '@metaplex-foundation/umi-serializers-core': 0.8.9 + dev: false + + /@metaplex-foundation/umi-serializers@0.8.9: + resolution: {integrity: sha512-Sve8Etm3zqvLSUfza+MYRkjTnCpiaAFT7VWdqeHzA3n58P0AfT3p74RrZwVt/UFkxI+ln8BslwBDJmwzcPkuHw==} + dependencies: + '@metaplex-foundation/umi-options': 0.8.9 + '@metaplex-foundation/umi-public-keys': 0.8.9 + '@metaplex-foundation/umi-serializers-core': 0.8.9 + '@metaplex-foundation/umi-serializers-encodings': 0.8.9 + '@metaplex-foundation/umi-serializers-numbers': 0.8.9 + dev: false + /@noble/curves@1.2.0: resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} dependencies: @@ -382,21 +478,18 @@ packages: is-glob: 4.0.3 open: 9.1.0 picocolors: 1.0.0 - tslib: 2.6.1 + tslib: 2.6.2 dev: true - /@solana/buffer-layout-utils@0.2.0: - resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} - engines: {node: '>= 10'} + /@solana/addresses@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-zmg+ALhjxZApKJKSjeGK7EgMT9NywdvGKlAjyNL2fieiFWp0lRTBmWyjPBCQQGdJjBkayCscq3GQkDF2MhC6fg==} dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.87.1 - bigint-buffer: 1.1.5 - bignumber.js: 9.1.1 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate + '@metaplex-foundation/umi-serializers': 0.8.9 + '@solana/assertions': 2.0.0-experimental.21e994f + dev: false + + /@solana/assertions@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-iGOUpOqkqxzQ/xi4Q3YLiBQPASiQ43NYTalmQm99hmOhySRA4+yyQTmMW1PJ8FAm7Zf86cCiYTf19Exa7+DxoQ==} dev: false /@solana/buffer-layout@4.0.1: @@ -405,26 +498,48 @@ packages: dependencies: buffer: 6.0.3 - /@solana/spl-token@0.3.8(@solana/web3.js@1.87.1): - resolution: {integrity: sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg==} - engines: {node: '>=16'} + /@solana/functional@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-FMXFiTA+hsc9FCv0r47oF7njq/K9x7zh0H+To7tpeqwN65LtJPu5BMG7xZY3rn5TrudgKw6XPuIr3ARbI8+IWA==} + dev: false + + /@solana/instructions@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-PuJJzvT7wtwE5UcGavUppnfVWnoxL8CPhZBb96HpOaQhQ2JuyhN445bfav5KkaUMCE6ubrVzOEqzrbtygD3aBg==} + dev: false + + /@solana/keys@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-Qsm7ARy69PdIuis7TZy8ELyhq0pcRFPXtaZ8vLFUvsukrcWRowiJ8JJs6Q3tA+gQK5vUn9ABp7a7Qs0FHzgbyw==} + dependencies: + '@solana/assertions': 2.0.0-experimental.21e994f + dev: false + + /@solana/rpc-core@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-T7VcTLRi4dsqmpFYdnvcHZFS8Vcgdi6funMUrXcM7ofQqb8vWGJnlX6AX0eIZiVsmoYk5Ki8wW4D6Ul6bXZyZg==} + dependencies: + '@metaplex-foundation/umi-serializers': 0.8.9 + dev: false + + /@solana/rpc-transport@2.0.0-experimental.21e994f(node-fetch@2.7.0)(ws@8.14.2): + resolution: {integrity: sha512-PfGPzRuEodhfLyOD8ZneYQ389SWYgmj1Q/HWQZo8yZMsiAaW/lqCygoW88lecxXKlZF5gJYrBX154kgvGqEM7g==} peerDependencies: - '@solana/web3.js': ^1.47.4 + node-fetch: ^2.6.7 + ws: ^8.14.0 dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/buffer-layout-utils': 0.2.0 - '@solana/web3.js': 1.87.1 - buffer: 6.0.3 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate + node-fetch: 2.7.0 + ws: 8.14.2(bufferutil@4.0.7)(utf-8-validate@5.0.10) + dev: false + + /@solana/transactions@2.0.0-experimental.21e994f: + resolution: {integrity: sha512-DunbTMBzlC7jmTzkFsRm5DhGe+MjaZ8m+SJ7V520mQq+kxrbPrRmI3ikfUVdejg0WaEV4Dy+RwQ5xllsrJ47kA==} + dependencies: + '@metaplex-foundation/umi-serializers': 0.8.9 + '@solana/addresses': 2.0.0-experimental.21e994f + '@solana/keys': 2.0.0-experimental.21e994f dev: false /@solana/web3.js@1.87.1: resolution: {integrity: sha512-E8Y9bNlZ8TQlhOvCx1b7jG+TjA4SJLVwufmIk1+tcQctUhK5HiB1Q8ljd4yQDkFlk6OOeAlAeqvW0YntWJU94Q==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.11 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 '@solana/buffer-layout': 4.0.1 @@ -436,7 +551,7 @@ packages: buffer: 6.0.3 fast-stable-stringify: 1.0.0 jayson: 4.1.0 - node-fetch: 2.6.12 + node-fetch: 2.7.0 rpc-websockets: 7.6.0 superstruct: 0.14.2 transitivePeerDependencies: @@ -444,6 +559,23 @@ packages: - encoding - utf-8-validate + /@solana/web3.js@2.0.0-experimental.21e994f(node-fetch@2.7.0)(ws@8.14.2): + resolution: {integrity: sha512-Yy0D57nlNTDm0BhBRIM85Sn52T6vjxpBRRdwE/FOJJmN92n0Qpc4mTAwOPfEqoVpiTcluUBZ4l8FAWxjGCFMgQ==} + dependencies: + '@metaplex-foundation/umi-serializers': 0.8.9 + '@solana/addresses': 2.0.0-experimental.21e994f + '@solana/functional': 2.0.0-experimental.21e994f + '@solana/instructions': 2.0.0-experimental.21e994f + '@solana/keys': 2.0.0-experimental.21e994f + '@solana/rpc-core': 2.0.0-experimental.21e994f + '@solana/rpc-transport': 2.0.0-experimental.21e994f(node-fetch@2.7.0)(ws@8.14.2) + '@solana/transactions': 2.0.0-experimental.21e994f + fast-stable-stringify: 1.0.0 + transitivePeerDependencies: + - node-fetch + - ws + dev: false + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true @@ -463,7 +595,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.5.0 + '@types/node': 12.20.55 /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} @@ -472,20 +604,23 @@ packages: /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - /@types/node@20.5.0: - resolution: {integrity: sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==} + /@types/node@20.8.4: + resolution: {integrity: sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==} + dependencies: + undici-types: 5.25.3 + dev: true - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + /@types/semver@7.5.1: + resolution: {integrity: sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==} dev: true /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: - '@types/node': 20.5.0 + '@types/node': 12.20.55 - /@typescript-eslint/eslint-plugin@6.7.5(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.2.2): - resolution: {integrity: sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==} + /@typescript-eslint/eslint-plugin@6.4.1(@typescript-eslint/parser@6.7.5)(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -495,26 +630,26 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.7.5 - '@typescript-eslint/type-utils': 6.7.5(eslint@8.47.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.5(eslint@8.47.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.7.5 + '@eslint-community/regexpp': 4.8.0 + '@typescript-eslint/parser': 6.7.5(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.4.1 + '@typescript-eslint/type-utils': 6.4.1(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.4.1(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.4.1 debug: 4.3.4 - eslint: 8.47.0 + eslint: 8.49.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.2.2) + ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.2.2): - resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==} + /@typescript-eslint/parser@6.7.5(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -523,23 +658,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.4.0 - '@typescript-eslint/types': 6.4.0 - '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.4.0 + '@typescript-eslint/scope-manager': 6.7.5 + '@typescript-eslint/types': 6.7.5 + '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.5 debug: 4.3.4 - eslint: 8.47.0 + eslint: 8.49.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@6.4.0: - resolution: {integrity: sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==} + /@typescript-eslint/scope-manager@6.4.1: + resolution: {integrity: sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.4.0 - '@typescript-eslint/visitor-keys': 6.4.0 + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/visitor-keys': 6.4.1 dev: true /@typescript-eslint/scope-manager@6.7.5: @@ -550,8 +685,8 @@ packages: '@typescript-eslint/visitor-keys': 6.7.5 dev: true - /@typescript-eslint/type-utils@6.7.5(eslint@8.47.0)(typescript@5.2.2): - resolution: {integrity: sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==} + /@typescript-eslint/type-utils@6.4.1(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -560,18 +695,18 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.5(eslint@8.47.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2) + '@typescript-eslint/utils': 6.4.1(eslint@8.49.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.47.0 - ts-api-utils: 1.0.1(typescript@5.2.2) + eslint: 8.49.0 + ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@6.4.0: - resolution: {integrity: sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==} + /@typescript-eslint/types@6.4.1: + resolution: {integrity: sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==} engines: {node: ^16.0.0 || >=18.0.0} dev: true @@ -580,8 +715,8 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.4.0(typescript@5.2.2): - resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==} + /@typescript-eslint/typescript-estree@6.4.1(typescript@5.2.2): + resolution: {integrity: sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -589,13 +724,13 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.4.0 - '@typescript-eslint/visitor-keys': 6.4.0 + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/visitor-keys': 6.4.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.2.2) + ts-api-utils: 1.0.2(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -616,36 +751,36 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.2.2) + ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@6.7.5(eslint@8.47.0)(typescript@5.2.2): - resolution: {integrity: sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==} + /@typescript-eslint/utils@6.4.1(eslint@8.49.0)(typescript@5.2.2): + resolution: {integrity: sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 6.7.5 - '@typescript-eslint/types': 6.7.5 - '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2) - eslint: 8.47.0 + '@types/semver': 7.5.1 + '@typescript-eslint/scope-manager': 6.4.1 + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2) + eslint: 8.49.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@6.4.0: - resolution: {integrity: sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==} + /@typescript-eslint/visitor-keys@6.4.1: + resolution: {integrity: sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.4.0 + '@typescript-eslint/types': 6.4.1 eslint-visitor-keys: 3.4.3 dev: true @@ -852,10 +987,6 @@ packages: dependencies: bindings: 1.5.0 - /bignumber.js@9.1.1: - resolution: {integrity: sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==} - dev: false - /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -921,7 +1052,7 @@ packages: engines: {node: '>=6.14.2'} requiresBuild: true dependencies: - node-gyp-build: 4.6.0 + node-gyp-build: 4.6.1 /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} @@ -1229,17 +1360,17 @@ packages: engines: {node: '>=12'} dev: true - /eslint-config-prettier@9.0.0(eslint@8.47.0): - resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} + /eslint-config-prettier@8.8.0(eslint@8.49.0): + resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.47.0 + eslint: 8.49.0 dev: true - /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.47.0)(prettier@3.0.2): - resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} + /eslint-plugin-prettier@5.0.0(eslint-config-prettier@8.8.0)(eslint@8.49.0)(prettier@3.0.3): + resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -1252,9 +1383,9 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.47.0 - eslint-config-prettier: 9.0.0(eslint@8.47.0) - prettier: 3.0.2 + eslint: 8.49.0 + eslint-config-prettier: 8.8.0(eslint@8.49.0) + prettier: 3.0.3 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 dev: true @@ -1272,16 +1403,16 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.47.0: - resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} + /eslint@8.49.0: + resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) - '@eslint-community/regexpp': 4.6.2 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/regexpp': 4.8.0 '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.47.0 - '@humanwhocodes/config-array': 0.11.10 + '@eslint/js': 8.49.0 + '@humanwhocodes/config-array': 0.11.11 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 @@ -1442,7 +1573,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.0.4 + flat-cache: 3.1.0 dev: true /file-uri-to-path@1.0.0: @@ -1471,11 +1602,12 @@ packages: path-exists: 5.0.0 dev: true - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + /flat-cache@3.1.0: + resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} + engines: {node: '>=12.0.0'} dependencies: flatted: 3.2.7 + keyv: 4.5.3 rimraf: 3.0.2 dev: true @@ -1505,8 +1637,8 @@ packages: engines: {node: '>=10'} dev: true - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + /get-tsconfig@4.7.0: + resolution: {integrity: sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==} dependencies: resolve-pkg-maps: 1.0.0 dev: true @@ -1783,6 +1915,10 @@ packages: argparse: 2.0.1 dev: true + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -1798,6 +1934,12 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + /keyv@4.5.3: + resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} + dependencies: + json-buffer: 3.0.1 + dev: true + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1917,8 +2059,8 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /node-fetch@2.6.12: - resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 @@ -1928,8 +2070,8 @@ packages: dependencies: whatwg-url: 5.0.0 - /node-gyp-build@4.6.0: - resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + /node-gyp-build@4.6.1: + resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} hasBin: true requiresBuild: true @@ -2129,8 +2271,8 @@ packages: fast-diff: 1.3.0 dev: true - /prettier@3.0.2: - resolution: {integrity: sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==} + /prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} engines: {node: '>=14'} hasBin: true dev: true @@ -2202,10 +2344,10 @@ packages: /rpc-websockets@7.6.0: resolution: {integrity: sha512-Jgcs8q6t8Go98dEulww1x7RysgTkzpCMelVxZW4hvuyFtOGpeUz9prpr2KjUa/usqxgFCd9Tu3+yhHEP9GVmiQ==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.11 eventemitter3: 4.0.7 uuid: 8.3.2 - ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) + ws: 8.14.2(bufferutil@4.0.7)(utf-8-validate@5.0.10) optionalDependencies: bufferutil: 4.0.7 utf-8-validate: 5.0.10 @@ -2280,8 +2422,8 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true - /solana-bankrun-darwin-arm64@0.2.0: - resolution: {integrity: sha512-ENQ5Z/CYeY8ZVWIc2VutY/gMlBaHi93/kDw9w0iVwewoV+/YpQmP2irwrshIKu6ggRPTF3Ehlh2V6fGVIYWcXw==} + /solana-bankrun-darwin-arm64@0.1.1: + resolution: {integrity: sha512-BYGrp9bWVaHKvPthKSLTCXWK7zXtH2m4YM6mJg0rCvJeSoRJt7S5K2JER4gXm0LglDXeD5Bg7NL+QM24t2kQmg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -2289,16 +2431,16 @@ packages: dev: true optional: true - /solana-bankrun-darwin-universal@0.2.0: - resolution: {integrity: sha512-HE45TvZXzBipm1fMn87+fkHeIuQ/KFAi5G/S29y/TLuBYt4RDI935RkWiT0rEQ7KwnwO6Y1aTsOaQXldY5R7uQ==} + /solana-bankrun-darwin-universal@0.1.1: + resolution: {integrity: sha512-c/knJppc7hlmEBwKmFyQhAcGiIvh8UgteEn9CwsfcqpUZpu2qCDSV3+VJmcGmuYNlXt+IEK9lTcTD6qu6EoM6w==} engines: {node: '>= 10'} os: [darwin] requiresBuild: true dev: true optional: true - /solana-bankrun-darwin-x64@0.2.0: - resolution: {integrity: sha512-42UsVrnac2Oo4UaIDo60zfI3Xn1i8W6fmcc9ixJQZNTtdO8o2/sY4mFxcJx9lhLMhda5FPHrQbGYgYdIs0kK0g==} + /solana-bankrun-darwin-x64@0.1.1: + resolution: {integrity: sha512-SMefIPsRVzhRXFpl0lcB+zNNm00b02yfm0cl+G3Abcm0v80Me7dNyv7QSLmwBzRXraSJOgHfobSfp610r9GA3g==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -2306,8 +2448,8 @@ packages: dev: true optional: true - /solana-bankrun-linux-x64-gnu@0.2.0: - resolution: {integrity: sha512-WnqQjfBBdcI0ZLysjvRStI8gX7vm1c3CI6CC03lgkUztH+Chcq9C4LI9m2M8mXza8Xkn9ryeKAmX36Bx/yoVzg==} + /solana-bankrun-linux-x64-gnu@0.1.1: + resolution: {integrity: sha512-l4YUpD6Lkv0I2WHIAg6vNTQXTIARanGyMM8WozFqYlcyYKWm/iR8MQ/+HxCcUnbSpsFX02eYq8GiBbzjNaCiuA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2315,8 +2457,8 @@ packages: dev: true optional: true - /solana-bankrun-linux-x64-musl@0.2.0: - resolution: {integrity: sha512-8mtf14ZBoah30+MIJBUwb5BlGLRZyK5cZhCkYnC/ROqaIDN8RxMM44NL63gTUIaNHsFwWGA9xR0KSeljeh3PKQ==} + /solana-bankrun-linux-x64-musl@0.1.1: + resolution: {integrity: sha512-w9CtdJav2esrJISurzCiqk2hiJSqHrzmJkrnZoA22PjqrfCYRCX7zLnB4RQfZN9dM7/U8Z5z8LoMR55EhXQ7Tw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2324,18 +2466,18 @@ packages: dev: true optional: true - /solana-bankrun@0.2.0: - resolution: {integrity: sha512-TS6vYoO/9YJZng7oiLOVyuz8V7yLow5Hp4SLYWW71XM3702v+z9f1fvUBKudRfa4dfpta4tRNufApSiBIALxJQ==} + /solana-bankrun@0.1.1: + resolution: {integrity: sha512-JkoU7yEyaJAZDMw5wGsFXOI/BNTk1lwcPX6Kgg9dBrCo0xs1VTn/ipwjKXwdRFLKF9N2kt4x+bLAipDp7W6GiA==} engines: {node: '>= 10'} dependencies: '@solana/web3.js': 1.87.1 bs58: 4.0.1 optionalDependencies: - solana-bankrun-darwin-arm64: 0.2.0 - solana-bankrun-darwin-universal: 0.2.0 - solana-bankrun-darwin-x64: 0.2.0 - solana-bankrun-linux-x64-gnu: 0.2.0 - solana-bankrun-linux-x64-musl: 0.2.0 + solana-bankrun-darwin-arm64: 0.1.1 + solana-bankrun-darwin-universal: 0.1.1 + solana-bankrun-darwin-x64: 0.1.1 + solana-bankrun-linux-x64-gnu: 0.1.1 + solana-bankrun-linux-x64-musl: 0.1.1 transitivePeerDependencies: - bufferutil - encoding @@ -2437,7 +2579,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/utils': 2.4.2 - tslib: 2.6.1 + tslib: 2.6.2 dev: true /temp-dir@3.0.0: @@ -2475,8 +2617,8 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - /ts-api-utils@1.0.1(typescript@5.2.2): - resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} + /ts-api-utils@1.0.2(typescript@5.2.2): + resolution: {integrity: sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==} engines: {node: '>=16.13.0'} peerDependencies: typescript: '>=4.2.0' @@ -2484,7 +2626,16 @@ packages: typescript: 5.2.2 dev: true - /ts-node@10.9.1(@types/node@20.5.0)(typescript@5.2.2): + /ts-api-utils@1.0.3(typescript@5.2.2): + resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.2.2 + dev: true + + /ts-node@10.9.1(@types/node@20.8.4)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -2503,7 +2654,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.5.0 + '@types/node': 20.8.4 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -2515,17 +2666,17 @@ packages: yn: 3.1.1 dev: true - /tslib@2.6.1: - resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: true - /tsx@3.13.0: - resolution: {integrity: sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==} + /tsx@3.12.7: + resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==} hasBin: true dependencies: - esbuild: 0.18.20 - get-tsconfig: 4.7.2 - source-map-support: 0.5.21 + '@esbuild-kit/cjs-loader': 2.4.2 + '@esbuild-kit/core-utils': 3.2.2 + '@esbuild-kit/esm-loader': 2.5.5 optionalDependencies: fsevents: 2.3.3 dev: true @@ -2553,6 +2704,10 @@ packages: hasBin: true dev: true + /undici-types@5.25.3: + resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} + dev: true + /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -2569,7 +2724,7 @@ packages: engines: {node: '>=6.14.2'} requiresBuild: true dependencies: - node-gyp-build: 4.6.0 + node-gyp-build: 4.6.1 /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} @@ -2634,8 +2789,8 @@ packages: utf-8-validate: optional: true - /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): - resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + /ws@8.14.2(bufferutil@4.0.7)(utf-8-validate@5.0.10): + resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 diff --git a/single-pool/js/pnpm-workspace.yaml b/single-pool/js/pnpm-workspace.yaml new file mode 100644 index 00000000000..dee51e928d8 --- /dev/null +++ b/single-pool/js/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/single-pool/js/src/addresses.ts b/single-pool/js/src/addresses.ts deleted file mode 100644 index 490e9971386..00000000000 --- a/single-pool/js/src/addresses.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PublicKey, StakeProgram } from '@solana/web3.js'; - -import { defaultDepositAccountSeed } from './internal'; - -export function findPoolAddress(programId: PublicKey, voteAccountAddress: PublicKey) { - return findPda(programId, voteAccountAddress, 'pool'); -} - -export function findPoolStakeAddress(programId: PublicKey, poolAddress: PublicKey) { - return findPda(programId, poolAddress, 'stake'); -} - -export function findPoolMintAddress(programId: PublicKey, poolAddress: PublicKey) { - return findPda(programId, poolAddress, 'mint'); -} - -export function findPoolStakeAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { - return findPda(programId, poolAddress, 'stake_authority'); -} - -export function findPoolMintAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { - return findPda(programId, poolAddress, 'mint_authority'); -} - -export function findPoolMplAuthorityAddress(programId: PublicKey, poolAddress: PublicKey) { - return findPda(programId, poolAddress, 'mpl_authority'); -} - -function findPda(programId: PublicKey, baseAddress: PublicKey, prefix: string) { - const [publicKey] = PublicKey.findProgramAddressSync( - [Buffer.from(prefix), baseAddress.toBuffer()], - programId, - ); - return publicKey; -} - -export async function findDefaultDepositAccountAddress( - poolAddress: PublicKey, - userWallet: PublicKey, -) { - return PublicKey.createWithSeed( - userWallet, - defaultDepositAccountSeed(poolAddress), - StakeProgram.programId, - ); -} diff --git a/single-pool/js/src/instructions.ts b/single-pool/js/src/instructions.ts deleted file mode 100644 index 5bebbae924c..00000000000 --- a/single-pool/js/src/instructions.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { - PublicKey, - TransactionInstruction, - SYSVAR_RENT_PUBKEY, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_STAKE_HISTORY_PUBKEY, - STAKE_CONFIG_ID, - StakeProgram, - SystemProgram, -} from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { Buffer } from 'buffer'; - -import { MPL_METADATA_PROGRAM_ID, findMplMetadataAddress } from './mpl_metadata'; -import { - findPoolAddress, - findPoolStakeAddress, - findPoolMintAddress, - findPoolStakeAuthorityAddress, - findPoolMintAuthorityAddress, - findPoolMplAuthorityAddress, -} from './addresses'; -import { - encodeData, - SINGLE_POOL_PROGRAM_ID, - SINGLE_POOL_INSTRUCTION_LAYOUTS, - updateTokenMetadataLayout, -} from './internal'; - -export class SinglePoolInstruction { - static initializePool(voteAccount: PublicKey): TransactionInstruction { - const programId = SINGLE_POOL_PROGRAM_ID; - const pool = findPoolAddress(programId, voteAccount); - - const keys = [ - { pubkey: voteAccount, isSigner: false, isWritable: false }, - { pubkey: pool, isSigner: false, isWritable: true }, - { pubkey: findPoolStakeAddress(programId, pool), isSigner: false, isWritable: true }, - { pubkey: findPoolMintAddress(programId, pool), isSigner: false, isWritable: true }, - { - pubkey: findPoolStakeAuthorityAddress(programId, pool), - isSigner: false, - isWritable: false, - }, - { pubkey: findPoolMintAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: StakeProgram.programId, isSigner: false, isWritable: false }, - ]; - - const type = SINGLE_POOL_INSTRUCTION_LAYOUTS.InitializePool; - const data = encodeData(type); - - return new TransactionInstruction({ - programId, - keys, - data, - }); - } - - static depositStake( - pool: PublicKey, - userStakeAccount: PublicKey, - userTokenAccount: PublicKey, - userLamportAccount: PublicKey, - ): TransactionInstruction { - const programId = SINGLE_POOL_PROGRAM_ID; - - const keys = [ - { pubkey: pool, isSigner: false, isWritable: false }, - { pubkey: findPoolStakeAddress(programId, pool), isSigner: false, isWritable: true }, - { pubkey: findPoolMintAddress(programId, pool), isSigner: false, isWritable: true }, - { - pubkey: findPoolStakeAuthorityAddress(programId, pool), - isSigner: false, - isWritable: false, - }, - { pubkey: findPoolMintAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: userStakeAccount, isSigner: false, isWritable: true }, - { pubkey: userTokenAccount, isSigner: false, isWritable: true }, - { pubkey: userLamportAccount, isSigner: false, isWritable: true }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: StakeProgram.programId, isSigner: false, isWritable: false }, - ]; - - const type = SINGLE_POOL_INSTRUCTION_LAYOUTS.DepositStake; - const data = encodeData(type); - - return new TransactionInstruction({ - programId, - keys, - data, - }); - } - - static withdrawStake( - pool: PublicKey, - userStakeAccount: PublicKey, - userStakeAuthority: PublicKey, - userTokenAccount: PublicKey, - userTokenAuthority: PublicKey, - tokenAmount: number | bigint, - ): TransactionInstruction { - const programId = SINGLE_POOL_PROGRAM_ID; - - const keys = [ - { pubkey: pool, isSigner: false, isWritable: false }, - { pubkey: findPoolStakeAddress(programId, pool), isSigner: false, isWritable: true }, - { pubkey: findPoolMintAddress(programId, pool), isSigner: false, isWritable: true }, - { - pubkey: findPoolStakeAuthorityAddress(programId, pool), - isSigner: false, - isWritable: false, - }, - { pubkey: findPoolMintAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: userStakeAccount, isSigner: false, isWritable: true }, - { pubkey: userTokenAccount, isSigner: false, isWritable: true }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: StakeProgram.programId, isSigner: false, isWritable: false }, - ]; - - const type = SINGLE_POOL_INSTRUCTION_LAYOUTS.WithdrawStake; - const data = encodeData(type, { - userStakeAuthority: userStakeAuthority.toBuffer(), - tokenAmount, - }); - - return new TransactionInstruction({ - programId, - keys, - data, - }); - } - - static createTokenMetadata(pool: PublicKey, payer: PublicKey): TransactionInstruction { - const programId = SINGLE_POOL_PROGRAM_ID; - const mint = findPoolMintAddress(programId, pool); - - const keys = [ - { pubkey: pool, isSigner: false, isWritable: false }, - { pubkey: mint, isSigner: false, isWritable: false }, - { pubkey: findPoolMintAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: findPoolMplAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: payer, isSigner: true, isWritable: true }, - { pubkey: findMplMetadataAddress(mint), isSigner: false, isWritable: true }, - { pubkey: MPL_METADATA_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - ]; - - const type = SINGLE_POOL_INSTRUCTION_LAYOUTS.CreateTokenMetadata; - const data = encodeData(type); - - return new TransactionInstruction({ - programId, - keys, - data, - }); - } - - static updateTokenMetadata( - voteAccount: PublicKey, - authorizedWithdrawer: PublicKey, - tokenName: string, - tokenSymbol: string, - tokenUri?: string, - ): TransactionInstruction { - const programId = SINGLE_POOL_PROGRAM_ID; - const pool = findPoolAddress(programId, voteAccount); - - tokenUri = tokenUri || ''; - - const keys = [ - { pubkey: voteAccount, isSigner: false, isWritable: false }, - { pubkey: pool, isSigner: false, isWritable: false }, - { pubkey: findPoolMplAuthorityAddress(programId, pool), isSigner: false, isWritable: false }, - { pubkey: authorizedWithdrawer, isSigner: true, isWritable: false }, - { - pubkey: findMplMetadataAddress(findPoolMintAddress(programId, pool)), - isSigner: false, - isWritable: true, - }, - { pubkey: MPL_METADATA_PROGRAM_ID, isSigner: false, isWritable: false }, - ]; - - const type = updateTokenMetadataLayout(tokenName.length, tokenSymbol.length, tokenUri.length); - - const data = encodeData(type, { - tokenNameLen: tokenName.length, - tokenName: Buffer.from(tokenName), - tokenSymbolLen: tokenSymbol.length, - tokenSymbol: Buffer.from(tokenSymbol), - tokenUriLen: tokenUri.length, - tokenUri: Buffer.from(tokenUri), - }); - - return new TransactionInstruction({ - programId, - keys, - data, - }); - } -} diff --git a/single-pool/js/src/internal.ts b/single-pool/js/src/internal.ts deleted file mode 100644 index b1af35cbbde..00000000000 --- a/single-pool/js/src/internal.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { PublicKey } from '@solana/web3.js'; -import * as BufferLayout from '@solana/buffer-layout'; -import { Buffer } from 'buffer'; - -// FIXME replace with real id when we have one -export const SINGLE_POOL_PROGRAM_ID = new PublicKey('3cqnsMsT6LE96pxv7GR4di5rLqHDZZbR3FbeSUeRLFqY'); - -export function defaultDepositAccountSeed(poolAddress: PublicKey): string { - return 'svsp' + poolAddress.toString().slice(0, 28); -} - -export type InstructionType = { - /** The Instruction index (from solana upstream program) */ - index: number; - /** The BufferLayout to use to build data */ - layout: BufferLayout.Layout; -}; - -export function encodeData(type: InstructionType, fields?: any): Buffer { - const allocLength = type.layout.span; - const data = Buffer.alloc(allocLength); - const layoutFields = Object.assign({ instruction: type.index }, fields); - type.layout.encode(layoutFields, data); - - return data; -} - -export function decodeData(type: InstructionType, buffer: Buffer): any { - let data; - try { - data = type.layout.decode(buffer); - } catch (err) { - throw new Error('invalid instruction; ' + err); - } - - if (data.instruction !== type.index) { - throw new Error( - `invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`, - ); - } - - return data; -} - -// UpdateTokenMetadata is omitted here because its size is runtime-dependent -type SinglePoolInstructionType = - | 'InitializePool' - | 'DepositStake' - | 'WithdrawStake' - | 'CreateTokenMetadata'; - -export const SINGLE_POOL_INSTRUCTION_LAYOUTS: { - [type in SinglePoolInstructionType]: InstructionType; -} = Object.freeze({ - InitializePool: { - index: 0, - layout: BufferLayout.struct([BufferLayout.u8('instruction')]), - }, - DepositStake: { - index: 2, - layout: BufferLayout.struct([BufferLayout.u8('instruction')]), - }, - WithdrawStake: { - index: 3, - layout: BufferLayout.struct([ - BufferLayout.u8('instruction'), - BufferLayout.seq(BufferLayout.u8(), 32, 'userStakeAuthority'), - BufferLayout.ns64('tokenAmount'), - ]), - }, - CreateTokenMetadata: { - index: 4, - layout: BufferLayout.struct([BufferLayout.u8('instruction')]), - }, -}); - -export function updateTokenMetadataLayout( - nameLength: number, - symbolLength: number, - uriLength: number, -) { - if (nameLength > 32) { - throw 'maximum token name length is 32 characters'; - } - - if (symbolLength > 10) { - throw 'maximum token symbol length is 10 characters'; - } - - if (uriLength > 200) { - throw 'maximum token uri length is 200 characters'; - } - - return { - index: 5, - layout: BufferLayout.struct([ - BufferLayout.u8('instruction'), - BufferLayout.u32('tokenNameLen'), - BufferLayout.blob(nameLength, 'tokenName'), - BufferLayout.u32('tokenSymbolLen'), - BufferLayout.blob(symbolLength, 'tokenSymbol'), - BufferLayout.u32('tokenUriLen'), - BufferLayout.blob(uriLength, 'tokenUri'), - ]), - }; -} diff --git a/single-pool/js/src/transactions.ts b/single-pool/js/src/transactions.ts deleted file mode 100644 index 5fcf1ebde6a..00000000000 --- a/single-pool/js/src/transactions.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { - PublicKey, - Connection, - Authorized, - Transaction, - StakeProgram, - SystemProgram, - StakeAuthorizationLayout, -} from '@solana/web3.js'; -import { - MINT_SIZE, - getAssociatedTokenAddressSync, - createAssociatedTokenAccountInstruction, - createApproveInstruction, -} from '@solana/spl-token'; - -import { - findDefaultDepositAccountAddress, - findPoolAddress, - findPoolStakeAddress, - findPoolMintAddress, - findPoolStakeAuthorityAddress, - findPoolMintAuthorityAddress, -} from './addresses'; -import { SinglePoolInstruction } from './instructions'; -import { SINGLE_POOL_PROGRAM_ID, defaultDepositAccountSeed } from './internal'; - -interface DepositParams { - connection: Connection; - pool: PublicKey; - userWallet: PublicKey; - userStakeAccount?: PublicKey; - depositFromDefaultAccount?: boolean; - userTokenAccount?: PublicKey; - userLamportAccount?: PublicKey; - userWithdrawAuthority?: PublicKey; -} - -interface WithdrawParams { - connection: Connection; - pool: PublicKey; - userWallet: PublicKey; - userStakeAccount: PublicKey; - tokenAmount: number | bigint; - createStakeAccount?: boolean; - userStakeAuthority?: PublicKey; - userTokenAccount?: PublicKey; - userTokenAuthority?: PublicKey; -} - -export class SinglePoolProgram { - static programId: PublicKey = SINGLE_POOL_PROGRAM_ID; - - static space: number = 33; - - static async initialize( - connection: Connection, - voteAccount: PublicKey, - payer: PublicKey, - skipMetadata = false, - ) { - const transaction = new Transaction(); - - const pool = findPoolAddress(this.programId, voteAccount); - const stake = findPoolStakeAddress(this.programId, pool); - const mint = findPoolMintAddress(this.programId, pool); - - const poolRent = await connection.getMinimumBalanceForRentExemption(this.space); - const stakeRent = await connection.getMinimumBalanceForRentExemption(StakeProgram.space); - const mintRent = await connection.getMinimumBalanceForRentExemption(MINT_SIZE); - const minimumDelegation = (await connection.getStakeMinimumDelegation()).value; - - transaction.add( - SystemProgram.transfer({ - fromPubkey: payer, - toPubkey: pool, - lamports: poolRent, - }), - ); - - transaction.add( - SystemProgram.transfer({ - fromPubkey: payer, - toPubkey: stake, - lamports: stakeRent + minimumDelegation, - }), - ); - - transaction.add( - SystemProgram.transfer({ - fromPubkey: payer, - toPubkey: mint, - lamports: mintRent, - }), - ); - - transaction.add(SinglePoolInstruction.initializePool(voteAccount)); - - if (!skipMetadata) { - transaction.add(SinglePoolInstruction.createTokenMetadata(pool, payer)); - } - - return transaction; - } - - static async deposit(params: DepositParams) { - const { connection, pool, userWallet } = params; - - // note this is just "if not xor" - if (!params.userStakeAccount == !params.depositFromDefaultAccount) { - throw 'must either provide userStakeAccount or true depositFromDefaultAccount'; - } - - const userStakeAccount = params.depositFromDefaultAccount - ? await findDefaultDepositAccountAddress(pool, userWallet) - : params.userStakeAccount; - - const transaction = new Transaction(); - - const mint = findPoolMintAddress(this.programId, pool); - const poolStakeAuthority = findPoolStakeAuthorityAddress(this.programId, pool); - const userAssociatedTokenAccount = getAssociatedTokenAddressSync(mint, userWallet); - - const userTokenAccount = params.userTokenAccount || userAssociatedTokenAccount; - const userLamportAccount = params.userLamportAccount || userWallet; - const userWithdrawAuthority = params.userWithdrawAuthority || userWallet; - - if ( - userTokenAccount.equals(userAssociatedTokenAccount) && - (await connection.getAccountInfo(userAssociatedTokenAccount)) == null - ) { - transaction.add( - createAssociatedTokenAccountInstruction( - userWallet, - userAssociatedTokenAccount, - userWallet, - mint, - ), - ); - } - - transaction.add( - StakeProgram.authorize({ - stakePubkey: userStakeAccount as PublicKey, - authorizedPubkey: userWithdrawAuthority, - newAuthorizedPubkey: poolStakeAuthority, - stakeAuthorizationType: StakeAuthorizationLayout.Staker, - }), - ); - - transaction.add( - StakeProgram.authorize({ - stakePubkey: userStakeAccount as PublicKey, - authorizedPubkey: userWithdrawAuthority, - newAuthorizedPubkey: poolStakeAuthority, - stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, - }), - ); - - transaction.add( - SinglePoolInstruction.depositStake( - pool, - userStakeAccount as PublicKey, - userTokenAccount, - userLamportAccount, - ), - ); - - return transaction; - } - - static async withdraw(params: WithdrawParams) { - const { connection, pool, userWallet, userStakeAccount, tokenAmount, createStakeAccount } = - params; - - const transaction = new Transaction(); - - const poolMintAuthority = findPoolMintAuthorityAddress(this.programId, pool); - - const userStakeAuthority = params.userStakeAuthority || userWallet; - const userTokenAccount = - params.userTokenAccount || - getAssociatedTokenAddressSync(findPoolMintAddress(this.programId, pool), userWallet); - const userTokenAuthority = params.userTokenAuthority || userWallet; - - if (createStakeAccount) { - transaction.add( - SystemProgram.createAccount({ - fromPubkey: userWallet, - lamports: await connection.getMinimumBalanceForRentExemption(StakeProgram.space), - newAccountPubkey: userStakeAccount, - programId: StakeProgram.programId, - space: StakeProgram.space, - }), - ); - } - - transaction.add( - createApproveInstruction( - userTokenAccount, - poolMintAuthority, - userTokenAuthority, - tokenAmount, - ), - ); - - transaction.add( - SinglePoolInstruction.withdrawStake( - pool, - userStakeAccount, - userStakeAuthority, - userTokenAccount, - userTokenAuthority, - tokenAmount, - ), - ); - - return transaction; - } - - static createTokenMetadata(pool: PublicKey, payer: PublicKey) { - const transaction = new Transaction(); - transaction.add(SinglePoolInstruction.createTokenMetadata(pool, payer)); - - return transaction; - } - - static updateTokenMetadata( - voteAccount: PublicKey, - authorizedWithdrawer: PublicKey, - name: string, - symbol: string, - uri?: string, - ) { - const transaction = new Transaction(); - transaction.add( - SinglePoolInstruction.updateTokenMetadata( - voteAccount, - authorizedWithdrawer, - name, - symbol, - uri, - ), - ); - - return transaction; - } - - static async createAndDelegateUserStake( - connection: Connection, - voteAccount: PublicKey, - userWallet: PublicKey, - stakeAmount: number | bigint, - ) { - const transaction = new Transaction(); - - const pool = findPoolAddress(this.programId, voteAccount); - const stakeAccount = await findDefaultDepositAccountAddress(pool, userWallet); - - const stakeRent = await connection.getMinimumBalanceForRentExemption(StakeProgram.space); - - // web3.js only supports number, so if amount is a bigint, we check that the conversion will be safe - if ( - typeof stakeAmount == 'bigint' && - stakeAmount + BigInt(stakeRent) > BigInt(Number.MAX_SAFE_INTEGER) - ) { - throw 'cannot convert stakeAmount to Number'; - } - - transaction.add( - SystemProgram.createAccountWithSeed({ - basePubkey: userWallet, - fromPubkey: userWallet, - lamports: Number(stakeAmount) + stakeRent, - newAccountPubkey: stakeAccount, - programId: StakeProgram.programId, - seed: defaultDepositAccountSeed(pool), - space: StakeProgram.space, - }), - ); - - transaction.add( - StakeProgram.initialize({ - authorized: new Authorized(userWallet, userWallet), - stakePubkey: stakeAccount, - }), - ); - - transaction.add( - StakeProgram.delegate({ - authorizedPubkey: userWallet, - stakePubkey: stakeAccount, - votePubkey: voteAccount, - }), - ); - - return transaction; - } -} diff --git a/single-pool/js/tests/fixtures/mpl_token_metadata.so b/single-pool/js/tests/fixtures/mpl_token_metadata.so deleted file mode 120000 index 43816797329..00000000000 --- a/single-pool/js/tests/fixtures/mpl_token_metadata.so +++ /dev/null @@ -1 +0,0 @@ -../../../../stake-pool/program/tests/fixtures/mpl_token_metadata.so \ No newline at end of file diff --git a/single-pool/js/tests/fixtures/spl_single_validator_pool.so b/single-pool/js/tests/fixtures/spl_single_validator_pool.so deleted file mode 120000 index a0967084234..00000000000 --- a/single-pool/js/tests/fixtures/spl_single_validator_pool.so +++ /dev/null @@ -1 +0,0 @@ -../../../../target/deploy/spl_single_validator_pool.so \ No newline at end of file