From e8fbb7f65e138edc945c357188c398394baa82bb Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Fri, 15 May 2026 16:37:13 +0100 Subject: [PATCH] Migrate JS client to Kit plugins This PR replaces the ad-hoc top-level helpers with a Kit-plugin-based client (`createTestClient()` + `programMetadataProgram()`) and a uniform `(client, input)` API for the metadata and buffer helpers. The plugin exposes `client.programMetadata.{createMetadata,updateMetadata,writeMetadata}` one-shots, composable instruction-plan variants, and new `createBuffer`/`createCanonicalBuffer`/`createNonCanonicalBuffer` instructions, all defaulting `payer` to `client.payer`. `MetadataInput` now takes optional `metadata` and `programData`, removing the RPC-based canonicity probe. Bumps `@solana/kit` to ^6.9, adds `@solana/kit-plugin-rpc` and `@solana/kit-plugin-signer`, bumps `@solana-program/system` to ^0.12 and `@solana-program/compute-budget` to ^0.15, and moves to flat ESLint 9 config. Tests rewritten on top of the plugin; `_setup.ts` is now down to genuine fixtures. --- clients/js/.eslintrc.cjs | 17 - clients/js/eslint.config.mjs | 20 + clients/js/package.json | 12 +- clients/js/pnpm-lock.yaml | 761 ++++++++++++++----- clients/js/src/cli/commands/create-buffer.ts | 9 +- clients/js/src/cli/commands/create.ts | 4 +- clients/js/src/cli/commands/update-buffer.ts | 9 +- clients/js/src/cli/commands/update.ts | 4 +- clients/js/src/cli/commands/write.ts | 4 +- clients/js/src/cli/logs.ts | 2 +- clients/js/src/cli/utils.ts | 44 +- clients/js/src/createBuffer.ts | 174 ++++- clients/js/src/createMetadata.ts | 110 +-- clients/js/src/fetchMetadataContent.ts | 2 +- clients/js/src/index.ts | 10 + clients/js/src/internals.ts | 17 +- clients/js/src/packData.ts | 4 +- clients/js/src/plugin.ts | 159 ++++ clients/js/src/updateBuffer.ts | 31 +- clients/js/src/updateMetadata.ts | 196 ++--- clients/js/src/utils.ts | 43 +- clients/js/src/writeMetadata.ts | 80 +- clients/js/test/_setup.ts | 404 ++-------- clients/js/test/allocate.test.ts | 118 +-- clients/js/test/close.test.ts | 339 ++++----- clients/js/test/createMetadata.test.ts | 114 ++- clients/js/test/extend.test.ts | 174 +++-- clients/js/test/fetchMetadataContent.test.ts | 42 +- clients/js/test/initialize.test.ts | 321 ++++---- clients/js/test/loader-v3/shared.ts | 2 +- clients/js/test/setAuthority.test.ts | 478 ++++++------ clients/js/test/setData.test.ts | 545 +++++++------ clients/js/test/setImmutable.test.ts | 117 ++- clients/js/test/trim.test.ts | 201 +++-- clients/js/test/updateMetadata.test.ts | 179 ++--- clients/js/test/write.test.ts | 198 ++--- clients/js/test/writeMetadata.test.ts | 50 +- 37 files changed, 2525 insertions(+), 2469 deletions(-) delete mode 100644 clients/js/.eslintrc.cjs create mode 100644 clients/js/eslint.config.mjs create mode 100644 clients/js/src/plugin.ts diff --git a/clients/js/.eslintrc.cjs b/clients/js/.eslintrc.cjs deleted file mode 100644 index 96a231d..0000000 --- a/clients/js/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - extends: ['@solana/eslint-config-solana'], - ignorePatterns: ['.eslintrc.cjs', 'tsup.config.ts', 'env-shim.ts', 'bin/cli.cjs'], - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - rules: { - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/sort-type-constituents': 'off', - 'prefer-destructuring': 'off', - 'simple-import-sort/imports': 'off', - 'sort-keys-fix/sort-keys-fix': 'off', - 'typescript-sort-keys/interface': 'off', - }, -}; diff --git a/clients/js/eslint.config.mjs b/clients/js/eslint.config.mjs new file mode 100644 index 0000000..93b643d --- /dev/null +++ b/clients/js/eslint.config.mjs @@ -0,0 +1,20 @@ +import solanaConfig from '@solana-config/eslint'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig([ + { ignores: ['**/dist/**'] }, + { files: ['**/*.ts', '**/*.(c|m)?js'], extends: [solanaConfig] }, + { + languageOptions: { parserOptions: { project: null } }, + rules: { + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + 'prefer-destructuring': 'off', + 'simple-import-sort/imports': 'off', + 'sort-keys-fix/sort-keys-fix': 'off', + 'typescript-sort-keys/interface': 'off', + }, + }, +]); \ No newline at end of file diff --git a/clients/js/package.json b/clients/js/package.json index bfd2a8c..c808023 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -50,8 +50,8 @@ }, "dependencies": { "@iarna/toml": "^2.2.5", - "@solana-program/compute-budget": "^0.13.0", - "@solana-program/system": "^0.11.0", + "@solana-program/compute-budget": "^0.15.0", + "@solana-program/system": "^0.12.0", "commander": "^13.0.0", "pako": "^2.1.0", "picocolors": "^1.1.1", @@ -59,14 +59,16 @@ }, "devDependencies": { "@ava/typescript": "^4.1.0", - "@solana/eslint-config-solana": "^3.0.3", - "@solana/kit": "^6.4.0", + "@solana-config/eslint": "^0.2.3", + "@solana/kit": "^6.9.0", + "@solana/kit-plugin-rpc": "^0.11.1", + "@solana/kit-plugin-signer": "^0.10.0", "@types/node": "^24", "@types/pako": "^2.0.3", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", "ava": "^6.1.3", - "eslint": "^8.57.0", + "eslint": "^9.39.2", "prettier": "^3.8.1", "rimraf": "^5.0.5", "tsup": "^8.1.2", diff --git a/clients/js/pnpm-lock.yaml b/clients/js/pnpm-lock.yaml index 164f08d..0c184b5 100644 --- a/clients/js/pnpm-lock.yaml +++ b/clients/js/pnpm-lock.yaml @@ -12,11 +12,11 @@ importers: specifier: ^2.2.5 version: 2.2.5 '@solana-program/compute-budget': - specifier: ^0.13.0 - version: 0.13.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) + specifier: ^0.15.0 + version: 0.15.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) '@solana-program/system': - specifier: ^0.11.0 - version: 0.11.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) + specifier: ^0.12.0 + version: 0.12.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) commander: specifier: ^13.0.0 version: 13.0.0 @@ -33,12 +33,18 @@ importers: '@ava/typescript': specifier: ^4.1.0 version: 4.1.0 - '@solana/eslint-config-solana': - specifier: ^3.0.3 - version: 3.0.6(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@solana-config/eslint': + specifier: ^0.2.3 + version: 0.2.3(@eslint/js@9.39.4)(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint-plugin-react-hooks@4.6.2(eslint@9.39.4))(eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.4))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(globals@14.0.0)(typescript-eslint@8.58.2(eslint@9.39.4)(typescript@5.9.3))(typescript@5.9.3) '@solana/kit': - specifier: ^6.4.0 + specifier: ^6.9.0 version: 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/kit-plugin-rpc': + specifier: ^0.11.1 + version: 0.11.1(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) + '@solana/kit-plugin-signer': + specifier: ^0.10.0 + version: 0.10.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) '@types/node': specifier: ^24 version: 24.3.0 @@ -47,16 +53,16 @@ importers: version: 2.0.3 '@typescript-eslint/eslint-plugin': specifier: ^7.16.1 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^7.16.1 - version: 7.18.0(eslint@8.57.1)(typescript@5.9.3) + version: 7.18.0(eslint@9.39.4)(typescript@5.9.3) ava: specifier: ^6.1.3 version: 6.2.0(@ava/typescript@4.1.0)(rollup@4.30.1) eslint: - specifier: ^8.57.0 - version: 8.57.1 + specifier: ^9.39.2 + version: 9.39.4 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -235,30 +241,67 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} @@ -418,15 +461,29 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@solana-program/compute-budget@0.13.0': - resolution: {integrity: sha512-jdiiWaxFG3kEf6bYPNo2mwz2jNxaj7sF+gZIb8wHw9zK3ZILmpkg4sUeChb1BnH2UGf+HgYb9L/lMdqOTqUoWA==} + '@solana-config/eslint@0.2.3': + resolution: {integrity: sha512-IXvDzqWgCF/5hGWXcNbRI9/eIGQg/o552BCtdph4JLI4LjUsQGh75JgQmIxxNv5dPaMxpwcf0BhrAenuya4Azw==} peerDependencies: - '@solana/kit': ^6.0.0 + '@eslint/js': ^9.39.1 + eslint: ^9.39.1 + eslint-plugin-jest: ^29.0.0 + eslint-plugin-react-hooks: ^7.0.0 + eslint-plugin-simple-import-sort: '>=12.0.0' + eslint-plugin-sort-keys-fix: '>=1.0.0' + eslint-plugin-typescript-sort-keys: '>=3.0.0' + globals: '>=14.0.0' + typescript: '>=5.6.0' + typescript-eslint: ^8.0.0 <8.59.0 + + '@solana-program/compute-budget@0.15.0': + resolution: {integrity: sha512-toejNdIkzpUTqLSIzP0Nofr/EFel8QpPWuTtIKzfCcjn+mXpkThHxPJaNesk251rSTiWaxDZ3WxG7RxYwTWTqA==} + peerDependencies: + '@solana/kit': ^6.3.0 - '@solana-program/system@0.11.0': - resolution: {integrity: sha512-SJeQVTkqGZzIXd7XHlCxnfpKpvPZghB1IFwddPPG04ydVXtDLRWp9wLoTR5Prkl9FIWRe/c5VgT4nxyzW1cAuQ==} + '@solana-program/system@0.12.0': + resolution: {integrity: sha512-ZnAAWeGVMWNtJhw3GdifI2HnhZ0A0H0qs8tBkcFvxp/8wIavvO+GOM4Jd0N22u2+Lni2zcwvcrxrsxj6Mjphng==} peerDependencies: - '@solana/kit': ^6.0.0 + '@solana/kit': ^6.1.0 '@solana/accounts@6.9.0': resolution: {integrity: sha512-g36AJreJrgf9AAjOfbdFHEFUTymBgzbWHoEDElZ+fDKvqBINDiUVKzDApwc7C7kGPMFqQBaoEHnQRxf2IqfKZQ==} @@ -513,19 +570,6 @@ packages: typescript: optional: true - '@solana/eslint-config-solana@3.0.6': - resolution: {integrity: sha512-3u024DkukJCfzUfOgN1EmWzVZLaZtgRLJ52FEdQmIG8NYOzLpaIJFgQvjYXWQlnK6ycIcSn/MesHG6sbKkMtTQ==} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^7.0.0 - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.45.0 - eslint-plugin-jest: ^27.2.3 - eslint-plugin-react-hooks: ^4.6.0 - eslint-plugin-simple-import-sort: ^12.0.0 - eslint-plugin-sort-keys-fix: ^1.1.2 - eslint-plugin-typescript-sort-keys: ^3.2.0 - typescript: ^5.1.6 - '@solana/fast-stable-stringify@6.9.0': resolution: {integrity: sha512-l14zGVsURbT5Aox/kLFQywqV4VaE9/j3h2EvCu9oULVPMwzQB6yezJb1/KyiDwhm/RscooPd0gFQFIKEGQbayw==} engines: {node: '>=20.18.0'} @@ -580,6 +624,21 @@ packages: typescript: optional: true + '@solana/kit-plugin-instruction-plan@0.10.0': + resolution: {integrity: sha512-h99B+1Vp5QWU/M/q0UXlTxKJt781vWw5aAopnbgVCy0F3hNaSDZ/gio126Oz0/cipGOnyaJf3NnV79GvxaEqZA==} + peerDependencies: + '@solana/kit': ^6.8.0 + + '@solana/kit-plugin-rpc@0.11.1': + resolution: {integrity: sha512-NpCKBY6y3lmOOZjzm5dvk/+iZsKOH+Wc9TQx5yKZ0RzNOXC6V9i0Inn+niVdkX5ECDJOmClfa64bLcImFJWLuQ==} + peerDependencies: + '@solana/kit': ^6.8.0 + + '@solana/kit-plugin-signer@0.10.0': + resolution: {integrity: sha512-3Gw3R6nQg/2r2+4cSaUZHj5zdbtb0SuA1mkDMd6uH7B+fdH1Ouxnulv1/sN8J0VxBo7tS+YDQA5t0arxX5gj+w==} + peerDependencies: + '@solana/kit': ^6.8.0 + '@solana/kit@6.9.0': resolution: {integrity: sha512-k7BRz7Akfv8wiRtlCR/xUyDLfuMfYMelMR1+AC5KgwaRRJReDF0BucMLNN1In7WoI+KuWwr1OKv4na/oKpyeAQ==} engines: {node: '>=20.18.0'} @@ -849,6 +908,14 @@ packages: typescript: optional: true + '@typescript-eslint/eslint-plugin@8.58.2': + resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.58.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/experimental-utils@5.62.0': resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -865,6 +932,19 @@ packages: typescript: optional: true + '@typescript-eslint/parser@8.58.2': + resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.58.2': + resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -873,6 +953,16 @@ packages: resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.58.2': + resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.2': + resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@7.18.0': resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -883,6 +973,13 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@8.58.2': + resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/types@5.62.0': resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -891,6 +988,10 @@ packages: resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -909,6 +1010,12 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.58.2': + resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@5.62.0': resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -921,6 +1028,13 @@ packages: peerDependencies: eslint: ^8.56.0 + '@typescript-eslint/utils@8.58.2': + resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -929,8 +1043,9 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@ungap/structured-clone@1.2.1': - resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@typescript-eslint/visitor-keys@8.58.2': + resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vercel/nft@0.27.10': resolution: {integrity: sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg==} @@ -965,12 +1080,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1032,6 +1152,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -1044,6 +1168,10 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1170,6 +1298,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1181,10 +1318,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1262,9 +1395,9 @@ packages: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@1.3.0: resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} @@ -1274,20 +1407,32 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@6.2.1: resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} engines: {node: '>=6.0.0'} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -1350,13 +1495,22 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1373,9 +1527,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} @@ -1420,9 +1574,9 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -1458,6 +1612,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1501,10 +1659,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} @@ -1538,8 +1692,8 @@ packages: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true json-buffer@3.0.1: @@ -1634,9 +1788,16 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -1776,6 +1937,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -1850,11 +2015,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true @@ -1872,6 +2032,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} @@ -1966,9 +2131,6 @@ packages: resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} engines: {node: '>=14.16'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1987,6 +2149,10 @@ packages: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2007,6 +2173,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2046,10 +2218,6 @@ packages: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - typedoc@0.25.13: resolution: {integrity: sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==} engines: {node: '>= 16'} @@ -2057,6 +2225,13 @@ packages: peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x + typescript-eslint@8.58.2: + resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2240,40 +2415,74 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.39.4)': dependencies: - eslint: 8.57.1 + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': dependencies: - ajv: 6.12.6 + '@eslint/object-schema': 2.1.7 debug: 4.4.0 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 - '@humanwhocodes/config-array@0.13.0': + '@eslint/eslintrc@3.3.5': dependencies: - '@humanwhocodes/object-schema': 2.0.3 + ajv: 6.15.0 debug: 4.4.0 - minimatch: 3.1.2 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.4.3': {} '@iarna/toml@2.2.5': {} @@ -2402,11 +2611,24 @@ snapshots: '@sindresorhus/merge-streams@2.3.0': {} - '@solana-program/compute-budget@0.13.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': + '@solana-config/eslint@0.2.3(@eslint/js@9.39.4)(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint-plugin-react-hooks@4.6.2(eslint@9.39.4))(eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.4))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(globals@14.0.0)(typescript-eslint@8.58.2(eslint@9.39.4)(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@eslint/js': 9.39.4 + eslint: 9.39.4 + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + eslint-plugin-react-hooks: 4.6.2(eslint@9.39.4) + eslint-plugin-simple-import-sort: 12.1.1(eslint@9.39.4) + eslint-plugin-sort-keys-fix: 1.1.2 + eslint-plugin-typescript-sort-keys: 3.3.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + globals: 14.0.0 + typescript: 5.9.3 + typescript-eslint: 8.58.2(eslint@9.39.4)(typescript@5.9.3) + + '@solana-program/compute-budget@0.15.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': dependencies: '@solana/kit': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) - '@solana-program/system@0.11.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': + '@solana-program/system@0.12.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': dependencies: '@solana/kit': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -2491,18 +2713,6 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@solana/eslint-config-solana@3.0.6(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-simple-import-sort: 12.1.1(eslint@8.57.1) - eslint-plugin-sort-keys-fix: 1.1.2 - eslint-plugin-typescript-sort-keys: 3.3.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - typescript: 5.9.3 - '@solana/fast-stable-stringify@6.9.0(typescript@5.9.3)': optionalDependencies: typescript: 5.9.3 @@ -2551,6 +2761,19 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/kit-plugin-instruction-plan@0.10.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': + dependencies: + '@solana/kit': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + + '@solana/kit-plugin-rpc@0.11.1(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': + dependencies: + '@solana/kit': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/kit-plugin-instruction-plan': 0.10.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)) + + '@solana/kit-plugin-signer@0.10.0(@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3))': + dependencies: + '@solana/kit': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + '@solana/kit@6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': dependencies: '@solana/accounts': 6.9.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -2904,15 +3127,15 @@ snapshots: '@types/semver@7.5.8': {} - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 7.18.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1 + eslint: 9.39.4 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -2922,27 +3145,64 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/type-utils': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.2 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/experimental-utils@5.62.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 5.62.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.4.0 - eslint: 8.57.1 + eslint: 9.39.4 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) + '@typescript-eslint/types': 8.58.2 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -2953,30 +3213,53 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/scope-manager@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@7.18.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.4)(typescript@5.9.3) debug: 4.4.0 - eslint: 8.57.1 + eslint: 9.39.4 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.58.2(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@5.62.0': {} '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.58.2': {} + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0 + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.8.0 tsutils: 3.21.0(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -2998,32 +3281,58 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.58.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.0 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) - eslint: 8.57.1 + eslint: 9.39.4 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.8.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@7.18.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.39.4) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) - eslint: 8.57.1 + eslint: 9.39.4 transitivePeerDependencies: - supports-color - typescript + '@typescript-eslint/utils@8.58.2(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -3034,7 +3343,10 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.1': {} + '@typescript-eslint/visitor-keys@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + eslint-visitor-keys: 5.0.1 '@vercel/nft@0.27.10(rollup@4.30.1)': dependencies: @@ -3065,9 +3377,9 @@ snapshots: dependencies: acorn: 7.4.1 - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.14.0 + acorn: 8.16.0 acorn-walk@8.3.4: dependencies: @@ -3077,9 +3389,11 @@ snapshots: acorn@8.14.0: {} + acorn@8.16.0: {} + agent-base@7.1.3: {} - ajv@6.12.6: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -3167,6 +3481,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -3182,6 +3498,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3286,6 +3606,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + deep-is@0.1.4: {} detect-libc@2.0.3: {} @@ -3294,10 +3618,6 @@ snapshots: dependencies: path-type: 4.0.0 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - eastasianwidth@0.2.0: {} emittery@1.0.3: {} @@ -3344,23 +3664,23 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 5.62.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + eslint-plugin-react-hooks@4.6.2(eslint@9.39.4): dependencies: - eslint: 8.57.1 + eslint: 9.39.4 - eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1): + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.4): dependencies: - eslint: 8.57.1 + eslint: 9.39.4 eslint-plugin-sort-keys-fix@1.1.2: dependencies: @@ -3369,11 +3689,11 @@ snapshots: natural-compare: 1.4.0 requireindex: 1.2.0 - eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3): + eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@7.18.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3): dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/experimental-utils': 5.62.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 7.18.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 json-schema: 0.4.0 natural-compare-lite: 1.4.0 typescript: 5.9.3 @@ -3385,7 +3705,7 @@ snapshots: esrecurse: 4.3.0 estraverse: 4.3.0 - eslint-scope@7.2.2: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -3394,61 +3714,61 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint@8.57.1: + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@eslint-community/regexpp': 4.12.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.1 - ajv: 6.12.6 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.6 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.0 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 transitivePeerDependencies: - supports-color + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + espree@6.2.1: dependencies: acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) eslint-visitor-keys: 1.3.0 - espree@9.6.1: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 - esprima@4.0.1: {} esquery@1.6.0: @@ -3506,13 +3826,17 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 file-uri-to-path@1.0.0: {} @@ -3527,11 +3851,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.2 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.3.2: {} @@ -3577,9 +3900,7 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} globby@11.1.0: dependencies: @@ -3618,6 +3939,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -3648,8 +3971,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-plain-object@5.0.0: {} is-promise@4.0.0: {} @@ -3675,7 +3996,7 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -3747,10 +4068,18 @@ snapshots: mimic-function@5.0.1: {} + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.11 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -3859,6 +4188,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.4: {} + pirates@4.0.6: {} plur@5.1.0: @@ -3899,10 +4230,6 @@ snapshots: reusify@1.0.4: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rimraf@5.0.10: dependencies: glob: 10.4.5 @@ -3938,6 +4265,8 @@ snapshots: semver@7.6.3: {} + semver@7.8.0: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 @@ -4040,8 +4369,6 @@ snapshots: temp-dir@3.0.0: {} - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -4059,6 +4386,11 @@ snapshots: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -4075,6 +4407,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} tslib@1.14.1: {} @@ -4116,8 +4452,6 @@ snapshots: type-fest@0.13.1: {} - type-fest@0.20.2: {} - typedoc@0.25.13(typescript@5.9.3): dependencies: lunr: 2.3.9 @@ -4126,6 +4460,17 @@ snapshots: shiki: 0.14.7 typescript: 5.9.3 + typescript-eslint@8.58.2(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} undici-types@7.10.0: {} diff --git a/clients/js/src/cli/commands/create-buffer.ts b/clients/js/src/cli/commands/create-buffer.ts index 9b9b029..5c15f2d 100644 --- a/clients/js/src/cli/commands/create-buffer.ts +++ b/clients/js/src/cli/commands/create-buffer.ts @@ -1,6 +1,5 @@ import { generateKeyPairSigner } from '@solana/kit'; import { getCreateBufferInstructionPlan } from '../../createBuffer'; -import { getAccountSize } from '../../utils'; import { fileArgument } from '../arguments'; import { GlobalOptions, setWriteOptions, WriteOptions } from '../options'; import { CustomCommand, getClient, getWriteInput } from '../utils'; @@ -26,18 +25,14 @@ export async function doCreateBuffer(file: string | undefined, _: Options, cmd: authority: client.authority.address, }); - const data = writeInput.buffer?.data.data ?? writeInput.data; - const rent = await client.rpc.getMinimumBalanceForRentExemption(getAccountSize(data.length)).send(); - await client.planAndExecute( - getCreateBufferInstructionPlan({ + await getCreateBufferInstructionPlan(client, { newBuffer: buffer, authority: client.authority, payer: client.payer, - rent, sourceBuffer: writeInput.buffer, closeSourceBuffer: writeInput.closeBuffer, - data, + data: writeInput.buffer?.data.data ?? writeInput.data, }), ); } diff --git a/clients/js/src/cli/commands/create.ts b/clients/js/src/cli/commands/create.ts index 7360e52..a6c3858 100644 --- a/clients/js/src/cli/commands/create.ts +++ b/clients/js/src/cli/commands/create.ts @@ -48,8 +48,7 @@ export async function doCreate(seed: Seed, program: Address, file: string | unde } await client.planAndExecute( - await getCreateMetadataInstructionPlan({ - ...client, + await getCreateMetadataInstructionPlan(client, { ...writeInput, payer: client.payer, authority: client.authority, @@ -57,7 +56,6 @@ export async function doCreate(seed: Seed, program: Address, file: string | unde programData, seed, metadata, - planner: client.planner, }), ); } diff --git a/clients/js/src/cli/commands/update-buffer.ts b/clients/js/src/cli/commands/update-buffer.ts index baa1e99..4848d32 100644 --- a/clients/js/src/cli/commands/update-buffer.ts +++ b/clients/js/src/cli/commands/update-buffer.ts @@ -1,4 +1,4 @@ -import { Address, lamports } from '@solana/kit'; +import { Address } from '@solana/kit'; import { fetchMaybeBuffer } from '../../generated'; import { getUpdateBufferInstructionPlan } from '../../updateBuffer'; import { bufferArgument, fileArgument } from '../arguments'; @@ -34,17 +34,12 @@ export async function doUpdateBuffer(buffer: Address, file: string | undefined, const currentData = bufferAccount.data.data; const newData = writeInput.buffer?.data.data ?? writeInput.data; const sizeDifference = newData.length - currentData.length; - const extraRent = - sizeDifference > 0 - ? await client.rpc.getMinimumBalanceForRentExemption(BigInt(sizeDifference)).send() - : lamports(0n); await client.planAndExecute( - getUpdateBufferInstructionPlan({ + await getUpdateBufferInstructionPlan(client, { buffer, authority: client.authority, payer: client.payer, - extraRent, sizeDifference, sourceBuffer: writeInput.buffer, closeSourceBuffer: writeInput.closeBuffer, diff --git a/clients/js/src/cli/commands/update.ts b/clients/js/src/cli/commands/update.ts index 2add099..0d162c6 100644 --- a/clients/js/src/cli/commands/update.ts +++ b/clients/js/src/cli/commands/update.ts @@ -48,15 +48,13 @@ export async function doWrite(seed: Seed, program: Address, file: string | undef } await client.planAndExecute( - await getUpdateMetadataInstructionPlan({ - ...client, + await getUpdateMetadataInstructionPlan(client, { ...writeInput, payer: client.payer, authority: client.authority, program, programData, metadata: metadataAccount, - planner: client.planner, }), ); } diff --git a/clients/js/src/cli/commands/write.ts b/clients/js/src/cli/commands/write.ts index 6b23323..bf77792 100644 --- a/clients/js/src/cli/commands/write.ts +++ b/clients/js/src/cli/commands/write.ts @@ -43,8 +43,7 @@ export async function doWrite(seed: Seed, program: Address, file: string | undef ]); await client.planAndExecute( - await getWriteMetadataInstructionPlan({ - ...client, + await getWriteMetadataInstructionPlan(client, { ...writeInput, payer: client.payer, authority: client.authority, @@ -52,7 +51,6 @@ export async function doWrite(seed: Seed, program: Address, file: string | undef programData, seed, metadata: metadataAccount, - planner: client.planner, }), ); } diff --git a/clients/js/src/cli/logs.ts b/clients/js/src/cli/logs.ts index 7b27df6..b13dc81 100644 --- a/clients/js/src/cli/logs.ts +++ b/clients/js/src/cli/logs.ts @@ -74,7 +74,7 @@ export function logTable(tabularData: unknown) { }); const logger = new Console({ stdout: ts }); logger.table(tabularData); - const table: string = (ts.read() || '').toString(); + const table: string = ((ts.read() as { toString: () => string } | null) || '').toString(); let result = ''; for (const row of table.split(/[\r\n]+/)) { let r = row.replace(/[^┬]*┬/, '┌'); diff --git a/clients/js/src/cli/utils.ts b/clients/js/src/cli/utils.ts index f016f5f..bf4be59 100644 --- a/clients/js/src/cli/utils.ts +++ b/clients/js/src/cli/utils.ts @@ -7,6 +7,9 @@ import { AccountRole, Address, address, + BASE_ACCOUNT_SIZE, + ClientWithGetMinimumBalance, + ClientWithTransactionPlanning, Commitment, compileTransaction, createKeyPairSignerFromBytes, @@ -14,8 +17,11 @@ import { createSolanaRpc, createSolanaRpcSubscriptions, flattenTransactionPlan, + GetMinimumBalanceConfig, getTransactionEncoder, InstructionPlan, + Lamports, + lamports, MessageSigner, pipe, Rpc, @@ -67,13 +73,15 @@ export class CustomCommand extends Command { } } -export type Client = ReadonlyClient & { - authority: TransactionSigner & MessageSigner; - executor: TransactionPlanExecutor; - payer: TransactionSigner & MessageSigner; - planAndExecute: (instructionPlan: InstructionPlan) => Promise; - planner: TransactionPlanner; -}; +export type Client = ReadonlyClient & + ClientWithGetMinimumBalance & + ClientWithTransactionPlanning & { + authority: TransactionSigner & MessageSigner; + executor: TransactionPlanExecutor; + payer: TransactionSigner & MessageSigner; + planAndExecute: (instructionPlan: InstructionPlan) => Promise; + planner: TransactionPlanner; + }; export async function getClient(options: GlobalOptions): Promise { const readonlyClient = getReadonlyClient(options); @@ -93,13 +101,33 @@ export async function getClient(options: GlobalOptions): Promise { await executeTransactionPlan(transactionPlan, executor); } }; + const getMinimumBalance = async (space: number, config?: GetMinimumBalanceConfig): Promise => { + if (config?.withoutHeader) { + const headerBalance = await readonlyClient.rpc.getMinimumBalanceForRentExemption(0n).send(); + const lamportsPerByte = BigInt(headerBalance) / BigInt(BASE_ACCOUNT_SIZE); + return lamports(lamportsPerByte * BigInt(space)); + } + return await readonlyClient.rpc.getMinimumBalanceForRentExemption(BigInt(space)).send(); + }; + const planTransaction: ClientWithTransactionPlanning['planTransaction'] = async (input, config) => { + const transactionPlan = await planner(input as InstructionPlan, config); + if (transactionPlan.kind !== 'single') { + throw new Error('Expected a single transaction plan'); + } + return transactionPlan.message; + }; + const planTransactions: ClientWithTransactionPlanning['planTransactions'] = async (input, config) => + await planner(input as InstructionPlan, config); return { ...readonlyClient, authority, executor, + getMinimumBalance, payer, planAndExecute, planner, + planTransaction, + planTransactions, }; } @@ -222,7 +250,7 @@ function getSolanaConfigs(): SolanaConfigs { logWarning('Solana config file not found'); return {}; } - return parseYaml(fs.readFileSync(getSolanaConfigPath(), 'utf8')); + return parseYaml(fs.readFileSync(getSolanaConfigPath(), 'utf8')) as SolanaConfigs; } function getSolanaConfigPath(): string { diff --git a/clients/js/src/createBuffer.ts b/clients/js/src/createBuffer.ts index 398c983..71fd769 100644 --- a/clients/js/src/createBuffer.ts +++ b/clients/js/src/createBuffer.ts @@ -1,8 +1,8 @@ -import { getCreateAccountInstruction } from '@solana-program/system'; +import { getCreateAccountInstruction, getTransferSolInstruction } from '@solana-program/system'; import { Account, Address, - Lamports, + ClientWithGetMinimumBalance, parallelInstructionPlan, ReadonlyUint8Array, sequentialInstructionPlan, @@ -10,34 +10,50 @@ import { } from '@solana/kit'; import { Buffer, + findCanonicalPda, + findNonCanonicalPda, getAllocateInstruction, getCloseInstruction, getSetAuthorityInstruction, getWriteInstruction, PROGRAM_METADATA_PROGRAM_ADDRESS, + SeedArgs, } from './generated'; -import { getAccountSize, getWriteInstructionPlan } from './utils'; +import { REALLOC_LIMIT } from './internals'; +import { getAccountSize, getExtendInstructionPlan, getWriteInstructionPlan } from './utils'; -export function getCreateBufferInstructionPlan(input: { - newBuffer: TransactionSigner; - authority: TransactionSigner; - payer: TransactionSigner; - sourceBuffer?: Account; - closeSourceBuffer?: Address | boolean; - data?: ReadonlyUint8Array; - rent: Lamports; -}) { +/** + * Builds a plan that creates a brand new buffer account owned by a fresh + * keypair. The account is created via the system program's `CreateAccount` + * instruction; the buffer authority is the `input.authority` signer. + * + * Use {@link getCreateCanonicalBufferInstructionPlan} or + * {@link getCreateNonCanonicalBufferInstructionPlan} to create PDA-backed + * buffer accounts instead. + */ +export async function getCreateBufferInstructionPlan( + client: ClientWithGetMinimumBalance, + input: { + newBuffer: TransactionSigner; + authority: TransactionSigner; + payer: TransactionSigner; + sourceBuffer?: Account; + closeSourceBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + }, +) { if (!input.data && !input.sourceBuffer) { throw new Error('Either `data` or `sourceBuffer` must be provided to create a buffer.'); } const data = (input.sourceBuffer?.data.data ?? input.data) as ReadonlyUint8Array; + const rent = await client.getMinimumBalance(Number(getAccountSize(data.length))); return sequentialInstructionPlan([ getCreateAccountInstruction({ payer: input.payer, newAccount: input.newBuffer, - lamports: input.rent, + lamports: rent, space: getAccountSize(data.length), programAddress: PROGRAM_METADATA_PROGRAM_ADDRESS, }), @@ -80,3 +96,135 @@ export function getCreateBufferInstructionPlan(input: { : []), ]); } + +/** + * Builds a plan that creates a canonical buffer account at a PDA derived from + * `program` and `seed`. The buffer is authority-managed by the program upgrade + * authority — `input.authority` must therefore match the upgrade authority and + * `input.programData` must be provided. + * + * The plan funds the account via a SOL transfer, allocates it via the + * program-metadata `allocate` instruction, extends it when necessary, and + * writes `data` into it (when provided). + */ +export async function getCreateCanonicalBufferInstructionPlan( + client: ClientWithGetMinimumBalance, + input: { + authority: TransactionSigner; + buffer?: Address; + closeBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + dataLength?: number; + payer: TransactionSigner; + program: Address; + programData: Address; + seed: SeedArgs; + }, +) { + const buffer = input.buffer ?? (await findCanonicalPda({ program: input.program, seed: input.seed }))[0]; + return await getPdaBufferInstructionPlan(client, { + ...input, + buffer, + program: input.program, + programData: input.programData, + }); +} + +/** + * Builds a plan that creates a non-canonical buffer account at a PDA derived + * from `program`, `seed` and `authority`. The buffer is managed by + * `input.authority` directly. + * + * The plan funds the account via a SOL transfer, allocates it via the + * program-metadata `allocate` instruction, extends it when necessary, and + * writes `data` into it (when provided). + */ +export async function getCreateNonCanonicalBufferInstructionPlan( + client: ClientWithGetMinimumBalance, + input: { + authority: TransactionSigner; + buffer?: Address; + closeBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + dataLength?: number; + payer: TransactionSigner; + program: Address; + seed: SeedArgs; + }, +) { + const buffer = + input.buffer ?? + ( + await findNonCanonicalPda({ + authority: input.authority.address, + program: input.program, + seed: input.seed, + }) + )[0]; + return await getPdaBufferInstructionPlan(client, { ...input, buffer }); +} + +async function getPdaBufferInstructionPlan( + client: ClientWithGetMinimumBalance, + input: { + authority: TransactionSigner; + buffer: Address; + closeBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + dataLength?: number; + payer: TransactionSigner; + program: Address; + programData?: Address; + seed: SeedArgs; + }, +) { + const dataLength = input.dataLength ?? input.data?.length ?? 0; + const rent = await client.getMinimumBalance(Number(getAccountSize(dataLength))); + return sequentialInstructionPlan([ + getTransferSolInstruction({ + source: input.payer, + destination: input.buffer, + amount: rent, + }), + getAllocateInstruction({ + buffer: input.buffer, + authority: input.authority, + program: input.program, + programData: input.programData, + seed: input.seed, + }), + ...(dataLength > REALLOC_LIMIT + ? [ + getExtendInstructionPlan({ + account: input.buffer, + authority: input.authority, + extraLength: dataLength, + program: input.program, + programData: input.programData, + }), + ] + : []), + ...(input.data + ? [ + parallelInstructionPlan([ + getWriteInstructionPlan({ + buffer: input.buffer, + authority: input.authority, + data: input.data, + }), + ]), + ] + : []), + ...(input.closeBuffer + ? [ + getCloseInstruction({ + account: input.buffer, + authority: input.authority, + program: input.program, + programData: input.programData, + destination: typeof input.closeBuffer === 'string' ? input.closeBuffer : input.payer.address, + }), + ] + : []), + ]); +} diff --git a/clients/js/src/createMetadata.ts b/clients/js/src/createMetadata.ts index d1dc0d5..02ab7d0 100644 --- a/clients/js/src/createMetadata.ts +++ b/clients/js/src/createMetadata.ts @@ -2,15 +2,16 @@ import { getTransferSolInstruction } from '@solana-program/system'; import { Account, Address, + ClientWithGetMinimumBalance, + ClientWithRpc, + ClientWithTransactionPlanning, + ClientWithTransactionSending, GetAccountInfoApi, - GetMinimumBalanceForRentExemptionApi, InstructionPlan, - Lamports, parallelInstructionPlan, ReadonlyUint8Array, - Rpc, sequentialInstructionPlan, - TransactionPlanner, + TransactionPlanResult, TransactionSigner, } from '@solana/kit'; import { @@ -23,52 +24,46 @@ import { InitializeInput, PROGRAM_METADATA_PROGRAM_ADDRESS, } from './generated'; -import { - createDefaultTransactionPlannerAndExecutor, - getPdaDetails, - isValidInstructionPlan, - REALLOC_LIMIT, -} from './internals'; +import { isValidInstructionPlan, REALLOC_LIMIT } from './internals'; import { getAccountSize, getExtendInstructionPlan, getWriteInstructionPlan, MetadataInput, - MetadataResponse, + resolveMetadataPda, } from './utils'; +type CreateMetadataClient = ClientWithGetMinimumBalance & + ClientWithRpc & + ClientWithTransactionPlanning & + ClientWithTransactionSending; + +/** + * Creates a new metadata account for the given program. + * + * When `input.metadata` is omitted, the PDA is derived from `program`, `seed` + * and — for non-canonical metadata accounts — `authority`. When `input.buffer` + * is provided as an address, the buffer account is fetched to determine its + * data length. + */ export async function createMetadata( - input: MetadataInput & { - rpc: Rpc & - Parameters[0]['rpc']; - rpcSubscriptions: Parameters[0]['rpcSubscriptions']; - }, -): Promise { - const { planner, executor } = createDefaultTransactionPlannerAndExecutor(input); - const [{ programData, isCanonical, metadata }, buffer] = await Promise.all([ - getPdaDetails(input), - input.buffer ? fetchBuffer(input.rpc, input.buffer) : Promise.resolve(undefined), + client: CreateMetadataClient, + input: MetadataInput, +): Promise { + const [metadata, buffer] = await Promise.all([ + resolveMetadataPda(input), + input.buffer ? fetchBuffer(client.rpc, input.buffer) : Promise.resolve(undefined), ]); - const instructionPlan = await getCreateMetadataInstructionPlan({ - ...input, - buffer, - metadata, - planner, - programData: isCanonical ? programData : undefined, - }); - - const transactionPlan = await planner(instructionPlan); - const result = await executor(transactionPlan); - return { metadata, result }; + const plan = await getCreateMetadataInstructionPlan(client, { ...input, buffer, metadata }); + return await client.sendTransactions(plan); } export async function getCreateMetadataInstructionPlan( + client: ClientWithGetMinimumBalance & ClientWithTransactionPlanning, input: Omit & { buffer?: Account; data?: ReadonlyUint8Array; payer: TransactionSigner; - planner: TransactionPlanner; - rpc: Rpc; closeBuffer?: Address | boolean; }, ): Promise { @@ -76,49 +71,57 @@ export async function getCreateMetadataInstructionPlan( throw new Error('Either `buffer` or `data` must be provided to create a new metadata account.'); } - const data = (input.buffer?.data.data ?? input.data) as ReadonlyUint8Array; - const rent = await input.rpc.getMinimumBalanceForRentExemption(getAccountSize(data.length)).send(); - if (input.buffer) { - return getCreateMetadataInstructionPlanUsingExistingBuffer({ + return await getCreateMetadataInstructionPlanUsingExistingBuffer(client, { ...input, buffer: input.buffer.address, - dataLength: data.length, - rent, + dataLength: input.buffer.data.data.length, }); } - const extendedInput = { ...input, rent, data }; - const plan = getCreateMetadataInstructionPlanUsingInstructionData(extendedInput); - const validPlan = await isValidInstructionPlan(plan, input.planner); - return validPlan ? plan : getCreateMetadataInstructionPlanUsingNewBuffer(extendedInput); + const data = input.data as ReadonlyUint8Array; + const usingInstructionDataPlan = await getCreateMetadataInstructionPlanUsingInstructionData(client, { + ...input, + data, + }); + const validPlan = await isValidInstructionPlan(usingInstructionDataPlan, client); + return validPlan + ? usingInstructionDataPlan + : await getCreateMetadataInstructionPlanUsingNewBuffer(client, { ...input, data }); } -export function getCreateMetadataInstructionPlanUsingInstructionData( - input: InitializeInput & { payer: TransactionSigner; rent: Lamports }, +export async function getCreateMetadataInstructionPlanUsingInstructionData( + client: ClientWithGetMinimumBalance, + input: Omit & { + data?: ReadonlyUint8Array; + payer: TransactionSigner; + }, ) { + const dataLength = input.data?.length ?? 0; + const rent = await client.getMinimumBalance(Number(getAccountSize(dataLength))); return sequentialInstructionPlan([ getTransferSolInstruction({ source: input.payer, destination: input.metadata, - amount: input.rent, + amount: rent, }), getInitializeInstruction(input), ]); } -export function getCreateMetadataInstructionPlanUsingNewBuffer( +export async function getCreateMetadataInstructionPlanUsingNewBuffer( + client: ClientWithGetMinimumBalance, input: Omit & { data: ReadonlyUint8Array; payer: TransactionSigner; - rent: Lamports; }, ) { + const rent = await client.getMinimumBalance(Number(getAccountSize(input.data.length))); return sequentialInstructionPlan([ getTransferSolInstruction({ source: input.payer, destination: input.metadata, - amount: input.rent, + amount: rent, }), getAllocateInstruction({ buffer: input.metadata, @@ -153,20 +156,21 @@ export function getCreateMetadataInstructionPlanUsingNewBuffer( ]); } -export function getCreateMetadataInstructionPlanUsingExistingBuffer( +export async function getCreateMetadataInstructionPlanUsingExistingBuffer( + client: ClientWithGetMinimumBalance, input: Omit & { buffer: Address; dataLength: number; payer: TransactionSigner; - rent: Lamports; closeBuffer?: Address | boolean; }, ) { + const rent = await client.getMinimumBalance(Number(getAccountSize(input.dataLength))); return sequentialInstructionPlan([ getTransferSolInstruction({ source: input.payer, destination: input.metadata, - amount: input.rent, + amount: rent, }), getAllocateInstruction({ buffer: input.metadata, diff --git a/clients/js/src/fetchMetadataContent.ts b/clients/js/src/fetchMetadataContent.ts index aaa186d..134fc33 100644 --- a/clients/js/src/fetchMetadataContent.ts +++ b/clients/js/src/fetchMetadataContent.ts @@ -40,7 +40,7 @@ export async function fetchAllMetadataContent( return await unpackAndFetchAllData({ rpc, accounts }); } -function parseContent(format: Format, content: string) { +function parseContent(format: Format, content: string): unknown { switch (format) { case Format.Json: return JSON.parse(content); diff --git a/clients/js/src/index.ts b/clients/js/src/index.ts index 75ef88f..115af8b 100644 --- a/clients/js/src/index.ts +++ b/clients/js/src/index.ts @@ -1,8 +1,18 @@ export * from './generated'; +// Generated overrides (must be re-exported explicitly). +export { + programMetadataProgram, + type ProgramMetadataPlugin, + type ProgramMetadataPluginInstructions, + type ProgramMetadataPluginRequirements, +} from './plugin'; + +export * from './createBuffer'; export * from './createMetadata'; export * from './fetchMetadataContent'; export * from './packData'; +export * from './updateBuffer'; export * from './updateMetadata'; export * from './utils'; export * from './writeMetadata'; diff --git a/clients/js/src/internals.ts b/clients/js/src/internals.ts index 0eea0d9..20acb23 100644 --- a/clients/js/src/internals.ts +++ b/clients/js/src/internals.ts @@ -8,6 +8,7 @@ import { Address, assertIsSendableTransaction, assertIsTransactionWithBlockhashLifetime, + ClientWithTransactionPlanning, createTransactionMessage, createTransactionPlanExecutor, createTransactionPlanner, @@ -23,7 +24,6 @@ import { signTransactionMessageWithSigners, SimulateTransactionApi, TransactionPlanExecutorConfig, - TransactionPlanner, TransactionSigner, } from '@solana/kit'; import { findMetadataPda, SeedArgs } from './generated'; @@ -37,6 +37,12 @@ export type PdaDetails = { programData?: Address; }; +/** + * Fetches the on-chain state of `program` to determine whether the metadata + * account is canonical (i.e. the caller's authority matches the program's + * upgrade authority) and returns the resolved metadata PDA along with the + * associated program-data account when applicable. + */ export async function getPdaDetails(input: { rpc: Rpc; program: Address; @@ -100,9 +106,14 @@ export function createDefaultTransactionPlannerAndExecutor(input: { return { planner, executor }; } -export async function isValidInstructionPlan(instructionPlan: InstructionPlan, planner: TransactionPlanner) { +/** + * Returns `true` if the given instruction plan can be planned by the client's + * transaction planner without throwing — i.e. it fits within a single + * transaction subject to the planner's compute and size limits. + */ +export async function isValidInstructionPlan(instructionPlan: InstructionPlan, client: ClientWithTransactionPlanning) { try { - await planner(instructionPlan); + await client.planTransaction(instructionPlan); return true; } catch { return false; diff --git a/clients/js/src/packData.ts b/clients/js/src/packData.ts index bf49ee7..3def8dc 100644 --- a/clients/js/src/packData.ts +++ b/clients/js/src/packData.ts @@ -227,7 +227,7 @@ export function compressData(data: ReadonlyUint8Array, compression: Compression) case Compression.None: return data; case Compression.Gzip: - throw gzip(data as Uint8Array); + return gzip(data as Uint8Array); case Compression.Zlib: return deflate(data as Uint8Array); } @@ -238,7 +238,7 @@ export function uncompressData(data: ReadonlyUint8Array, compression: Compressio case Compression.None: return data; case Compression.Gzip: - throw ungzip(data as Uint8Array); + return ungzip(data as Uint8Array); case Compression.Zlib: return inflate(data as Uint8Array); } diff --git a/clients/js/src/plugin.ts b/clients/js/src/plugin.ts new file mode 100644 index 0000000..114189f --- /dev/null +++ b/clients/js/src/plugin.ts @@ -0,0 +1,159 @@ +import { + ClientWithGetMinimumBalance, + ClientWithPayer, + ClientWithTransactionPlanning, + ClientWithTransactionSending, + extendClient, + pipe, + TransactionPlanResult, +} from '@solana/kit'; +import { addSelfPlanAndSendFunctions, SelfPlanAndSendFunctions } from '@solana/kit/program-client-core'; + +import { + getCreateBufferInstructionPlan, + getCreateCanonicalBufferInstructionPlan, + getCreateNonCanonicalBufferInstructionPlan, +} from './createBuffer'; +import { + createMetadata, + getCreateMetadataInstructionPlanUsingExistingBuffer, + getCreateMetadataInstructionPlanUsingInstructionData, + getCreateMetadataInstructionPlanUsingNewBuffer, +} from './createMetadata'; +import { + programMetadataProgram as generatedProgramMetadataProgram, + ProgramMetadataPlugin as GeneratedProgramMetadataPlugin, + ProgramMetadataPluginInstructions as GeneratedProgramMetadataPluginInstructions, + ProgramMetadataPluginRequirements as GeneratedProgramMetadataPluginRequirements, +} from './generated'; +import { getUpdateBufferInstructionPlan } from './updateBuffer'; +import { + getUpdateMetadataInstructionPlanUsingExistingBuffer, + getUpdateMetadataInstructionPlanUsingInstructionData, + getUpdateMetadataInstructionPlanUsingNewBuffer, + updateMetadata, +} from './updateMetadata'; +import { MetadataInput } from './utils'; +import { writeMetadata } from './writeMetadata'; + +type MakeOptional = Omit & Partial>; + +type PluginInstructionInput unknown> = MakeOptional< + Parameters[1], + 'payer' +>; + +type PluginInstructionReturn unknown> = ReturnType & + SelfPlanAndSendFunctions; + +export type ProgramMetadataPluginRequirements = GeneratedProgramMetadataPluginRequirements & + ClientWithGetMinimumBalance & + ClientWithPayer & + ClientWithTransactionPlanning & + ClientWithTransactionSending; + +export type ProgramMetadataPlugin = Omit & { + instructions: ProgramMetadataPluginInstructions; + createMetadata: (input: MakeOptional) => Promise; + updateMetadata: (input: MakeOptional) => Promise; + writeMetadata: (input: MakeOptional) => Promise; +}; + +export type ProgramMetadataPluginInstructions = Omit & { + createBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + createCanonicalBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + createNonCanonicalBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + updateBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + createMetadataUsingInstructionData: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + createMetadataUsingNewBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + createMetadataUsingExistingBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + updateMetadataUsingInstructionData: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + updateMetadataUsingNewBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; + updateMetadataUsingExistingBuffer: ( + input: PluginInstructionInput, + ) => PluginInstructionReturn; +}; + +export function programMetadataProgram() { + return (client: T) => { + return pipe(client, generatedProgramMetadataProgram(), c => { + const withPayer = (input: TInput) => + ({ ...input, payer: input.payer ?? client.payer }) as TInput & { + payer: ProgramMetadataPluginRequirements['payer']; + }; + return extendClient(c, { + programMetadata: { + ...c.programMetadata, + instructions: { + ...c.programMetadata.instructions, + createBuffer: input => + addSelfPlanAndSendFunctions(c, getCreateBufferInstructionPlan(c, withPayer(input))), + createCanonicalBuffer: input => + addSelfPlanAndSendFunctions( + c, + getCreateCanonicalBufferInstructionPlan(c, withPayer(input)), + ), + createNonCanonicalBuffer: input => + addSelfPlanAndSendFunctions( + c, + getCreateNonCanonicalBufferInstructionPlan(c, withPayer(input)), + ), + updateBuffer: input => + addSelfPlanAndSendFunctions(c, getUpdateBufferInstructionPlan(c, withPayer(input))), + createMetadataUsingInstructionData: input => + addSelfPlanAndSendFunctions( + c, + getCreateMetadataInstructionPlanUsingInstructionData(c, withPayer(input)), + ), + createMetadataUsingNewBuffer: input => + addSelfPlanAndSendFunctions( + c, + getCreateMetadataInstructionPlanUsingNewBuffer(c, withPayer(input)), + ), + createMetadataUsingExistingBuffer: input => + addSelfPlanAndSendFunctions( + c, + getCreateMetadataInstructionPlanUsingExistingBuffer(c, withPayer(input)), + ), + updateMetadataUsingInstructionData: input => + addSelfPlanAndSendFunctions( + c, + getUpdateMetadataInstructionPlanUsingInstructionData(c, withPayer(input)), + ), + updateMetadataUsingNewBuffer: input => + addSelfPlanAndSendFunctions( + c, + getUpdateMetadataInstructionPlanUsingNewBuffer(c, withPayer(input)), + ), + updateMetadataUsingExistingBuffer: input => + addSelfPlanAndSendFunctions( + c, + getUpdateMetadataInstructionPlanUsingExistingBuffer(c, withPayer(input)), + ), + }, + createMetadata: input => createMetadata(c, withPayer(input)), + updateMetadata: input => updateMetadata(c, withPayer(input)), + writeMetadata: input => writeMetadata(c, withPayer(input)), + } satisfies ProgramMetadataPlugin, + }); + }); + }; +} diff --git a/clients/js/src/updateBuffer.ts b/clients/js/src/updateBuffer.ts index cc19d46..d82d3d5 100644 --- a/clients/js/src/updateBuffer.ts +++ b/clients/js/src/updateBuffer.ts @@ -2,7 +2,8 @@ import { getTransferSolInstruction } from '@solana-program/system'; import { Account, Address, - Lamports, + ClientWithGetMinimumBalance, + lamports, parallelInstructionPlan, ReadonlyUint8Array, sequentialInstructionPlan, @@ -12,21 +13,27 @@ import { Buffer, getCloseInstruction, getTrimInstruction, getWriteInstruction } import { REALLOC_LIMIT } from './internals'; import { getExtendInstructionPlan, getWriteInstructionPlan } from './utils'; -export function getUpdateBufferInstructionPlan(input: { - buffer: Address; - authority: TransactionSigner; - payer: TransactionSigner; - extraRent: Lamports; - sizeDifference: number | bigint; - sourceBuffer?: Account; - closeSourceBuffer?: Address | boolean; - data?: ReadonlyUint8Array; -}) { +export async function getUpdateBufferInstructionPlan( + client: ClientWithGetMinimumBalance, + input: { + buffer: Address; + authority: TransactionSigner; + payer: TransactionSigner; + sizeDifference: number | bigint; + sourceBuffer?: Account; + closeSourceBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + }, +) { if (!input.data && !input.sourceBuffer) { throw new Error('Either `data` or `sourceBuffer` must be provided to update a buffer.'); } const data = (input.sourceBuffer?.data.data ?? input.data) as ReadonlyUint8Array; + const extraRent = + input.sizeDifference > 0 + ? await client.getMinimumBalance(Number(input.sizeDifference), { withoutHeader: true }) + : lamports(0n); return sequentialInstructionPlan([ ...(input.sizeDifference > 0 @@ -34,7 +41,7 @@ export function getUpdateBufferInstructionPlan(input: { getTransferSolInstruction({ source: input.payer, destination: input.buffer, - amount: input.extraRent, + amount: extraRent, }), ] : []), diff --git a/clients/js/src/updateMetadata.ts b/clients/js/src/updateMetadata.ts index ca4f740..41b4a14 100644 --- a/clients/js/src/updateMetadata.ts +++ b/clients/js/src/updateMetadata.ts @@ -2,16 +2,18 @@ import { getTransferSolInstruction } from '@solana-program/system'; import { Account, Address, + ClientWithGetMinimumBalance, + ClientWithRpc, + ClientWithTransactionPlanning, + ClientWithTransactionSending, generateKeyPairSigner, GetAccountInfoApi, - GetMinimumBalanceForRentExemptionApi, InstructionPlan, lamports, Lamports, ReadonlyUint8Array, - Rpc, sequentialInstructionPlan, - TransactionPlanner, + TransactionPlanResult, TransactionSigner, } from '@solana/kit'; import { getCreateBufferInstructionPlan } from './createBuffer'; @@ -25,49 +27,47 @@ import { Metadata, SetDataInput, } from './generated'; -import { - createDefaultTransactionPlannerAndExecutor, - getPdaDetails, - isValidInstructionPlan, - REALLOC_LIMIT, -} from './internals'; -import { getAccountSize, getExtendInstructionPlan, MetadataInput, MetadataResponse } from './utils'; +import { isValidInstructionPlan, REALLOC_LIMIT } from './internals'; +import { getExtendInstructionPlan, MetadataInput, resolveMetadataPda } from './utils'; + +type UpdateMetadataClient = ClientWithGetMinimumBalance & + ClientWithRpc & + ClientWithTransactionPlanning & + ClientWithTransactionSending; +/** + * Updates an existing metadata account. + * + * When `input.metadata` is omitted, the PDA is derived from `program`, `seed` + * and — for non-canonical metadata accounts — `authority`. The current + * metadata account is fetched to compute the size difference. When + * `input.buffer` is provided as an address, the buffer account is fetched + * to determine its data length. + */ export async function updateMetadata( - input: MetadataInput & { - rpc: Rpc & - Parameters[0]['rpc']; - rpcSubscriptions: Parameters[0]['rpcSubscriptions']; - }, -): Promise { - const { planner, executor } = createDefaultTransactionPlannerAndExecutor(input); - const { programData, isCanonical, metadata } = await getPdaDetails(input); + client: UpdateMetadataClient, + input: MetadataInput, +): Promise { + const metadata = await resolveMetadataPda(input); const [metadataAccount, buffer] = await Promise.all([ - fetchMetadata(input.rpc, metadata), - input.buffer ? fetchBuffer(input.rpc, input.buffer) : Promise.resolve(undefined), + fetchMetadata(client.rpc, metadata), + input.buffer ? fetchBuffer(client.rpc, input.buffer) : Promise.resolve(undefined), ]); - - const instructionPlan = await getUpdateMetadataInstructionPlan({ + const plan = await getUpdateMetadataInstructionPlan(client, { ...input, - buffer, metadata: metadataAccount, - planner, - programData: isCanonical ? programData : undefined, + buffer, }); - - const transactionPlan = await planner(instructionPlan); - const result = await executor(transactionPlan); - return { metadata, result }; + return await client.sendTransactions(plan); } export async function getUpdateMetadataInstructionPlan( + client: ClientWithGetMinimumBalance & ClientWithTransactionPlanning, input: Omit & { metadata: Account; buffer?: Account; data?: ReadonlyUint8Array; payer: TransactionSigner; - planner: TransactionPlanner; - rpc: Rpc; closeBuffer?: Address | boolean; }, ): Promise { @@ -78,118 +78,110 @@ export async function getUpdateMetadataInstructionPlan( throw new Error('Metadata account is immutable'); } - const data = (input.buffer?.data.data ?? input.data) as ReadonlyUint8Array; - const fullRentPromise = input.rpc.getMinimumBalanceForRentExemption(getAccountSize(data.length)).send(); - const sizeDifference = BigInt(data.length) - BigInt(input.metadata.data.data.length); - const extraRentPromise = - sizeDifference > 0 - ? input.rpc.getMinimumBalanceForRentExemption(sizeDifference).send() - : Promise.resolve(lamports(0n)); - const [fullRent, extraRent, buffer] = await Promise.all([ - fullRentPromise, - extraRentPromise, - generateKeyPairSigner(), - ]); - if (input.buffer) { - return getUpdateMetadataInstructionPlanUsingExistingBuffer({ + return await getUpdateMetadataInstructionPlanUsingExistingBuffer(client, { ...input, buffer: input.buffer.address, - extraRent, - metadata: input.metadata.address, - fullRent, - sizeDifference, + dataLength: input.buffer.data.data.length, + metadata: input.metadata, }); } - const extendedInput = { + const data = input.data as ReadonlyUint8Array; + const usingInstructionDataPlan = await getUpdateMetadataInstructionPlanUsingInstructionData(client, { ...input, - buffer, data, - extraRent, - fullRent, - metadata: input.metadata.address, - sizeDifference, - }; + metadata: input.metadata, + }); + const validPlan = await isValidInstructionPlan(usingInstructionDataPlan, client); + if (validPlan) return usingInstructionDataPlan; - const plan = getUpdateMetadataInstructionPlanUsingInstructionData(extendedInput); - const validPlan = await isValidInstructionPlan(plan, input.planner); - return validPlan ? plan : getUpdateMetadataInstructionPlanUsingNewBuffer(extendedInput); + const newBuffer = await generateKeyPairSigner(); + return await getUpdateMetadataInstructionPlanUsingNewBuffer(client, { + ...input, + buffer: newBuffer, + data, + metadata: input.metadata, + }); } -export function getUpdateMetadataInstructionPlanUsingInstructionData( - input: Omit & { - extraRent: Lamports; +export async function getUpdateMetadataInstructionPlanUsingInstructionData( + client: ClientWithGetMinimumBalance, + input: Omit & { + data: ReadonlyUint8Array; + metadata: Account; payer: TransactionSigner; - sizeDifference: bigint | number; }, ) { + const sizeDifference = BigInt(input.data.length) - BigInt(input.metadata.data.data.length); + const extraRent = await getExtraRent(client, sizeDifference); return sequentialInstructionPlan([ - ...(input.sizeDifference > 0 + ...(sizeDifference > 0 ? [ getTransferSolInstruction({ source: input.payer, - destination: input.metadata, - amount: input.extraRent, + destination: input.metadata.address, + amount: extraRent, }), ] : []), - getSetDataInstruction({ ...input, buffer: undefined }), - ...(input.sizeDifference < 0 + getSetDataInstruction({ ...input, metadata: input.metadata.address }), + ...(sizeDifference < 0 ? [ getTrimInstruction({ - account: input.metadata, + account: input.metadata.address, authority: input.authority, destination: input.payer.address, program: input.program, - programData: input.program, + programData: input.programData, }), ] : []), ]); } -export function getUpdateMetadataInstructionPlanUsingNewBuffer( - input: Omit & { +export async function getUpdateMetadataInstructionPlanUsingNewBuffer( + client: ClientWithGetMinimumBalance, + input: Omit & { buffer: TransactionSigner; closeBuffer?: Address | boolean; data: ReadonlyUint8Array; - extraRent: Lamports; - fullRent: Lamports; + metadata: Account; payer: TransactionSigner; - sizeDifference: number | bigint; }, ) { + const sizeDifference = BigInt(input.data.length) - BigInt(input.metadata.data.data.length); + const extraRent = await getExtraRent(client, sizeDifference); return sequentialInstructionPlan([ - ...(input.sizeDifference > 0 + ...(sizeDifference > 0 ? [ getTransferSolInstruction({ source: input.payer, - destination: input.metadata, - amount: input.extraRent, + destination: input.metadata.address, + amount: extraRent, }), ] : []), - ...(input.sizeDifference > REALLOC_LIMIT + ...(sizeDifference > REALLOC_LIMIT ? [ getExtendInstructionPlan({ - account: input.metadata, + account: input.metadata.address, authority: input.authority, - extraLength: Number(input.sizeDifference), + extraLength: Number(sizeDifference), program: input.program, programData: input.programData, }), ] : []), - getCreateBufferInstructionPlan({ + await getCreateBufferInstructionPlan(client, { newBuffer: input.buffer, authority: input.authority, payer: input.payer, - rent: input.fullRent, data: input.data, }), getSetDataInstruction({ ...input, + metadata: input.metadata.address, buffer: input.buffer.address, data: undefined, }), @@ -202,10 +194,10 @@ export function getUpdateMetadataInstructionPlanUsingNewBuffer( }), ] : []), - ...(input.sizeDifference < 0 + ...(sizeDifference < 0 ? [ getTrimInstruction({ - account: input.metadata, + account: input.metadata.address, authority: input.authority, destination: input.payer.address, program: input.program, @@ -216,32 +208,34 @@ export function getUpdateMetadataInstructionPlanUsingNewBuffer( ]); } -export function getUpdateMetadataInstructionPlanUsingExistingBuffer( - input: Omit & { +export async function getUpdateMetadataInstructionPlanUsingExistingBuffer( + client: ClientWithGetMinimumBalance, + input: Omit & { buffer: Address; closeBuffer?: Address | boolean; - extraRent: Lamports; - fullRent: Lamports; + dataLength: number; + metadata: Account; payer: TransactionSigner; - sizeDifference: number | bigint; }, ) { + const sizeDifference = BigInt(input.dataLength) - BigInt(input.metadata.data.data.length); + const extraRent = await getExtraRent(client, sizeDifference); return sequentialInstructionPlan([ - ...(input.sizeDifference > 0 + ...(sizeDifference > 0 ? [ getTransferSolInstruction({ source: input.payer, - destination: input.metadata, - amount: input.extraRent, + destination: input.metadata.address, + amount: extraRent, }), ] : []), - ...(input.sizeDifference > REALLOC_LIMIT + ...(sizeDifference > REALLOC_LIMIT ? [ getExtendInstructionPlan({ - account: input.metadata, + account: input.metadata.address, authority: input.authority, - extraLength: Number(input.sizeDifference), + extraLength: Number(sizeDifference), program: input.program, programData: input.programData, }), @@ -249,6 +243,7 @@ export function getUpdateMetadataInstructionPlanUsingExistingBuffer( : []), getSetDataInstruction({ ...input, + metadata: input.metadata.address, buffer: input.buffer, data: undefined, }), @@ -261,10 +256,10 @@ export function getUpdateMetadataInstructionPlanUsingExistingBuffer( }), ] : []), - ...(input.sizeDifference < 0 + ...(sizeDifference < 0 ? [ getTrimInstruction({ - account: input.metadata, + account: input.metadata.address, authority: input.authority, destination: input.payer.address, program: input.program, @@ -274,3 +269,8 @@ export function getUpdateMetadataInstructionPlanUsingExistingBuffer( : []), ]); } + +async function getExtraRent(client: ClientWithGetMinimumBalance, sizeDifference: bigint): Promise { + if (sizeDifference <= 0) return lamports(0n); + return await client.getMinimumBalance(Number(sizeDifference), { withoutHeader: true }); +} diff --git a/clients/js/src/utils.ts b/clients/js/src/utils.ts index 6558a40..33e3856 100644 --- a/clients/js/src/utils.ts +++ b/clients/js/src/utils.ts @@ -17,7 +17,6 @@ import { MicroLamports, ReadonlyUint8Array, Rpc, - TransactionPlanResult, TransactionSigner, unwrapOption, } from '@solana/kit'; @@ -25,6 +24,8 @@ import { CompressionArgs, DataSourceArgs, EncodingArgs, + findCanonicalPda, + findNonCanonicalPda, FormatArgs, getExtendInstruction, getWriteInstruction, @@ -64,17 +65,47 @@ export type MetadataInput = { * Defaults to `true`. */ closeBuffer?: Address | boolean; -}; - -export type MetadataResponse = { - metadata: Address; - result: TransactionPlanResult; + /** + * The metadata PDA address. When omitted, it is derived from `program`, + * `seed`, and — for non-canonical metadata accounts — `authority`. + * + * Provide this explicitly to skip the PDA derivation step. + */ + metadata?: Address; + /** + * The program data account address. When provided, the operation targets a + * canonical metadata account (managed by the program upgrade authority). + * When omitted, the operation targets a non-canonical metadata account + * (managed by a third-party authority). + */ + programData?: Address; }; export function getAccountSize(dataLength: bigint | number) { return BigInt(ACCOUNT_HEADER_LENGTH) + BigInt(dataLength); } +/** + * Resolves the metadata PDA address for the given input. + * + * - When `input.metadata` is provided, it is used as-is. + * - When `input.programData` is provided, the canonical PDA is derived from + * `program` and `seed`. + * - Otherwise the non-canonical PDA is derived from `program`, `seed` and + * `authority`. + */ +export async function resolveMetadataPda(input: MetadataInput): Promise
{ + if (input.metadata) return input.metadata; + const [metadata] = input.programData + ? await findCanonicalPda({ program: input.program, seed: input.seed }) + : await findNonCanonicalPda({ + authority: input.authority.address, + program: input.program, + seed: input.seed, + }); + return metadata; +} + export async function getProgramDataPda(program: Address) { return await getProgramDerivedAddress({ programAddress: LOADER_V3_PROGRAM_ADDRESS, diff --git a/clients/js/src/writeMetadata.ts b/clients/js/src/writeMetadata.ts index da55ae3..7cae8cb 100644 --- a/clients/js/src/writeMetadata.ts +++ b/clients/js/src/writeMetadata.ts @@ -1,55 +1,63 @@ import { + Account, + Address, + ClientWithGetMinimumBalance, + ClientWithRpc, + ClientWithTransactionPlanning, + ClientWithTransactionSending, GetAccountInfoApi, - GetMinimumBalanceForRentExemptionApi, InstructionPlan, MaybeAccount, - Rpc, + ReadonlyUint8Array, + TransactionPlanResult, + TransactionSigner, } from '@solana/kit'; import { getCreateMetadataInstructionPlan } from './createMetadata'; -import { fetchBuffer, fetchMaybeMetadata, Metadata } from './generated'; -import { createDefaultTransactionPlannerAndExecutor, getPdaDetails } from './internals'; +import { Buffer, fetchBuffer, fetchMaybeMetadata, InitializeInput, Metadata, SetDataInput } from './generated'; import { getUpdateMetadataInstructionPlan } from './updateMetadata'; -import { MetadataInput, MetadataResponse } from './utils'; +import { MetadataInput, resolveMetadataPda } from './utils'; -export async function writeMetadata( - input: MetadataInput & { - rpc: Rpc & - Parameters[0]['rpc']; - rpcSubscriptions: Parameters[0]['rpcSubscriptions']; - }, -): Promise { - const { planner, executor } = createDefaultTransactionPlannerAndExecutor(input); - const { programData, isCanonical, metadata } = await getPdaDetails(input); +type WriteMetadataClient = ClientWithGetMinimumBalance & + ClientWithRpc & + ClientWithTransactionPlanning & + ClientWithTransactionSending; + +/** + * Creates or updates a metadata account. + * + * When `input.metadata` is omitted, the PDA is derived from `program`, `seed` + * and — for non-canonical metadata accounts — `authority`. The metadata + * account is fetched to determine whether it already exists; if so, the + * operation is an update, otherwise it is a create. When `input.buffer` is + * provided as an address, the buffer account is fetched to determine its data + * length. + */ +export async function writeMetadata(client: WriteMetadataClient, input: MetadataInput): Promise { + const metadata = await resolveMetadataPda(input); const [metadataAccount, buffer] = await Promise.all([ - fetchMaybeMetadata(input.rpc, metadata), - input.buffer ? fetchBuffer(input.rpc, input.buffer) : Promise.resolve(undefined), + fetchMaybeMetadata(client.rpc, metadata), + input.buffer ? fetchBuffer(client.rpc, input.buffer) : Promise.resolve(undefined), ]); - - const instructionPlan = await getWriteMetadataInstructionPlan({ + const plan = await getWriteMetadataInstructionPlan(client, { ...input, - buffer, metadata: metadataAccount, - programData: isCanonical ? programData : undefined, - planner, + buffer, }); - - const transactionPlan = await planner(instructionPlan); - const result = await executor(transactionPlan); - return { metadata, result }; + return await client.sendTransactions(plan); } export async function getWriteMetadataInstructionPlan( - input: Omit[0], 'metadata'> & { - metadata: MaybeAccount; - }, + client: ClientWithGetMinimumBalance & ClientWithTransactionPlanning, + input: Omit & + Omit & { + buffer?: Account; + closeBuffer?: Address | boolean; + data?: ReadonlyUint8Array; + metadata: MaybeAccount; + payer: TransactionSigner; + }, ): Promise { return input.metadata.exists - ? await getUpdateMetadataInstructionPlan({ - ...input, - metadata: input.metadata, - }) - : await getCreateMetadataInstructionPlan({ - ...input, - metadata: input.metadata.address, - }); + ? await getUpdateMetadataInstructionPlan(client, { ...input, metadata: input.metadata }) + : await getCreateMetadataInstructionPlan(client, { ...input, metadata: input.metadata.address }); } diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index 7875605..1acfc2f 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -1,122 +1,56 @@ -import { getCreateAccountInstruction, getTransferSolInstruction } from '@solana-program/system'; +import { systemProgram } from '@solana-program/system'; import { Address, - airdropFactory, - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, - assertIsSendableTransaction, - assertIsTransactionWithBlockhashLifetime, - BASE_ACCOUNT_SIZE, - BaseTransactionMessage, - Commitment, - createSolanaRpc, - createSolanaRpcSubscriptions, - createTransactionMessage, + createClient, generateKeyPairSigner, getBase64Encoder, - getSignatureFromTransaction, - Instruction, - isOption, KeyPairSigner, - lamports, - pipe, - ReadonlyUint8Array, - Rpc, - RpcSubscriptions, - sendAndConfirmTransactionFactory, - setTransactionMessageFeePayerSigner, - setTransactionMessageLifetimeUsingBlockhash, - Signature, - signTransactionMessageWithSigners, - SolanaRpcApi, - SolanaRpcSubscriptionsApi, - TransactionMessageWithBlockhashLifetime, - TransactionMessageWithFeePayer, - TransactionSigner, - unwrapOption, + Lamports, + sol, + solToLamports, } from '@solana/kit'; +import { solanaLocalRpc } from '@solana/kit-plugin-rpc'; +import { airdropSigner, generatedSigner } from '@solana/kit-plugin-signer'; + import { - Compression, - DataSource, - Encoding, - findCanonicalPda, - findNonCanonicalPda, - Format, - getAllocateInstruction, - getExtendInstruction, - getInitializeInstruction, getProgramDataPda as getLoaderV3ProgramDataPda, - getSetAuthorityInstruction, - getWriteInstruction, - InitializeInput, LOADER_V3_PROGRAM_ADDRESS, - SeedArgs, + programMetadataProgram, } from '../src'; import { getDeployWithMaxDataLenInstruction as getLoaderV3DeployInstruction } from './loader-v3/deploy'; import { getInitializeBufferInstruction as getLoaderV3InitializeBufferInstruction } from './loader-v3/initializeBuffer'; import { getWriteInstruction as getLoaderV3WriteInstruction } from './loader-v3/write'; export const REALLOC_LIMIT = 10_240; + const SMALLER_VALID_PROGRAM_BINARY = 'f0VMRgIBAQAAAAAAAAAAAAMA9wABAAAA6AAAAAAAAABAAAAAAAAAAMgBAAAAAAAAAAAAAEAAOAADAEAABgAFAAEAAAAFAAAA6AAAAAAAAADoAAAAAAAAAOgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAQAAAAAAAAAQAAAAQAAABgAQAAAAAAAGABAAAAAAAAYAEAAAAAAAA8AAAAAAAAADwAAAAAAAAAABAAAAAAAAACAAAABgAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAHAAAAAAAAAAcAAAAAAAAAAIAAAAAAAAAJUAAAAAAAAAHgAAAAAAAAAEAAAAAAAAAAYAAAAAAAAAYAEAAAAAAAALAAAAAAAAABgAAAAAAAAABQAAAAAAAACQAQAAAAAAAAoAAAAAAAAADAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAQAAEA6AAAAAAAAAAAAAAAAAAAAABlbnRyeXBvaW50AAAudGV4dAAuZHluYW1pYwAuZHluc3ltAC5keW5zdHIALnNoc3RydGFiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABgAAAAAAAADoAAAAAAAAAOgAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAHAAAABgAAAAMAAAAAAAAA8AAAAAAAAADwAAAAAAAAAHAAAAAAAAAABAAAAAAAAAAIAAAAAAAAABAAAAAAAAAAEAAAAAsAAAACAAAAAAAAAGABAAAAAAAAYAEAAAAAAAAwAAAAAAAAAAQAAAABAAAACAAAAAAAAAAYAAAAAAAAABgAAAADAAAAAgAAAAAAAACQAQAAAAAAAJABAAAAAAAADAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAgAAAAAwAAAAAAAAAAAAAAAAAAAAAAAACcAQAAAAAAACoAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA'; -type Client = { - rpc: Rpc; - rpcSubscriptions: RpcSubscriptions; +export const createTestClient = () => { + return createClient() + .use(generatedSigner()) + .use(solanaLocalRpc()) + .use(airdropSigner(solToLamports(sol('1')))) + .use(systemProgram()) + .use(programMetadataProgram()); }; -export const createDefaultSolanaClient = (): Client => { - const rpc = createSolanaRpc('http://127.0.0.1:8899'); - const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900'); - return { rpc, rpcSubscriptions }; -}; +export type TestClient = Awaited>; -export const generateKeyPairSignerWithSol = async (client: Client, putativeLamports: bigint = 1_000_000_000n) => { +export const generateKeyPairSignerWithSol = async ( + client: TestClient, + putativeLamports: bigint = 1_000_000_000n, +): Promise => { const signer = await generateKeyPairSigner(); - await airdropFactory(client)({ - recipientAddress: signer.address, - lamports: lamports(putativeLamports), - commitment: 'confirmed', - }); + await client.airdrop(signer.address, putativeLamports as Lamports); return signer; }; -export const createDefaultTransaction = async (client: Client, feePayer: TransactionSigner) => { - const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); - return pipe( - createTransactionMessage({ version: 0 }), - tx => setTransactionMessageFeePayerSigner(feePayer, tx), - tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), - ); -}; - -export const signAndSendTransaction = async ( - client: Client, - transactionMessage: BaseTransactionMessage & - TransactionMessageWithFeePayer & - TransactionMessageWithBlockhashLifetime, - commitment: Commitment = 'confirmed', -) => { - const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); - const signature = getSignatureFromTransaction(signedTransaction); - assertIsSendableTransaction(signedTransaction); - assertIsTransactionWithBlockhashLifetime(signedTransaction); - await sendAndConfirmTransactionFactory(client)(signedTransaction, { - commitment, - }); - return signature; -}; - -export const getBalance = async (client: Client, address: Address) => +export const getBalance = async (client: TestClient, address: Address) => (await client.rpc.getBalance(address, { commitment: 'confirmed' }).send()).value; -export const getRentWithoutHeader = async (client: Client, bytes: bigint | number) => { - const rentForHeader = await client.rpc.getMinimumBalanceForRentExemption(0n).send(); - return lamports((rentForHeader * BigInt(bytes)) / BigInt(BASE_ACCOUNT_SIZE)); -}; - export const createDeployedProgram = async ( - client: Client, + client: TestClient, authority: KeyPairSigner, payer?: KeyPairSigner, ): Promise<[Address, Address]> => { @@ -125,260 +59,50 @@ export const createDeployedProgram = async ( const data = getBase64Encoder().encode(SMALLER_VALID_PROGRAM_BINARY); const dataSize = BigInt(37 + data.length); const programSize = 36n; - const [buffer, program, dataRent, programRent, defaultTransaction] = await Promise.all([ + const [buffer, program, dataRent, programRent] = await Promise.all([ generateKeyPairSigner(), generateKeyPairSigner(), - client.rpc.getMinimumBalanceForRentExemption(dataSize).send(), - client.rpc.getMinimumBalanceForRentExemption(programSize).send(), - createDefaultTransaction(client, payer), + client.getMinimumBalance(Number(dataSize)), + client.getMinimumBalance(Number(programSize)), ]); const [programData] = await getLoaderV3ProgramDataPda(program.address); - // Create instructions. - const createBufferIx = getCreateAccountInstruction({ - payer, - newAccount: buffer, - lamports: dataRent, - space: dataSize, - programAddress: LOADER_V3_PROGRAM_ADDRESS, - }); - const initializeBufferIx = getLoaderV3InitializeBufferInstruction({ - sourceAccount: buffer.address, - bufferAuthority: authority.address, - }); - const writeIx = getLoaderV3WriteInstruction({ - bufferAccount: buffer.address, - bufferAuthority: authority, - offset: 0, - bytes: data, - }); - const createProgramIx = getCreateAccountInstruction({ - payer, - newAccount: program, - lamports: programRent, - space: programSize, - programAddress: LOADER_V3_PROGRAM_ADDRESS, - }); - const deployIx = getLoaderV3DeployInstruction({ - payerAccount: payer, - programDataAccount: programData, - programAccount: program.address, - bufferAccount: buffer.address, - authority, - maxDataLen: data.length, - }); - - // Send instructions. - await pipe( - defaultTransaction, - tx => appendTransactionMessageInstructions([createBufferIx, initializeBufferIx, writeIx], tx), - tx => signAndSendTransaction(client, tx), - ); - await pipe( - defaultTransaction, - tx => appendTransactionMessageInstructions([createProgramIx, deployIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // Create the buffer, write the program binary to it, create the program + // account and deploy the program from the buffer. + await client.sendTransactions([ + client.system.instructions.createAccount({ + payer, + newAccount: buffer, + lamports: dataRent, + space: dataSize, + programAddress: LOADER_V3_PROGRAM_ADDRESS, + }), + getLoaderV3InitializeBufferInstruction({ + sourceAccount: buffer.address, + bufferAuthority: authority.address, + }), + getLoaderV3WriteInstruction({ + bufferAccount: buffer.address, + bufferAuthority: authority, + offset: 0, + bytes: data, + }), + client.system.instructions.createAccount({ + payer, + newAccount: program, + lamports: programRent, + space: programSize, + programAddress: LOADER_V3_PROGRAM_ADDRESS, + }), + getLoaderV3DeployInstruction({ + payerAccount: payer, + programDataAccount: programData, + programAccount: program.address, + bufferAccount: buffer.address, + authority, + maxDataLen: data.length, + }), + ]); return [program.address, programData]; }; - -export async function createBuffer( - client: Client, - input: { - buffer: Address; - authority: TransactionSigner; - payer?: TransactionSigner; - program?: Address; - programData?: Address; - seed?: SeedArgs; - dataLength?: number; - data?: ReadonlyUint8Array; - }, -): Promise { - const { buffer, authority, program, programData, seed, data } = input; - const payer = input.payer ?? authority; - const dataLenth = input.dataLength ?? input.data?.length ?? 0; - const bufferSize = 96 + dataLenth; - const [rent, defaultTransaction] = await Promise.all([ - client.rpc.getMinimumBalanceForRentExemption(BigInt(bufferSize)).send(), - createDefaultTransaction(client, payer), - ]); - const preFundIx = getTransferSolInstruction({ - source: payer, - destination: buffer, - amount: rent, - }); - const allocateIx = getAllocateInstruction({ - buffer, - authority, - program, - programData, - seed, - }); - const instructions: Instruction[] = [preFundIx, allocateIx]; - if (dataLenth >= REALLOC_LIMIT) { - let offset = 0; - while (offset < dataLenth) { - const length = Math.min(dataLenth - offset, REALLOC_LIMIT); - instructions.push(getExtendInstruction({ account: buffer, authority, length })); - offset += length; - } - } - await pipe( - defaultTransaction, - tx => appendTransactionMessageInstructions(instructions, tx), - tx => signAndSendTransaction(client, tx), - ); - if (data) { - let offset = 0; - const chunkSize = 900; - const writePromises: Promise[] = []; - while (offset < data.length) { - const writeIx = getWriteInstruction({ - buffer, - authority, - offset, - data: data.slice(offset, offset + chunkSize), - }); - writePromises.push( - pipe( - defaultTransaction, - tx => appendTransactionMessageInstruction(writeIx, tx), - tx => signAndSendTransaction(client, tx), - ), - ); - offset += chunkSize; - } - await Promise.all(writePromises); - } -} - -export async function createCanonicalBuffer( - client: Client, - input: { - authority: TransactionSigner; - payer?: TransactionSigner; - program: Address; - programData: Address; - seed: SeedArgs; - dataLength?: number; - data?: ReadonlyUint8Array; - }, -) { - const buffer = await findCanonicalPda({ - program: input.program, - seed: input.seed, - }); - await createBuffer(client, { buffer: buffer[0], ...input }); - return buffer; -} - -export async function createNonCanonicalBuffer( - client: Client, - input: { - authority: TransactionSigner; - payer?: TransactionSigner; - program: Address; - seed: SeedArgs; - dataLength?: number; - data?: ReadonlyUint8Array; - }, -) { - const buffer = await findNonCanonicalPda({ - program: input.program, - authority: input.authority.address, - seed: input.seed, - }); - await createBuffer(client, { buffer: buffer[0], ...input }); - return buffer; -} - -export async function createKeypairBuffer( - client: Client, - input: { - payer: TransactionSigner; - dataLength?: number; - data?: ReadonlyUint8Array; - }, -) { - const buffer = await generateKeyPairSigner(); - await createBuffer(client, { - buffer: buffer.address, - authority: buffer, - ...input, - }); - return buffer; -} - -export async function setAuthority( - client: Client, - input: Parameters[0] & { - payer: TransactionSigner; - }, -) { - const setAuthorityIx = getSetAuthorityInstruction(input); - await pipe( - await createDefaultTransaction(client, input.payer), - tx => appendTransactionMessageInstructions([setAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); -} - -type PartialExcept = Partial> & Pick; -export async function createMetadata( - client: Client, - input: PartialExcept & { - payer?: TransactionSigner; - }, -): Promise { - const { payer = input.authority, ...initializeInput } = input; - const data = (isOption(input.data) ? unwrapOption(input.data) : input.data) ?? new Uint8Array([]); - const dataSize = BigInt(96 + data.length); - const [rent, defaultTransaction] = await Promise.all([ - client.rpc.getMinimumBalanceForRentExemption(dataSize).send(), - createDefaultTransaction(client, payer), - ]); - const preFundIx = getTransferSolInstruction({ - source: payer, - destination: input.metadata, - amount: rent, - }); - const initializeIx = getInitializeInstruction({ - encoding: Encoding.None, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Direct, - ...initializeInput, - }); - await pipe( - defaultTransaction, - tx => appendTransactionMessageInstructions([preFundIx, initializeIx], tx), - tx => signAndSendTransaction(client, tx), - ); -} - -export async function createCanonicalMetadata( - client: Client, - input: Omit[1], 'metadata'>, -) { - const metadata = await findCanonicalPda({ - program: input.program, - seed: input.seed, - }); - await createMetadata(client, { metadata: metadata[0], ...input }); - return metadata; -} - -export async function createNonCanonicalMetadata( - client: Client, - input: Omit[1], 'metadata'>, -) { - const metadata = await findNonCanonicalPda({ - program: input.program, - authority: input.authority.address, - seed: input.seed, - }); - await createMetadata(client, { metadata: metadata[0], ...input }); - return metadata; -} diff --git a/clients/js/test/allocate.test.ts b/clients/js/test/allocate.test.ts index 8af6773..36bbbfd 100644 --- a/clients/js/test/allocate.test.ts +++ b/clients/js/test/allocate.test.ts @@ -1,25 +1,11 @@ -import { getTransferSolInstruction } from '@solana-program/system'; -import { address, appendTransactionMessageInstructions, generateKeyPairSigner, none, pipe, some } from '@solana/kit'; +import { address, generateKeyPairSigner, none, some } from '@solana/kit'; import test from 'ava'; -import { - AccountDiscriminator, - Buffer, - fetchBuffer, - findCanonicalPda, - findNonCanonicalPda, - getAllocateInstruction, -} from '../src'; -import { - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { ACCOUNT_HEADER_LENGTH, AccountDiscriminator, Buffer, findCanonicalPda, findNonCanonicalPda } from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it allocates a canonical PDA buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); @@ -27,30 +13,18 @@ test('it allocates a canonical PDA buffer', async t => { const seed = 'dummy'; const [buffer] = await findCanonicalPda({ program, seed }); - // And given the buffer account is pre-funded. - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n).send(); - const preFundIx = getTransferSolInstruction({ - source: authority, - destination: buffer, - amount: rent, - }); - // When we allocate the buffer account for a canonical PDA. - const allocateIx = getAllocateInstruction({ - buffer, - authority, - program, - programData, - seed, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFundIx, allocateIx], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: buffer, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH), + }), + client.programMetadata.instructions.allocate({ buffer, authority, program, programData, seed }), + ]); // Then we expect the following buffer account to be created. - const bufferAccount = await fetchBuffer(client.rpc, buffer); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer); t.like(bufferAccount.data, { discriminator: AccountDiscriminator.Buffer, program: some(program), @@ -63,41 +37,26 @@ test('it allocates a canonical PDA buffer', async t => { test('it allocates a non-canonical PDA buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following seed derivation for the buffer. const seed = 'dummy'; - const [buffer] = await findNonCanonicalPda({ - program, - authority: authority.address, - seed, - }); - - // And given the buffer account is pre-funded. - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n).send(); - const preFundIx = getTransferSolInstruction({ - source: authority, - destination: buffer, - amount: rent, - }); + const [buffer] = await findNonCanonicalPda({ program, authority: authority.address, seed }); // When we allocate the buffer account for a canonical PDA. - const allocateIx = getAllocateInstruction({ - buffer, - authority, - program, - seed, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFundIx, allocateIx], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: buffer, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH), + }), + client.programMetadata.instructions.allocate({ buffer, authority, program, seed }), + ]); // Then we expect the following buffer account to be created. - const bufferAccount = await fetchBuffer(client.rpc, buffer); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer); t.like(bufferAccount.data, { discriminator: AccountDiscriminator.Buffer, program: some(program), @@ -110,30 +69,21 @@ test('it allocates a non-canonical PDA buffer', async t => { test('it allocates a keypair buffer', async t => { // Given the following payer and buffer keypairs. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [payer, buffer] = await Promise.all([generateKeyPairSignerWithSol(client), generateKeyPairSigner()]); - // And given the buffer account is pre-funded. - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n).send(); - const preFundIx = getTransferSolInstruction({ - source: payer, - destination: buffer.address, - amount: rent, - }); - // When we allocate the buffer account for a canonical PDA. - const allocateIx = getAllocateInstruction({ - buffer: buffer.address, - authority: buffer, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions([preFundIx, allocateIx], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: payer, + destination: buffer.address, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH), + }), + client.programMetadata.instructions.allocate({ buffer: buffer.address, authority: buffer }), + ]); // Then we expect the following buffer account to be created. - const bufferAccount = await fetchBuffer(client.rpc, buffer.address); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer.address); t.like(bufferAccount.data, { discriminator: AccountDiscriminator.Buffer, program: none(), diff --git a/clients/js/test/close.test.ts b/clients/js/test/close.test.ts index 5b636ba..b590542 100644 --- a/clients/js/test/close.test.ts +++ b/clients/js/test/close.test.ts @@ -1,65 +1,47 @@ import { address, - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, generateKeyPairSigner, getUtf8Encoder, isSolanaError, - pipe, SOLANA_ERROR__INSTRUCTION_ERROR__INCORRECT_AUTHORITY, } from '@solana/kit'; import test from 'ava'; -import { fetchMaybeMetadata, getCloseInstruction, getSetAuthorityInstruction } from '../src'; -import { - createCanonicalBuffer, - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createKeypairBuffer, - createNonCanonicalBuffer, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { Compression, DataSource, Encoding, findCanonicalPda, findNonCanonicalPda, Format } from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it can close canonical metadata accounts', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // When the program authority closes the metadata account. - const closeIx = getCloseInstruction({ - account: metadata, - authority, - program, - programData, - destination: authority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: metadata, authority, program, programData, destination: authority.address }) + .sendTransaction(); // Then we expect the metadata account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetchMaybe(metadata); t.false(account.exists); }); test('the set authority of a canonical metadata can close the account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -67,43 +49,44 @@ test('the set authority of a canonical metadata can close the account', async t const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - newAuthority: explicitAuthority.address, - program, - programData, - }); - - // When the explicit authority closes the metadata account. - const closeIx = getCloseInstruction({ - account: metadata, - authority: explicitAuthority, - destination: explicitAuthority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, closeIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When an explicit authority is set on the metadata account + // and that explicit authority closes the account. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + newAuthority: explicitAuthority.address, + program, + programData, + }), + client.programMetadata.instructions.close({ + account: metadata, + authority: explicitAuthority, + destination: explicitAuthority.address, + }), + ]); // Then we expect the metadata account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetchMaybe(metadata); t.false(account.exists); }); test('the current upgrade authority of program can close its canonical metadata account even when an authority is set on the account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -111,203 +94,169 @@ test('the current upgrade authority of program can close its canonical metadata const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - newAuthority: explicitAuthority.address, - program, - programData, - }); - - // When the program authority closes the metadata account. - const closeIx = getCloseInstruction({ - account: metadata, - authority, - destination: authority.address, - program, - programData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, closeIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When an explicit authority is set on the metadata account + // and the program authority closes it anyway. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + newAuthority: explicitAuthority.address, + program, + programData, + }), + client.programMetadata.instructions.close({ + account: metadata, + authority, + destination: authority.address, + program, + programData, + }), + ]); // Then we expect the metadata account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetchMaybe(metadata); t.false(account.exists); }); test('it can close non-canonical metadata accounts', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized non-canonical metadata account. - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When the metadata authority closes the metadata account. - const closeIx = getCloseInstruction({ - account: metadata, - authority, - program, - destination: authority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: metadata, authority, program, destination: authority.address }) + .sendTransaction(); // Then we expect the metadata account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetchMaybe(metadata); t.false(account.exists); }); test('it can close canonical buffers', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following pre-allocated canonical buffer. - const [buffer] = await createCanonicalBuffer(client, { - authority, - program, - programData, - seed: 'dummy', - data: getUtf8Encoder().encode('Hello, World!'), - }); + await client.programMetadata.instructions + .createCanonicalBuffer({ + authority, + program, + programData, + seed: 'dummy', + data: getUtf8Encoder().encode('Hello, World!'), + }) + .sendTransaction(); + const [buffer] = await findCanonicalPda({ program, seed: 'dummy' }); // When the program authority closes the buffer account. - const closeIx = getCloseInstruction({ - account: buffer, - authority, - program, - programData, - destination: authority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: buffer, authority, program, programData, destination: authority.address }) + .sendTransaction(); // Then we expect the buffer account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, buffer); + const account = await client.programMetadata.accounts.buffer.fetchMaybe(buffer); t.false(account.exists); }); test('it can close non-canonical buffers', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following pre-allocated non-canonical buffer. - const [buffer] = await createNonCanonicalBuffer(client, { - authority, - program, - seed: 'dummy', - data: getUtf8Encoder().encode('Hello, World!'), - }); + await client.programMetadata.instructions + .createNonCanonicalBuffer({ + authority, + program, + seed: 'dummy', + data: getUtf8Encoder().encode('Hello, World!'), + }) + .sendTransaction(); + const [buffer] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When the buffer authority closes the buffer account. - const closeIx = getCloseInstruction({ - account: buffer, - authority, - program, - destination: authority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: buffer, authority, program, destination: authority.address }) + .sendTransaction(); // Then we expect the buffer account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, buffer); + const account = await client.programMetadata.accounts.buffer.fetchMaybe(buffer); t.false(account.exists); }); test('it can close keypair buffers', async t => { // Given the following payer. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const payer = await generateKeyPairSignerWithSol(client); // And the following pre-allocated keypair buffer. - const buffer = await createKeypairBuffer(client, { - payer, - data: getUtf8Encoder().encode('Hello, World!'), - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, payer, data: getUtf8Encoder().encode('Hello, World!') }) + .sendTransaction(); // When the buffer closes its own account. - const closeIx = getCloseInstruction({ - account: buffer.address, - authority: buffer, - destination: payer.address, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: buffer.address, authority: buffer, destination: payer.address }) + .sendTransaction(); // Then we expect the buffer account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, buffer.address); + const account = await client.programMetadata.accounts.buffer.fetchMaybe(buffer.address); t.false(account.exists); }); test('it cannot close a keypair buffer with a different authority set using the buffer keypair', async t => { // Given the following payer. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const payer = await generateKeyPairSignerWithSol(client); // And the following pre-allocated keypair buffer. - const buffer = await createKeypairBuffer(client, { - payer, - data: getUtf8Encoder().encode('Hello, World!'), - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, payer, data: getUtf8Encoder().encode('Hello, World!') }) + .sendTransaction(); // And we set a different authority on the buffer account. const bufferAuthority = await generateKeyPairSigner(); - const setAuthorityIx = getSetAuthorityInstruction({ - account: buffer.address, - authority: buffer, - newAuthority: bufferAuthority.address, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(setAuthorityIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .setAuthority({ account: buffer.address, authority: buffer, newAuthority: bufferAuthority.address }) + .sendTransaction(); // When the buffer keypair tries to close its own account. - const closeIx = getCloseInstruction({ - account: buffer.address, - authority: buffer, - destination: payer.address, - }); - const promise = pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + const promise = client.programMetadata.instructions + .close({ account: buffer.address, authority: buffer, destination: payer.address }) + .sendTransaction(); // Then we expect a program error. const error = await t.throwsAsync(promise); @@ -317,41 +266,27 @@ test('it cannot close a keypair buffer with a different authority set using the test('it can close a keypair buffer with its authority', async t => { // Given the following payer. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const payer = await generateKeyPairSignerWithSol(client); // And the following pre-allocated keypair buffer. - const buffer = await createKeypairBuffer(client, { - payer, - data: getUtf8Encoder().encode('Hello, World!'), - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, payer, data: getUtf8Encoder().encode('Hello, World!') }) + .sendTransaction(); // And we set a different authority on the buffer account. const bufferAuthority = await generateKeyPairSigner(); - const setAuthorityIx = getSetAuthorityInstruction({ - account: buffer.address, - authority: buffer, - newAuthority: bufferAuthority.address, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(setAuthorityIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .setAuthority({ account: buffer.address, authority: buffer, newAuthority: bufferAuthority.address }) + .sendTransaction(); // When the authority closes the buffer account. - const closeIx = getCloseInstruction({ - account: buffer.address, - authority: bufferAuthority, - destination: payer.address, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(closeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .close({ account: buffer.address, authority: bufferAuthority, destination: payer.address }) + .sendTransaction(); // Then we expect the buffer account to no longer exist. - const account = await fetchMaybeMetadata(client.rpc, buffer.address); + const account = await client.programMetadata.accounts.buffer.fetchMaybe(buffer.address); t.false(account.exists); }); diff --git a/clients/js/test/createMetadata.test.ts b/clients/js/test/createMetadata.test.ts index a0bfc1d..dca9a08 100644 --- a/clients/js/test/createMetadata.test.ts +++ b/clients/js/test/createMetadata.test.ts @@ -1,37 +1,29 @@ -import { address, getUtf8Encoder, none, some } from '@solana/kit'; +import { address, generateKeyPairSigner, getUtf8Encoder, none, some } from '@solana/kit'; import test from 'ava'; import { AccountDiscriminator, Compression, - createMetadata, DataSource, Encoding, - fetchMaybeBuffer, - fetchMetadata, + findCanonicalPda, + findNonCanonicalPda, Format, Metadata, } from '../src'; -import { - createDefaultSolanaClient, - createDeployedProgram, - createKeypairBuffer, - generateKeyPairSignerWithSol, - setAuthority, -} from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it creates a canonical metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // When we create a canonical metadata account for the program. const data = getUtf8Encoder().encode('{"standard":"dummyIdl"}'); - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -41,7 +33,8 @@ test('it creates a canonical metadata account', async t => { }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -60,17 +53,16 @@ test('it creates a canonical metadata account', async t => { test('it creates a canonical metadata account with data larger than a transaction size', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // When we create a canonical metadata account for the program with a lot of data. const largeData = getUtf8Encoder().encode('x'.repeat(3_000)); - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -80,7 +72,8 @@ test('it creates a canonical metadata account with data larger than a transactio }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -99,20 +92,22 @@ test('it creates a canonical metadata account with data larger than a transactio test('it creates a canonical metadata account using an existing buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And an existing buffer with the following data. const data = getUtf8Encoder().encode('{"standard":"dummyIdl"}'); - const buffer = await createKeypairBuffer(client, { payer: authority, data }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data }) + .sendTransaction(); // When we create a canonical metadata account using the existing buffer. - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -122,7 +117,8 @@ test('it creates a canonical metadata account using an existing buffer', async t }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -141,15 +137,13 @@ test('it creates a canonical metadata account using an existing buffer', async t test('it creates a non-canonical metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // When we create a non-canonical metadata account for the program. const data = getUtf8Encoder().encode('{"standard":"dummyIdl"}'); - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -161,7 +155,8 @@ test('it creates a non-canonical metadata account', async t => { }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -180,15 +175,13 @@ test('it creates a non-canonical metadata account', async t => { test('it creates a non-canonical metadata account with data larger than a transaction size', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // When we create a non-canonical metadata account for the program with a lot of data. const largeData = getUtf8Encoder().encode('x'.repeat(3_000)); - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -200,7 +193,8 @@ test('it creates a non-canonical metadata account with data larger than a transa }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -219,18 +213,19 @@ test('it creates a non-canonical metadata account with data larger than a transa test('it creates a non-canonical metadata account using an existing buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And an existing buffer with the following data. const data = getUtf8Encoder().encode('{"standard":"dummyIdl"}'); - const buffer = await createKeypairBuffer(client, { payer: authority, data }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data }) + .sendTransaction(); // When we create a non-canonical metadata account using the existing buffer. - const { metadata } = await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -242,7 +237,8 @@ test('it creates a non-canonical metadata account using an existing buffer', asy }); // Then we expect the following metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -261,14 +257,12 @@ test('it creates a non-canonical metadata account using an existing buffer', asy test('it cannot create a metadata account if no data or buffer is provided', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // When we try to create a metadata account without providing data or buffer. - const promise = createMetadata({ - ...client, - payer: authority, + const promise = client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -286,27 +280,21 @@ test('it cannot create a metadata account if no data or buffer is provided', asy test('it can close an existing buffer after using it to create a new metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And an existing buffer with the same authority. const data = getUtf8Encoder().encode('{"standard":"dummyIdl"}'); - const buffer = await createKeypairBuffer(client, { payer: authority, data }); - await setAuthority(client, { - payer: authority, - authority: buffer, - account: buffer.address, - newAuthority: authority.address, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions.createBuffer({ newBuffer: buffer, authority, data }).sendTransaction(); // When we create a canonical metadata account // using the existing buffer and the `closeBuffer` option. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -317,6 +305,6 @@ test('it can close an existing buffer after using it to create a new metadata ac }); // Then we expect the buffer account to no longer exist. - const bufferAccount = await fetchMaybeBuffer(client.rpc, buffer.address); + const bufferAccount = await client.programMetadata.accounts.buffer.fetchMaybe(buffer.address); t.false(bufferAccount.exists); }); diff --git a/clients/js/test/extend.test.ts b/clients/js/test/extend.test.ts index 630ce83..4fd6292 100644 --- a/clients/js/test/extend.test.ts +++ b/clients/js/test/extend.test.ts @@ -1,61 +1,53 @@ -import { getTransferSolInstruction } from '@solana-program/system'; -import { - address, - appendTransactionMessageInstructions, - assertAccountExists, - fetchEncodedAccount, - generateKeyPairSigner, - getUtf8Encoder, - pipe, -} from '@solana/kit'; +import { address, assertAccountExists, fetchEncodedAccount, generateKeyPairSigner, getUtf8Encoder } from '@solana/kit'; import test from 'ava'; -import { ACCOUNT_HEADER_LENGTH, getExtendInstruction, getSetAuthorityInstruction } from '../src'; import { - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - getRentWithoutHeader, - signAndSendTransaction, -} from './_setup'; + ACCOUNT_HEADER_LENGTH, + Compression, + DataSource, + Encoding, + findCanonicalPda, + findNonCanonicalPda, + Format, +} from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('the program authority of a canonical metadata account can extend it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, - data, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // When we extend the metadata account by 100 bytes. - const extraRent = await getRentWithoutHeader(client, 100); - const transferIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - const extendIx = getExtendInstruction({ - account: metadata, - authority, - length: 100, - program, - programData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, extendIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(100, { withoutHeader: true }); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.extend({ + account: metadata, + authority, + length: 100, + program, + programData, + }), + ]); // Then we expect the metadata account to be extended. const metadataAccount = await fetchEncodedAccount(client.rpc, metadata); @@ -65,7 +57,7 @@ test('the program authority of a canonical metadata account can extend it', asyn test('the explicit authority of a canonical metadata account can extend it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [programAuthority, authority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -74,40 +66,42 @@ test('the explicit authority of a canonical metadata account can extend it', asy // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ + payer: programAuthority, authority: programAuthority, - data, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // And an explicit authority is set for the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority: programAuthority, - newAuthority: authority.address, - program, - programData, - }); - - // When we extend the metadata account by 100 bytes. - const extraRent = await getRentWithoutHeader(client, 100); - const transferIx = getTransferSolInstruction({ - source: programAuthority, - destination: metadata, - amount: extraRent, - }); - const extendIx = getExtendInstruction({ - account: metadata, - authority, - length: 100, - }); - await pipe( - await createDefaultTransaction(client, programAuthority), - tx => appendTransactionMessageInstructions([setAuthorityIx, transferIx, extendIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When we extend the metadata account by 100 bytes + // after setting an explicit authority for the metadata account. + const extraRent = await client.getMinimumBalance(100, { withoutHeader: true }); + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority: programAuthority, + newAuthority: authority.address, + program, + programData, + }), + client.system.instructions.transferSol({ + source: programAuthority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.extend({ + account: metadata, + authority, + length: 100, + }), + ]); // Then we expect the metadata account to be extended. const metadataAccount = await fetchEncodedAccount(client.rpc, metadata); @@ -117,36 +111,38 @@ test('the explicit authority of a canonical metadata account can extend it', asy test('the metadata authority of a non-canonical metadata account can extend it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, - data, program, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data, }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When we extend the metadata account by 100 bytes. - const extraRent = await getRentWithoutHeader(client, 100); - const transferIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - const extendIx = getExtendInstruction({ - account: metadata, - authority, - length: 100, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, extendIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(100, { withoutHeader: true }); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.extend({ + account: metadata, + authority, + length: 100, + }), + ]); // Then we expect the metadata account to be extended. const metadataAccount = await fetchEncodedAccount(client.rpc, metadata); diff --git a/clients/js/test/fetchMetadataContent.test.ts b/clients/js/test/fetchMetadataContent.test.ts index 4c276e7..86bc59f 100644 --- a/clients/js/test/fetchMetadataContent.test.ts +++ b/clients/js/test/fetchMetadataContent.test.ts @@ -8,24 +8,22 @@ import { Format, packDirectData, packExternalData, - writeMetadata, } from '../src'; -import { createBuffer, createDefaultSolanaClient, createDeployedProgram, generateKeyPairSignerWithSol } from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it fetches and parses direct IDLs from canonical metadata accounts', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And given the following IDL exists for the program. const idl = '{"kind":"rootNode","standard":"codama","version":"1.0.0"}'; - await writeMetadata({ - ...client, + await client.programMetadata.writeMetadata({ ...packDirectData({ content: idl }), - payer: authority, authority, program, + programData, seed: 'idl', format: Format.Json, }); @@ -43,16 +41,14 @@ test('it fetches and parses direct IDLs from canonical metadata accounts', async test('it fetches and parses direct IDLs from non-canonical metadata accounts', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And given the following IDL exists for the program. const idl = '{"kind":"rootNode","standard":"codama","version":"1.0.0"}'; - await writeMetadata({ - ...client, + await client.programMetadata.writeMetadata({ ...packDirectData({ content: idl }), - payer: authority, authority, program, seed: 'idl', @@ -72,9 +68,8 @@ test('it fetches and parses direct IDLs from non-canonical metadata accounts', a test('it fetches and parses multiple direct IDLs from metadata accounts', async t => { t.timeout(30_000); - // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); - const authority = await generateKeyPairSignerWithSol(client); + // Given the following deployed program. + const client = await createTestClient(); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const metadata1 = await generateKeyPairSigner(); @@ -83,29 +78,23 @@ test('it fetches and parses multiple direct IDLs from metadata accounts', async // And given the following IDLs exist for the programs. const idl1 = '{"kind":"rootNode","standard":"codama","version":"1.0.0"}'; const idl2 = '{"kind":"rootNode","standard":"codama","version":"1.0.1"}'; - const buffer = await generateKeyPairSigner(); // We create a buffer account to hold the IDL data - await createBuffer(client, { - buffer: buffer.address, - authority: buffer, - payer: authority, - data: getUtf8Encoder().encode(idl2), - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: getUtf8Encoder().encode(idl2) }) + .sendTransaction(); // And we create metadata accounts for direct and external data await Promise.all([ - writeMetadata({ - ...client, + client.programMetadata.writeMetadata({ ...packDirectData({ content: idl1 }), - payer: authority, authority: metadata1, program, seed: 'idl', format: Format.Json, }), - writeMetadata({ - ...client, + client.programMetadata.writeMetadata({ ...packExternalData({ address: buffer.address, offset: 96, @@ -113,7 +102,6 @@ test('it fetches and parses multiple direct IDLs from metadata accounts', async compression: Compression.None, encoding: Encoding.Utf8, }), - payer: authority, authority: metadata2, program, seed: 'idl', diff --git a/clients/js/test/initialize.test.ts b/clients/js/test/initialize.test.ts index 03578c5..fe493ab 100644 --- a/clients/js/test/initialize.test.ts +++ b/clients/js/test/initialize.test.ts @@ -1,87 +1,58 @@ -import { getTransferSolInstruction } from '@solana-program/system'; -import { - address, - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, - getUtf8Encoder, - none, - pipe, - some, -} from '@solana/kit'; +import { address, getUtf8Encoder, none, some } from '@solana/kit'; import test from 'ava'; import { + ACCOUNT_HEADER_LENGTH, AccountDiscriminator, Compression, DataSource, Encoding, - fetchMetadata, findCanonicalPda, findNonCanonicalPda, Format, getExternalDataEncoder, - getInitializeInstruction, - getInitializeInstructionAsync, Metadata, } from '../src'; -import { - createCanonicalBuffer, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createNonCanonicalBuffer, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it initializes a non canonical PDA with direct data from instruction data', async t => { // Given the following authority and program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); - // And the following metadata seed and data. + // And the following metadata seed, data and address. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - - // And given the metadata account is pre-funded. - const [metadata] = await findNonCanonicalPda({ - authority: authority.address, - program, - seed, - }); - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n + BigInt(data.length)).send(); - const preFund = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: rent, - }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed }); // When we initialize the metadata account with the data on the instruction. - const initialize = await getInitializeInstructionAsync({ - authority, - program, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Direct, - data, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFund, initialize], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH + data.length), + }), + await client.programMetadata.instructions.initialize({ + authority, + program, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data, + }), + ]); // Then we expect the following metadata account to be created. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, authority: some(authority.address), mutable: true, canonical: false, - seed: 'dummy', + seed, encoding: Encoding.Utf8, compression: Compression.None, format: Format.None, @@ -93,46 +64,36 @@ test('it initializes a non canonical PDA with direct data from instruction data' test('it initializes a non canonical PDA with url data from instruction data', async t => { // Given the following authority and program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); - // And the following metadata seed and URL. + // And the following metadata seed, URL and address. const seed = 'dummy'; const data = getUtf8Encoder().encode('https://example.com/my-metadata.json'); - - // And given the metadata account is pre-funded. - const [metadata] = await findNonCanonicalPda({ - authority: authority.address, - program, - seed, - }); - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n + BigInt(data.length)).send(); - const preFund = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: rent, - }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed }); // When we initialize the metadata account with the URL on the instruction. - const initialize = await getInitializeInstructionAsync({ - authority, - program, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Url, - data, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFund, initialize], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH + data.length), + }), + await client.programMetadata.instructions.initialize({ + authority, + program, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Url, + data, + }), + ]); // Then we expect a URL metadata account to be created. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -151,50 +112,41 @@ test('it initializes a non canonical PDA with url data from instruction data', a test('it initializes a non canonical PDA with external data from instruction data', async t => { // Given the following authority and program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); - // And the following metadata seed and external data. + // And the following metadata seed, external data and address. const seed = 'dummy'; + const dataAddress = address('7yokRnUjUPbpiLfyuA9NHwyQk66fYRW8mkrU5LPK3rnV'); const data = getExternalDataEncoder().encode({ - address: address('7yokRnUjUPbpiLfyuA9NHwyQk66fYRW8mkrU5LPK3rnV'), + address: dataAddress, offset: 32, - length: some(96), - }); - - // And given the metadata account is pre-funded. - const [metadata] = await findNonCanonicalPda({ - authority: authority.address, - program, - seed, - }); - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n + BigInt(data.length)).send(); - const preFund = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: rent, + length: some(ACCOUNT_HEADER_LENGTH), }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed }); // When we initialize the metadata account with the external data on the instruction. - const initialize = await getInitializeInstructionAsync({ - authority, - program, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.External, - data, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFund, initialize], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH + data.length), + }), + await client.programMetadata.instructions.initialize({ + authority, + program, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.External, + data, + }), + ]); // Then we expect an external metadata account to be created. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -213,43 +165,37 @@ test('it initializes a non canonical PDA with external data from instruction dat test('it initializes a canonical PDA from instruction data', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); - // And the following metadata seed and data. + // And the following metadata seed, data and address. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - - // And given the metadata account is pre-funded. const [metadata] = await findCanonicalPda({ program, seed }); - const rent = await client.rpc.getMinimumBalanceForRentExemption(96n + BigInt(data.length)).send(); - const preFund = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: rent, - }); // When we initialize the metadata account with the data on the instruction. - const initialize = await getInitializeInstructionAsync({ - authority, - program, - programData, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Direct, - data, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([preFund, initialize], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH + data.length), + }), + await client.programMetadata.instructions.initialize({ + authority, + program, + programData, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data, + }), + ]); // Then we expect the following metadata account to be created. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -268,41 +214,35 @@ test('it initializes a canonical PDA from instruction data', async t => { test('it initializes a canonical PDA from a pre-allocated buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following pre-allocated buffer account. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - const [metadata] = await createCanonicalBuffer(client, { - authority, - program, - programData, - seed, - data, - }); + await client.programMetadata.instructions + .createCanonicalBuffer({ authority, program, programData, seed, data }) + .sendTransaction(); + const [metadata] = await findCanonicalPda({ program, seed }); // When we initialize the metadata account from the pre-allocated buffer at the same address. - const initialize = getInitializeInstruction({ - metadata, - authority, - program, - programData, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Direct, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(initialize, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .initialize({ + metadata, + authority, + program, + programData, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + }) + .sendTransaction(); // Then we expect the buffer account to now be a metadata account. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -321,39 +261,34 @@ test('it initializes a canonical PDA from a pre-allocated buffer', async t => { test('it initializes a non-canonical PDA from a pre-allocated buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following pre-allocated buffer account. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - const [metadata] = await createNonCanonicalBuffer(client, { - authority, - program, - seed, - data, - }); + await client.programMetadata.instructions + .createNonCanonicalBuffer({ authority, program, seed, data }) + .sendTransaction(); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed }); // When we initialize the metadata account from the pre-allocated buffer at the same address. - const initialize = getInitializeInstruction({ - metadata, - authority, - program, - seed, - encoding: Encoding.Utf8, - compression: Compression.None, - format: Format.None, - dataSource: DataSource.Direct, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(initialize, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .initialize({ + metadata, + authority, + program, + seed, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + }) + .sendTransaction(); // Then we expect the buffer account to now be a metadata account. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, program, diff --git a/clients/js/test/loader-v3/shared.ts b/clients/js/test/loader-v3/shared.ts index cfc5a1e..1df89e4 100644 --- a/clients/js/test/loader-v3/shared.ts +++ b/clients/js/test/loader-v3/shared.ts @@ -35,7 +35,7 @@ export function expectAddress( return value.address; } if (Array.isArray(value)) { - return value[0]; + return value[0] as Address; } return value as Address; } diff --git a/clients/js/test/setAuthority.test.ts b/clients/js/test/setAuthority.test.ts index dee502f..24f9c5d 100644 --- a/clients/js/test/setAuthority.test.ts +++ b/clients/js/test/setAuthority.test.ts @@ -1,11 +1,10 @@ import { - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, generateKeyPairSigner, getUtf8Encoder, isSolanaError, none, - pipe, + SingleTransactionPlanResult, + SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION, SOLANA_ERROR__INSTRUCTION_ERROR__INVALID_ACCOUNT_DATA, SOLANA_ERROR__INSTRUCTION_ERROR__INVALID_ARGUMENT, some, @@ -13,28 +12,20 @@ import { import test from 'ava'; import { ACCOUNT_HEADER_LENGTH, - fetchBuffer, - fetchMetadata, - getAllocateInstruction, - getSetAuthorityInstruction, - getSetImmutableInstruction, + Compression, + DataSource, + Encoding, + findCanonicalPda, + findNonCanonicalPda, + Format, isProgramMetadataError, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT, } from '../src'; -import { - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; -import { getTransferSolInstruction } from '@solana-program/system'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('the program authority can set another authority on canonical metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, newAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -42,36 +33,32 @@ test('the program authority can set another authority on canonical metadata acco const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // When the program authority sets a new authority on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: newAuthority.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(setAuthorityIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .setAuthority({ account: metadata, authority, program, programData, newAuthority: newAuthority.address }) + .sendTransaction(); // Then we expect the metadata account to record the new authority. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.authority, some(newAuthority.address)); }); test('the program authority can update an existing authority on canonical metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthorityA, explicitAuthorityB] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -80,45 +67,46 @@ test('the program authority can update an existing authority on canonical metada const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority A is set on the metadata account. - const firstSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthorityA.address, - }); - - // When the program authority sets another authority B on the metadata account. - const secondSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthorityB.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([firstSetAuthorityIx, secondSetAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority sets explicit authority A + // and then replaces it with explicit authority B. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthorityA.address, + }), + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthorityB.address, + }), + ]); // Then we expect the metadata account to record the latest explicit authority. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.authority, some(explicitAuthorityB.address)); }); test('the program authority can remove an existing authority on canonical metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -126,45 +114,46 @@ test('the program authority can remove an existing authority on canonical metada const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority is set on the metadata account. - const firstSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // When the program authority removes the explicit authority from the metadata account. - const secondSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: null, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([firstSetAuthorityIx, secondSetAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority sets an explicit authority + // and then removes it from the metadata account. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: null, + }), + ]); // Then we expect the metadata account to have no explicit authority. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.authority, none()); }); test('an explicitly set authority can update itself on canonical metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthorityA, explicitAuthorityB] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -173,45 +162,46 @@ test('an explicitly set authority can update itself on canonical metadata accoun const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority A is set on the metadata account. - const firstSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthorityA.address, - }); - - // When the explicit authority A sets another authority B on the metadata account. - const secondSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority: explicitAuthorityA, - program, - programData, - newAuthority: explicitAuthorityB.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([firstSetAuthorityIx, secondSetAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority sets explicit authority A, + // and then explicit authority A sets explicit authority B. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthorityA.address, + }), + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority: explicitAuthorityA, + program, + programData, + newAuthority: explicitAuthorityB.address, + }), + ]); // Then we expect the metadata account to record the latest explicit authority. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.authority, some(explicitAuthorityB.address)); }); test('an explicitly set authority can remove itself on canonical metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -219,45 +209,46 @@ test('an explicitly set authority can remove itself on canonical metadata accoun const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); - - // And given an explicit authority is set on the metadata account. - const firstSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // When the explicit authority removes itself from the metadata account. - const secondSetAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority: explicitAuthority, - program, - programData, - newAuthority: null, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([firstSetAuthorityIx, secondSetAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority sets an explicit authority, + // and then that explicit authority removes itself. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority: explicitAuthority, + program, + programData, + newAuthority: null, + }), + ]); // Then we expect the metadata account to have no explicit authority. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.authority, none()); }); test('the authority of a non-canonical metadata account cannot set another authority on the account', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, newAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -265,26 +256,22 @@ test('the authority of a non-canonical metadata account cannot set another autho const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized non-canonical metadata account. - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When the authority attempts to set a new authority on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: newAuthority.address, - }); - const promise = pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(setAuthorityIx, tx), - tx => signAndSendTransaction(client, tx), - ); + const promise = client.programMetadata.instructions + .setAuthority({ account: metadata, authority, program, programData, newAuthority: newAuthority.address }) + .sendTransaction(); // Then we expect the transaction to fail. const error = await t.throwsAsync(promise); @@ -293,31 +280,27 @@ test('the authority of a non-canonical metadata account cannot set another autho test('the authority of a non-canonical metadata account cannot remove itself on the account', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); - const [authority] = await Promise.all([generateKeyPairSignerWithSol(client)]); + const client = await createTestClient(); + const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized non-canonical metadata account. - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When the authority attempts to remove itself from the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: null, - }); - const promise = pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(setAuthorityIx, tx), - tx => signAndSendTransaction(client, tx), - ); + const promise = client.programMetadata.instructions + .setAuthority({ account: metadata, authority, program, programData, newAuthority: null }) + .sendTransaction(); // Then we expect the transaction to fail. const error = await t.throwsAsync(promise); @@ -326,70 +309,60 @@ test('the authority of a non-canonical metadata account cannot remove itself on test('the authority can update itself on buffer accounts', async t => { // Given the following buffer and authorities. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [payer, buffer, newAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), generateKeyPairSigner(), ]); - // And the following initialized buffer account. - const bufferRent = await client.rpc.getMinimumBalanceForRentExemption(BigInt(ACCOUNT_HEADER_LENGTH)).send(); - const fundBufferIx = getTransferSolInstruction({ - source: payer, - destination: buffer.address, - amount: bufferRent, - }); - const allocateBufferIx = getAllocateInstruction({ - buffer: buffer.address, - authority: buffer, - }); - - // When the current authority sets another authority on the buffer account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: buffer.address, - authority: buffer, - newAuthority: newAuthority.address, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions([fundBufferIx, allocateBufferIx, setAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When we fund, allocate, and update the authority of the buffer account. + const bufferRent = await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: payer, + destination: buffer.address, + amount: bufferRent, + }), + client.programMetadata.instructions.allocate({ + buffer: buffer.address, + authority: buffer, + }), + client.programMetadata.instructions.setAuthority({ + account: buffer.address, + authority: buffer, + newAuthority: newAuthority.address, + }), + ]); // Then we expect the buffer account to record the new authority. - const account = await fetchBuffer(client.rpc, buffer.address); + const account = await client.programMetadata.accounts.buffer.fetch(buffer.address); t.deepEqual(account.data.authority, some(newAuthority.address)); }); test('the authority cannot remove itself on buffer accounts', async t => { // Given the following buffer and authorities. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [payer, buffer] = await Promise.all([generateKeyPairSignerWithSol(client), generateKeyPairSigner()]); - // And the following initialized buffer account. - const bufferRent = await client.rpc.getMinimumBalanceForRentExemption(BigInt(ACCOUNT_HEADER_LENGTH)).send(); - const fundBufferIx = getTransferSolInstruction({ - source: payer, - destination: buffer.address, - amount: bufferRent, - }); - const allocateBufferIx = getAllocateInstruction({ - buffer: buffer.address, - authority: buffer, - }); - - // When the current authority removes itself from the buffer account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: buffer.address, - authority: buffer, - newAuthority: null, - }); - const promise = pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions([fundBufferIx, allocateBufferIx, setAuthorityIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When we fund, allocate, and attempt to remove the authority of the buffer account. + const bufferRent = await client.getMinimumBalance(ACCOUNT_HEADER_LENGTH); + const promise = client.sendTransaction([ + client.system.instructions.transferSol({ + source: payer, + destination: buffer.address, + amount: bufferRent, + }), + client.programMetadata.instructions.allocate({ + buffer: buffer.address, + authority: buffer, + }), + client.programMetadata.instructions.setAuthority({ + account: buffer.address, + authority: buffer, + newAuthority: null, + }), + ]); // Then we expect the transaction to fail. const error = await t.throwsAsync(promise); @@ -398,7 +371,7 @@ test('the authority cannot remove itself on buffer accounts', async t => { test('the authority cannot be changed on immutable metadata accounts', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority, anotherAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -407,46 +380,51 @@ test('the authority cannot be changed on immutable metadata accounts', async t = const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority sets the explicit authority, the explicit + // authority makes the metadata account immutable, then the program + // authority tries to set yet another authority — all in one transaction. + const promise = client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.programMetadata.instructions.setImmutable({ + metadata, + authority: explicitAuthority, + program, + programData, + }), + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: anotherAuthority.address, + }), + ]); - // And given the explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // And the explicit authority sets the metadata account to be immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority: explicitAuthority, - program, - programData, - }); - - // When the explicit authority attempts to set another authority on the - // metadata account after setting it to be immutable. - const setAnotherAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: anotherAuthority.address, - }); - const transactionMessage = pipe(await createDefaultTransaction(client, authority), tx => - appendTransactionMessageInstructions([setAuthorityIx, setImmutableIx, setAnotherAuthorityIx], tx), - ); - const promise = signAndSendTransaction(client, transactionMessage); - - // Then we expect the transaction to fail. + // Then we expect the transaction to fail with the IMMUTABLE_METADATA_ACCOUNT program error. const error = await t.throwsAsync(promise); - t.true(isProgramMetadataError(error.cause, transactionMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT)); + t.true(isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)); + if (!isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)) return; + const result = error.context.transactionPlanResult as SingleTransactionPlanResult; + t.true( + isProgramMetadataError(error.cause, result.plannedMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT), + ); }); diff --git a/clients/js/test/setData.test.ts b/clients/js/test/setData.test.ts index 2e09103..9d1725f 100644 --- a/clients/js/test/setData.test.ts +++ b/clients/js/test/setData.test.ts @@ -1,11 +1,10 @@ -import { getTransferSolInstruction } from '@solana-program/system'; import { address, - appendTransactionMessageInstructions, generateKeyPairSigner, getUtf8Encoder, isSolanaError, - pipe, + SingleTransactionPlanResult, + SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION, SOLANA_ERROR__INSTRUCTION_ERROR__INVALID_REALLOC, } from '@solana/kit'; import test from 'ava'; @@ -13,38 +12,24 @@ import { Compression, DataSource, Encoding, - fetchMetadata, + findCanonicalPda, + findNonCanonicalPda, Format, - getExtendInstruction, - getSetAuthorityInstruction, - getSetDataInstruction, - getSetImmutableInstruction, isProgramMetadataError, Metadata, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT, } from '../src'; -import { - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createKeypairBuffer, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - getRentWithoutHeader, - REALLOC_LIMIT, - signAndSendTransaction, -} from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol, REALLOC_LIMIT } from './_setup'; test('the program authority of a canonical metadata account can update its data using instruction data', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, @@ -55,36 +40,32 @@ test('the program authority of a canonical metadata account can update its data dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // And given we fund the metadata account for the extra space needed for the new data. + // When the program authority funds and updates the data of the metadata account using instruction data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const transferIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - - // When the program authority updates the data of the metadata account using instruction data. - const setDataIx = getSetDataInstruction({ - metadata, - authority, - program, - programData, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - data: newData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.setData({ + metadata, + authority, + program, + programData, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + data: newData, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -96,7 +77,7 @@ test('the program authority of a canonical metadata account can update its data test('the explicit authority of a canonical metadata account can update its data using instruction data', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -105,7 +86,7 @@ test('the explicit authority of a canonical metadata account can update its data // And the following initialized canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, @@ -116,45 +97,40 @@ test('the explicit authority of a canonical metadata account can update its data dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // And given an explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // And given we fund the metadata account for the extra space needed for the new data. + // When the program authority sets an explicit authority, + // funds the metadata account and that explicit authority updates the data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const transferIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - - // When the explicit authority updates the data of the metadata account using instruction data. - const setDataIx = getSetDataInstruction({ - metadata, - authority: explicitAuthority, - program, - programData, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - data: newData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, transferIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.setData({ + metadata, + authority: explicitAuthority, + program, + programData, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + data: newData, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -166,13 +142,13 @@ test('the explicit authority of a canonical metadata account can update its data test('the authority of a non-canonical metadata account can update its data using instruction data', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized non-canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', @@ -182,35 +158,31 @@ test('the authority of a non-canonical metadata account can update its data usin dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); - // And given we fund the metadata account for the extra space needed for the new data. + // When the metadata authority funds and updates the account data using instruction data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const transferIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - - // When the metadata authority updates the account data using instruction data. - const setDataIx = getSetDataInstruction({ - metadata, - authority, - program, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - data: newData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.setData({ + metadata, + authority, + program, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + data: newData, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -222,13 +194,13 @@ test('the authority of a non-canonical metadata account can update its data usin test('the program authority of a canonical metadata account can update its data using a pre-allocated buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, @@ -239,40 +211,38 @@ test('the program authority of a canonical metadata account can update its data dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // And the following pre-allocated buffer account with written data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransaction(); // When the program authority updates the data of the metadata account using the buffer. - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const fundMetadataIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - const setDataIx = getSetDataInstruction({ - metadata, - authority, - buffer: buffer.address, - program, - programData, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setDataIx, fundMetadataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.programMetadata.instructions.setData({ + metadata, + authority, + buffer: buffer.address, + program, + programData, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + }), + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -284,7 +254,7 @@ test('the program authority of a canonical metadata account can update its data test('the explicit authority of a canonical metadata account can update its data using a pre-allocated buffer', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -293,7 +263,7 @@ test('the explicit authority of a canonical metadata account can update its data // And the following initialized canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, @@ -304,49 +274,46 @@ test('the explicit authority of a canonical metadata account can update its data dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // And the following pre-allocated buffer account with written data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); - - // And given an explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // When the explicit authority updates the data of the metadata account using the buffer. - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const fundMetadataIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - const setDataIx = getSetDataInstruction({ - metadata, - authority: explicitAuthority, - buffer: buffer.address, - program, - programData, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, fundMetadataIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransaction(); + + // When the program authority sets an explicit authority, + // funds the metadata account and that explicit authority updates the data using the buffer. + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.setData({ + metadata, + authority: explicitAuthority, + buffer: buffer.address, + program, + programData, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -358,13 +325,13 @@ test('the explicit authority of a canonical metadata account can update its data test('the authority of a non-canonical metadata account can update its data using a pre-allocated buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized non-canonical metadata account. const originalData = getUtf8Encoder().encode('Original data'); - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', @@ -374,39 +341,37 @@ test('the authority of a non-canonical metadata account can update its data usin dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // And the following pre-allocated buffer account with written data. const newData = getUtf8Encoder().encode('https://example.com/new-data.json'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); - - // When the metadata authority updates the account using the pre-allocated buffer. - const extraRent = await getRentWithoutHeader(client, newData.length - originalData.length); - const fundMetadataIx = getTransferSolInstruction({ - source: authority, - destination: metadata, - amount: extraRent, - }); - const setDataIx = getSetDataInstruction({ - metadata, - authority, - buffer: buffer.address, - program, - encoding: Encoding.Utf8, - compression: Compression.Gzip, - format: Format.Json, - dataSource: DataSource.Url, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([fundMetadataIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransaction(); + + // When the metadata authority funds and updates the account using the pre-allocated buffer. + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + await client.sendTransaction([ + client.system.instructions.transferSol({ + source: authority, + destination: metadata, + amount: extraRent, + }), + client.programMetadata.instructions.setData({ + metadata, + authority, + buffer: buffer.address, + program, + encoding: Encoding.Utf8, + compression: Compression.Gzip, + format: Format.Json, + dataSource: DataSource.Url, + }), + ]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, @@ -418,100 +383,114 @@ test('the authority of a non-canonical metadata account can update its data usin test('an immutable canonical metadata account cannot be updated', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', - data: getUtf8Encoder().encode('Original data'), - }); - - // And given the metadata account is immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority, - program, - programData, - }); - - // When the program authority tries to update the data of the metadata account. - const setDataIx = getSetDataInstruction({ - metadata, - authority, - program, - programData, - encoding: Encoding.Utf8, + encoding: Encoding.None, compression: Compression.None, - format: Format.Json, + format: Format.None, dataSource: DataSource.Direct, - data: getUtf8Encoder().encode('New data'), + data: getUtf8Encoder().encode('Original data'), }); - const transactionMessage = pipe(await createDefaultTransaction(client, authority), tx => - appendTransactionMessageInstructions([setImmutableIx, setDataIx], tx), - ); - const promise = signAndSendTransaction(client, transactionMessage); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); + + // When the program authority makes the metadata account immutable + // and tries to update its data in the same transaction. + const promise = client.sendTransaction([ + client.programMetadata.instructions.setImmutable({ + metadata, + authority, + program, + programData, + }), + client.programMetadata.instructions.setData({ + metadata, + authority, + program, + programData, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.Json, + dataSource: DataSource.Direct, + data: getUtf8Encoder().encode('New data'), + }), + ]); - // Then we expect the transaction to fail. + // Then we expect the transaction to fail with the IMMUTABLE_METADATA_ACCOUNT program error. const error = await t.throwsAsync(promise); - t.true(isProgramMetadataError(error.cause, transactionMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT)); + t.true(isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)); + if (!isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)) return; + const result = error.context.transactionPlanResult as SingleTransactionPlanResult; + t.true( + isProgramMetadataError(error.cause, result.plannedMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT), + ); }); test('an immutable non-canonical metadata account cannot be updated', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized non-canonical metadata account. - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', - data: getUtf8Encoder().encode('Original data'), - }); - - // And given the metadata account is immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority, - program, - }); - - // When the metadata authority tries to update the data. - const setDataIx = getSetDataInstruction({ - metadata, - authority, - program, - encoding: Encoding.Utf8, + encoding: Encoding.None, compression: Compression.None, - format: Format.Json, + format: Format.None, dataSource: DataSource.Direct, - data: getUtf8Encoder().encode('New data'), + data: getUtf8Encoder().encode('Original data'), }); - const transactionMessage = pipe(await createDefaultTransaction(client, authority), tx => - appendTransactionMessageInstructions([setImmutableIx, setDataIx], tx), - ); - const promise = signAndSendTransaction(client, transactionMessage); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); + + // When the metadata authority makes the metadata account immutable + // and tries to update its data in the same transaction. + const promise = client.sendTransaction([ + client.programMetadata.instructions.setImmutable({ + metadata, + authority, + program, + }), + client.programMetadata.instructions.setData({ + metadata, + authority, + program, + encoding: Encoding.Utf8, + compression: Compression.None, + format: Format.Json, + dataSource: DataSource.Direct, + data: getUtf8Encoder().encode('New data'), + }), + ]); - // Then we expect the transaction to fail. + // Then we expect the transaction to fail with the IMMUTABLE_METADATA_ACCOUNT program error. const error = await t.throwsAsync(promise); - t.true(isProgramMetadataError(error.cause, transactionMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT)); + t.true(isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)); + if (!isSolanaError(error, SOLANA_ERROR__FAILED_TO_SEND_TRANSACTION)) return; + const result = error.context.transactionPlanResult as SingleTransactionPlanResult; + t.true( + isProgramMetadataError(error.cause, result.plannedMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT), + ); }); test('The metadata account needs to be extended for data changes that add more than 1KB', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized metadata account with 200 bytes of data. const originalData = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', @@ -521,28 +500,28 @@ test('The metadata account needs to be extended for data changes that add more t dataSource: DataSource.Direct, data: originalData, }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // And the following pre-allocated buffer account with written data. const newData = getUtf8Encoder().encode('x'.repeat(originalData.length + REALLOC_LIMIT + 1)); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransactions(); // And given the following instructions to fund extra rent, extend extra space and update the data. - const extraSize = newData.length - originalData.length; - const extraRent = await getRentWithoutHeader(client, extraSize); - const transferIx = getTransferSolInstruction({ + const extraRent = await client.getMinimumBalance(newData.length - originalData.length, { withoutHeader: true }); + const transferIx = client.system.instructions.transferSol({ source: authority, destination: metadata, amount: extraRent, }); - const extendIx = getExtendInstruction({ + const extendIx = client.programMetadata.instructions.extend({ account: metadata, authority, length: REALLOC_LIMIT, }); - const setDataIx = getSetDataInstruction({ + const setDataIx = client.programMetadata.instructions.setData({ metadata, authority, program, @@ -554,11 +533,7 @@ test('The metadata account needs to be extended for data changes that add more t }); // When we try to update the data without extending the account. - const promise = pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + const promise = client.sendTransaction([transferIx, setDataIx]); // Then we expect a program error. const error = await t.throwsAsync(promise); @@ -566,14 +541,10 @@ test('The metadata account needs to be extended for data changes that add more t t.true(isSolanaError(error.cause, SOLANA_ERROR__INSTRUCTION_ERROR__INVALID_REALLOC)); // But when we extend the account and try again. - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([transferIx, extendIx, setDataIx], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([transferIx, extendIx, setDataIx]); // Then we expect the metadata account have the new data. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { encoding: Encoding.Utf8, compression: Compression.Gzip, diff --git a/clients/js/test/setImmutable.test.ts b/clients/js/test/setImmutable.test.ts index c6cccc4..14ad048 100644 --- a/clients/js/test/setImmutable.test.ts +++ b/clients/js/test/setImmutable.test.ts @@ -1,59 +1,41 @@ -import { - address, - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, - generateKeyPairSigner, - getUtf8Encoder, - pipe, -} from '@solana/kit'; +import { address, generateKeyPairSigner, getUtf8Encoder } from '@solana/kit'; import test from 'ava'; -import { fetchMetadata, getSetAuthorityInstruction, getSetImmutableInstruction } from '../src'; -import { - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { Compression, DataSource, Encoding, findCanonicalPda, findNonCanonicalPda, Format } from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('the program authority can of a canonical metadata account can make it immutable', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); // When the program authority sets the metadata account to be immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority, - program, - programData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(setImmutableIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .setImmutable({ metadata, authority, program, programData }) + .sendTransaction(); // Then we expect the metadata account to be immutable. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.mutable, false); }); test('the explicit authority of a canonical metadata account can make it immutable', async t => { // Given the following authorities and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const [authority, explicitAuthority] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -61,68 +43,65 @@ test('the explicit authority of a canonical metadata account can make it immutab const [program, programData] = await createDeployedProgram(client, authority); // And the following initialized canonical metadata account. - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, programData, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // And given the explicit authority is set on the metadata account. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - program, - programData, - newAuthority: explicitAuthority.address, - }); - - // When the explicit authority sets the metadata account to be immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority: explicitAuthority, - program, - programData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, setImmutableIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When the explicit authority is set on the metadata account + // and then sets the metadata account to be immutable. + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }), + client.programMetadata.instructions.setImmutable({ + metadata, + authority: explicitAuthority, + program, + programData, + }), + ]); // Then we expect the metadata account to be immutable. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.mutable, false); }); test('the authority of a non-canonical metadata account can make it immutable', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenKEGQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following initialized non-canonical metadata account. - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, program, seed: 'dummy', + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, data: getUtf8Encoder().encode('Hello, World!'), }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); // When the metadata authority sets the metadata account to be immutable. - const setImmutableIx = getSetImmutableInstruction({ - metadata, - authority, - program, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(setImmutableIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions.setImmutable({ metadata, authority, program }).sendTransaction(); // Then we expect the metadata account to be immutable. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.deepEqual(account.data.mutable, false); }); diff --git a/clients/js/test/trim.test.ts b/clients/js/test/trim.test.ts index 3e7eae9..ac729b9 100644 --- a/clients/js/test/trim.test.ts +++ b/clients/js/test/trim.test.ts @@ -1,40 +1,21 @@ -import { - address, - appendTransactionMessageInstructions, - generateKeyPairSigner, - getUtf8Encoder, - lamports, - pipe, -} from '@solana/kit'; +import { address, generateKeyPairSigner, getUtf8Encoder, lamports } from '@solana/kit'; import test from 'ava'; import { AccountDiscriminator, Compression, DataSource, Encoding, - fetchMetadata, + findCanonicalPda, + findNonCanonicalPda, Format, - getSetAuthorityInstruction, - getSetDataInstruction, - getTrimInstruction, Metadata, } from '../src'; -import { - createCanonicalMetadata, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createNonCanonicalMetadata, - generateKeyPairSignerWithSol, - getBalance, - getRentWithoutHeader, - signAndSendTransaction, -} from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol, getBalance } from './_setup'; test('the program authority of a canonical metadata account can trim it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); - const rentForAccountHeader = await client.rpc.getMinimumBalanceForRentExemption(0n).send(); + const client = await createTestClient(); + const rentForAccountHeader = await client.getMinimumBalance(0); const [authority, destination] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSignerWithSol(client, rentForAccountHeader), @@ -43,59 +24,59 @@ test('the program authority of a canonical metadata account can trim it', async // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, - data, program, programData, seed: 'dummy', - }); - - // And given we remove 100 bytes of data. - const reducedData = getUtf8Encoder().encode('x'.repeat(100)); - const reduceBytesIx = getSetDataInstruction({ - metadata, - authority, encoding: Encoding.None, compression: Compression.None, format: Format.None, dataSource: DataSource.Direct, - data: reducedData, - program, - programData, + data, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // When we trim the metadata account. - const trimIx = getTrimInstruction({ - account: metadata, - authority, - destination: destination.address, - program, - programData, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([reduceBytesIx, trimIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When we remove 100 bytes of data and trim the metadata account. + const reducedData = getUtf8Encoder().encode('x'.repeat(100)); + await client.sendTransaction([ + client.programMetadata.instructions.setData({ + metadata, + authority, + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data: reducedData, + program, + programData, + }), + client.programMetadata.instructions.trim({ + account: metadata, + authority, + destination: destination.address, + program, + programData, + }), + ]); // Then we expect the metadata account to be trimmed. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, data: reducedData, }); // And we expect the destination account to have the rent difference. - const rentDifference = await getRentWithoutHeader(client, 100); + const rentDifference = await client.getMinimumBalance(100, { withoutHeader: true }); const destinationBalance = await getBalance(client, destination.address); t.is(destinationBalance, lamports(rentForAccountHeader + rentDifference)); }); test('the explicit authority of a canonical metadata account can trim it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); - const rentForAccountHeader = await client.rpc.getMinimumBalanceForRentExemption(0n).send(); + const client = await createTestClient(); + const rentForAccountHeader = await client.getMinimumBalance(0); const [authority, explicitAuthority, destination] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), @@ -105,64 +86,62 @@ test('the explicit authority of a canonical metadata account can trim it', async // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, - data, program, programData, seed: 'dummy', - }); - - // And given an explicit authority is set. - const setAuthorityIx = getSetAuthorityInstruction({ - account: metadata, - authority, - newAuthority: explicitAuthority.address, - program, - programData, - }); - - // And given we remove 100 bytes of data. - const reducedData = getUtf8Encoder().encode('x'.repeat(100)); - const reduceBytesIx = getSetDataInstruction({ - metadata, - authority: explicitAuthority, encoding: Encoding.None, compression: Compression.None, format: Format.None, dataSource: DataSource.Direct, - data: reducedData, + data, }); + const [metadata] = await findCanonicalPda({ program, seed: 'dummy' }); - // When we trim the metadata account. - const trimIx = getTrimInstruction({ - account: metadata, - authority: explicitAuthority, - destination: destination.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, reduceBytesIx, trimIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When an explicit authority is set, removes 100 bytes of data, and trims the metadata account. + const reducedData = getUtf8Encoder().encode('x'.repeat(100)); + await client.sendTransaction([ + client.programMetadata.instructions.setAuthority({ + account: metadata, + authority, + newAuthority: explicitAuthority.address, + program, + programData, + }), + client.programMetadata.instructions.setData({ + metadata, + authority: explicitAuthority, + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data: reducedData, + }), + client.programMetadata.instructions.trim({ + account: metadata, + authority: explicitAuthority, + destination: destination.address, + }), + ]); // Then we expect the metadata account to be trimmed. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, data: reducedData, }); // And we expect the destination account to have the rent difference. - const rentDifference = await getRentWithoutHeader(client, 100); + const rentDifference = await client.getMinimumBalance(100, { withoutHeader: true }); const destinationBalance = await getBalance(client, destination.address); t.is(destinationBalance, lamports(rentForAccountHeader + rentDifference)); }); test('the metadata authority of a non-canonical metadata account can trim it', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); - const rentForAccountHeader = await client.rpc.getMinimumBalanceForRentExemption(0n).send(); + const client = await createTestClient(); + const rentForAccountHeader = await client.getMinimumBalance(0); const [authority, destination] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSignerWithSol(client, rentForAccountHeader), @@ -171,46 +150,46 @@ test('the metadata authority of a non-canonical metadata account can trim it', a // And the following metadata account with 200 bytes of data. const data = getUtf8Encoder().encode('x'.repeat(200)); - const [metadata] = await createNonCanonicalMetadata(client, { + await client.programMetadata.createMetadata({ authority, - data, program, seed: 'dummy', - }); - - // And given we remove 100 bytes of data. - const reducedData = getUtf8Encoder().encode('x'.repeat(100)); - const reduceBytesIx = getSetDataInstruction({ - metadata, - authority, encoding: Encoding.None, compression: Compression.None, format: Format.None, dataSource: DataSource.Direct, - data: reducedData, + data, }); + const [metadata] = await findNonCanonicalPda({ authority: authority.address, program, seed: 'dummy' }); - // When we trim the metadata account. - const trimIx = getTrimInstruction({ - account: metadata, - authority, - destination: destination.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([reduceBytesIx, trimIx], tx), - tx => signAndSendTransaction(client, tx), - ); + // When we remove 100 bytes of data and trim the metadata account. + const reducedData = getUtf8Encoder().encode('x'.repeat(100)); + await client.sendTransaction([ + client.programMetadata.instructions.setData({ + metadata, + authority, + encoding: Encoding.None, + compression: Compression.None, + format: Format.None, + dataSource: DataSource.Direct, + data: reducedData, + }), + client.programMetadata.instructions.trim({ + account: metadata, + authority, + destination: destination.address, + }), + ]); // Then we expect the metadata account to be trimmed. - const metadataAccount = await fetchMetadata(client.rpc, metadata); + const metadataAccount = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(metadataAccount.data, { discriminator: AccountDiscriminator.Metadata, data: reducedData, }); // And we expect the destination account to have the rent difference. - const rentDifference = await getRentWithoutHeader(client, 100); + const rentDifference = await client.getMinimumBalance(100, { withoutHeader: true }); const destinationBalance = await getBalance(client, destination.address); t.is(destinationBalance, lamports(rentForAccountHeader + rentDifference)); }); diff --git a/clients/js/test/updateMetadata.test.ts b/clients/js/test/updateMetadata.test.ts index 5771b52..604c542 100644 --- a/clients/js/test/updateMetadata.test.ts +++ b/clients/js/test/updateMetadata.test.ts @@ -1,37 +1,28 @@ -import { address, getUtf8Encoder, none, some } from '@solana/kit'; +import { address, generateKeyPairSigner, getUtf8Encoder, none, some } from '@solana/kit'; import test from 'ava'; import { AccountDiscriminator, Compression, - createMetadata, DataSource, Encoding, - fetchMaybeBuffer, - fetchMetadata, + findCanonicalPda, + findNonCanonicalPda, Format, Metadata, - updateMetadata, } from '../src'; -import { - createDefaultSolanaClient, - createDeployedProgram, - createKeypairBuffer, - generateKeyPairSignerWithSol, - setAuthority, -} from './_setup'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it updates a canonical metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -42,11 +33,10 @@ test('it updates a canonical metadata account', async t => { // When we update the metadata account with new data. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -56,7 +46,8 @@ test('it updates a canonical metadata account', async t => { }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -75,16 +66,15 @@ test('it updates a canonical metadata account', async t => { test('it updates a canonical metadata account with data larger than a transaction size', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -95,11 +85,10 @@ test('it updates a canonical metadata account with data larger than a transactio // When we update the metadata account with new data with a lot of data. const newData = getUtf8Encoder().encode('x'.repeat(3_000)); - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -109,7 +98,8 @@ test('it updates a canonical metadata account with data larger than a transactio }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -128,16 +118,15 @@ test('it updates a canonical metadata account with data larger than a transactio test('it updates a canonical metadata account using an existing buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -148,17 +137,16 @@ test('it updates a canonical metadata account using an existing buffer', async t // And an existing buffer with the following data. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransaction(); // When we update the metadata account using the existing buffer. - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -168,7 +156,8 @@ test('it updates a canonical metadata account using an existing buffer', async t }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -187,14 +176,12 @@ test('it updates a canonical metadata account using an existing buffer', async t test('it updates a non-canonical metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following existing non-canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -207,9 +194,7 @@ test('it updates a non-canonical metadata account', async t => { // When we update the metadata account with new data. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, seed: 'idl', @@ -221,7 +206,8 @@ test('it updates a non-canonical metadata account', async t => { }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -240,14 +226,12 @@ test('it updates a non-canonical metadata account', async t => { test('it updates a non-canonical metadata account with data larger than a transaction size', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following existing non-canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -260,9 +244,7 @@ test('it updates a non-canonical metadata account with data larger than a transa // When we update the metadata account with new data. const newData = getUtf8Encoder().encode('x'.repeat(3_000)); - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, seed: 'idl', @@ -274,7 +256,8 @@ test('it updates a non-canonical metadata account with data larger than a transa }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -293,14 +276,12 @@ test('it updates a non-canonical metadata account with data larger than a transa test('it updates a non-canonical metadata account using an existing buffer', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following existing non-canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -313,15 +294,13 @@ test('it updates a non-canonical metadata account using an existing buffer', asy // And an existing buffer with the following data. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, data: newData }) + .sendTransaction(); // When we update the metadata account using the existing buffer. - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, seed: 'idl', @@ -333,7 +312,8 @@ test('it updates a non-canonical metadata account using an existing buffer', asy }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findNonCanonicalPda({ program, authority: authority.address, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -352,14 +332,12 @@ test('it updates a non-canonical metadata account using an existing buffer', asy test('it cannot update a metadata account if no data or buffer is provided', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, seed: 'idl', @@ -371,9 +349,7 @@ test('it cannot update a metadata account if no data or buffer is provided', asy }); // When we try to update a metadata account without providing data or buffer. - const promise = updateMetadata({ - ...client, - payer: authority, + const promise = client.programMetadata.updateMetadata({ authority, program, seed: 'idl', @@ -391,16 +367,15 @@ test('it cannot update a metadata account if no data or buffer is provided', asy test('it can close a new buffer after using it to update a new metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -411,11 +386,10 @@ test('it can close a new buffer after using it to update a new metadata account' // When we update the metadata account using the close buffer. const newData = getUtf8Encoder().encode('x'.repeat(3_000)); - const { metadata } = await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -426,7 +400,8 @@ test('it can close a new buffer after using it to update a new metadata account' }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -445,16 +420,15 @@ test('it can close a new buffer after using it to update a new metadata account' test('it can close an existing buffer after using it to update a new metadata account', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And the following existing canonical metadata account. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -465,24 +439,17 @@ test('it can close an existing buffer after using it to update a new metadata ac // And an existing buffer with the following data and the same authority. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const buffer = await createKeypairBuffer(client, { - payer: authority, - data: newData, - }); - await setAuthority(client, { - payer: authority, - account: buffer.address, - authority: buffer, - newAuthority: authority.address, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority, data: newData }) + .sendTransaction(); // When we update the metadata account using // the existing buffer and the `closeBuffer` option. - await updateMetadata({ - ...client, - payer: authority, + await client.programMetadata.updateMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -493,6 +460,6 @@ test('it can close an existing buffer after using it to update a new metadata ac }); // Then we expect the buffer account to no longer exist. - const bufferAccount = await fetchMaybeBuffer(client.rpc, buffer.address); + const bufferAccount = await client.programMetadata.accounts.buffer.fetchMaybe(buffer.address); t.false(bufferAccount.exists); }); diff --git a/clients/js/test/write.test.ts b/clients/js/test/write.test.ts index da89709..5bbd55b 100644 --- a/clients/js/test/write.test.ts +++ b/clients/js/test/write.test.ts @@ -1,157 +1,110 @@ -import { - address, - appendTransactionMessageInstruction, - appendTransactionMessageInstructions, - getUtf8Encoder, - pipe, -} from '@solana/kit'; +import { address, generateKeyPairSigner, getUtf8Encoder } from '@solana/kit'; import test from 'ava'; -import { AccountDiscriminator, Buffer, fetchBuffer, getWriteInstruction } from '../src'; -import { - createCanonicalBuffer, - createDefaultSolanaClient, - createDefaultTransaction, - createDeployedProgram, - createKeypairBuffer, - createNonCanonicalBuffer, - generateKeyPairSignerWithSol, - signAndSendTransaction, -} from './_setup'; +import { AccountDiscriminator, Buffer, findCanonicalPda, findNonCanonicalPda } from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it writes to canonical PDA buffers', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And the following pre-allocated buffer account. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - const [buffer] = await createCanonicalBuffer(client, { - authority, - program, - programData, - seed, - dataLength: data.length, - }); + await client.programMetadata.instructions + .createCanonicalBuffer({ authority, program, programData, seed, dataLength: data.length }) + .sendTransaction(); + const [buffer] = await findCanonicalPda({ program, seed }); // When we write some data to the buffer account. - const writeIx = getWriteInstruction({ buffer, authority, offset: 0, data }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(writeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions.write({ buffer, authority, offset: 0, data }).sendTransaction(); // Then we expect the buffer account to contain the written data. - const bufferAccount = await fetchBuffer(client.rpc, buffer); - t.like(bufferAccount.data, >{ - discriminator: AccountDiscriminator.Buffer, - canonical: true, - data, - }); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer); + t.like(bufferAccount.data, >{ discriminator: AccountDiscriminator.Buffer, canonical: true, data }); }); test('it writes to non-canonical PDA buffers', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const program = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); // And the following pre-allocated buffer account. const seed = 'dummy'; const data = getUtf8Encoder().encode('Hello, World!'); - const [buffer] = await createNonCanonicalBuffer(client, { - authority, - program, - seed, - dataLength: data.length, - }); + await client.programMetadata.instructions + .createNonCanonicalBuffer({ authority, program, seed, dataLength: data.length }) + .sendTransaction(); + const [buffer] = await findNonCanonicalPda({ authority: authority.address, program, seed }); // When we write some data to the buffer account. - const writeIx = getWriteInstruction({ buffer, authority, offset: 0, data }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(writeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions.write({ buffer, authority, offset: 0, data }).sendTransaction(); // Then we expect the buffer account to contain the written data. - const bufferAccount = await fetchBuffer(client.rpc, buffer); - t.like(bufferAccount.data, >{ - discriminator: AccountDiscriminator.Buffer, - canonical: false, - data, - }); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer); + t.like(bufferAccount.data, >{ discriminator: AccountDiscriminator.Buffer, canonical: false, data }); }); test('it writes to keypair buffers', async t => { // Given the following payer. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const payer = await generateKeyPairSignerWithSol(client); // And the following pre-allocated keypair buffer account. const data = getUtf8Encoder().encode('Hello, World!'); - const buffer = await createKeypairBuffer(client, { - payer, - dataLength: data.length, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: buffer, authority: buffer, payer, data: new Uint8Array(data.length) }) + .sendTransaction(); // When we write some data to the buffer account. - const writeIx = getWriteInstruction({ - buffer: buffer.address, - authority: buffer, - offset: 0, - data, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstruction(writeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .write({ buffer: buffer.address, authority: buffer, offset: 0, data }) + .sendTransaction(); // Then we expect the buffer account to contain the written data. - const bufferAccount = await fetchBuffer(client.rpc, buffer.address); - t.like(bufferAccount.data, >{ - discriminator: AccountDiscriminator.Buffer, - data, - }); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer.address); + t.like(bufferAccount.data, >{ discriminator: AccountDiscriminator.Buffer, data }); }); test('it appends to the end of buffers when doing multiple writes', async t => { // Given the following payer. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const payer = await generateKeyPairSignerWithSol(client); // And the following pre-allocated keypair buffer account. const dataChunk1 = getUtf8Encoder().encode('[Data part 1]'); const dataChunk2 = getUtf8Encoder().encode('[Data part 2]'); - const buffer = await createKeypairBuffer(client, { - payer, - dataLength: dataChunk1.length + dataChunk2.length, - }); + const buffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ + newBuffer: buffer, + authority: buffer, + payer, + data: new Uint8Array(dataChunk1.length + dataChunk2.length), + }) + .sendTransaction(); // When we write some data to the buffer account. - const firstWriteIx = getWriteInstruction({ - buffer: buffer.address, - authority: buffer, - offset: 0, - data: dataChunk1, - }); - const secondWriteIx = getWriteInstruction({ - buffer: buffer.address, - authority: buffer, - offset: dataChunk1.length, - data: dataChunk2, - }); - await pipe( - await createDefaultTransaction(client, payer), - tx => appendTransactionMessageInstructions([firstWriteIx, secondWriteIx], tx), - tx => signAndSendTransaction(client, tx), - ); + await client.sendTransaction([ + client.programMetadata.instructions.write({ + buffer: buffer.address, + authority: buffer, + offset: 0, + data: dataChunk1, + }), + client.programMetadata.instructions.write({ + buffer: buffer.address, + authority: buffer, + offset: dataChunk1.length, + data: dataChunk2, + }), + ]); // Then we expect the buffer account to contain the written data. - const bufferAccount = await fetchBuffer(client.rpc, buffer.address); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer.address); t.like(bufferAccount.data, >{ discriminator: AccountDiscriminator.Buffer, data: new Uint8Array([...dataChunk1, ...dataChunk2]), @@ -160,46 +113,31 @@ test('it appends to the end of buffers when doing multiple writes', async t => { test('it writes to buffers using other buffers', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); const [program, programData] = await createDeployedProgram(client, authority); // And an existing keypair buffer account with data. const data = getUtf8Encoder().encode('Hello, World!'); - const sourceBuffer = await createKeypairBuffer(client, { - payer: authority, - data, - }); + const sourceBuffer = await generateKeyPairSigner(); + await client.programMetadata.instructions + .createBuffer({ newBuffer: sourceBuffer, authority: sourceBuffer, data }) + .sendTransaction(); // And the following pre-allocated pre-funded empty canonical buffer account. const seed = 'dummy'; - const [buffer] = await createCanonicalBuffer(client, { - authority, - program, - programData, - seed, - dataLength: data.length, - }); + await client.programMetadata.instructions + .createCanonicalBuffer({ authority, program, programData, seed, dataLength: data.length }) + .sendTransaction(); + const [buffer] = await findCanonicalPda({ program, seed }); // When we write some data to the canonical buffer account // using the keypair buffer account as source. - const writeIx = getWriteInstruction({ - buffer, - authority, - offset: 0, - sourceBuffer: sourceBuffer.address, - }); - await pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstruction(writeIx, tx), - tx => signAndSendTransaction(client, tx), - ); + await client.programMetadata.instructions + .write({ buffer, authority, offset: 0, sourceBuffer: sourceBuffer.address }) + .sendTransaction(); // Then we expect the buffer account to contain the data from the source buffer. - const bufferAccount = await fetchBuffer(client.rpc, buffer); - t.like(bufferAccount.data, >{ - discriminator: AccountDiscriminator.Buffer, - canonical: true, - data, - }); + const bufferAccount = await client.programMetadata.accounts.buffer.fetch(buffer); + t.like(bufferAccount.data, >{ discriminator: AccountDiscriminator.Buffer, canonical: true, data }); }); diff --git a/clients/js/test/writeMetadata.test.ts b/clients/js/test/writeMetadata.test.ts index 04fa3de..14261fe 100644 --- a/clients/js/test/writeMetadata.test.ts +++ b/clients/js/test/writeMetadata.test.ts @@ -1,36 +1,25 @@ -import { fetchEncodedAccount, getUtf8Encoder, none } from '@solana/kit'; +import { getUtf8Encoder, none } from '@solana/kit'; import test from 'ava'; -import { - AccountDiscriminator, - Compression, - createMetadata, - DataSource, - Encoding, - fetchMetadata, - findCanonicalPda, - Format, - Metadata, - writeMetadata, -} from '../src'; -import { createDefaultSolanaClient, createDeployedProgram, generateKeyPairSignerWithSol } from './_setup'; +import { AccountDiscriminator, Compression, DataSource, Encoding, findCanonicalPda, Format, Metadata } from '../src'; +import { createDeployedProgram, createTestClient, generateKeyPairSignerWithSol } from './_setup'; test('it creates a new metadata account if it does not exist', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And given the following canonical metadata account does not exist. const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); - t.false((await fetchEncodedAccount(client.rpc, metadata)).exists); + const initialAccount = await client.programMetadata.accounts.metadata.fetchMaybe(metadata); + t.false(initialAccount.exists); // When we upload this canonical metadata account. const data = getUtf8Encoder().encode('Some data'); - await writeMetadata({ - ...client, - payer: authority, + await client.programMetadata.writeMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -40,7 +29,7 @@ test('it creates a new metadata account if it does not exist', async t => { }); // Then we expect the metadata account to be created. - const account = await fetchMetadata(client.rpc, metadata); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program, @@ -53,22 +42,21 @@ test('it creates a new metadata account if it does not exist', async t => { dataSource: DataSource.Direct, format: Format.Json, dataLength: data.length, - data: data, + data, }); }); test('it updates a metadata account if it exists', async t => { // Given the following authority and deployed program. - const client = createDefaultSolanaClient(); + const client = await createTestClient(); const authority = await generateKeyPairSignerWithSol(client); - const [program] = await createDeployedProgram(client, authority); + const [program, programData] = await createDeployedProgram(client, authority); // And given the following canonical metadata account exists. - await createMetadata({ - ...client, - payer: authority, + await client.programMetadata.createMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Utf8, compression: Compression.None, @@ -79,11 +67,10 @@ test('it updates a metadata account if it exists', async t => { // When we upload this canonical metadata account with different data. const newData = getUtf8Encoder().encode('NEW DATA WITH MORE BYTES'); - const { metadata } = await writeMetadata({ - ...client, - payer: authority, + await client.programMetadata.writeMetadata({ authority, program, + programData, seed: 'idl', encoding: Encoding.Base58, compression: Compression.Gzip, @@ -93,7 +80,8 @@ test('it updates a metadata account if it exists', async t => { }); // Then we expect the metadata account to be updated. - const account = await fetchMetadata(client.rpc, metadata); + const [metadata] = await findCanonicalPda({ program, seed: 'idl' }); + const account = await client.programMetadata.accounts.metadata.fetch(metadata); t.like(account.data, { discriminator: AccountDiscriminator.Metadata, program,