From 9053d3bdd4755e4ef3aa99db5ee36be4e4433bf7 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 20 May 2024 15:03:44 -0700 Subject: [PATCH] Third party plugins staging to main (#107) * Add structs and instructions for third party plugins (#58) * Add structs and ixs for third party plugins * lifecyle_checks into a HashMap and add check_external_registry * Use Vec for third party lifecycle checks * Combine lifecycle hooks and allow custom seeds * Regenerate IDL and clients * Rename external plugin header to external plugin * Also move external plugin data offset inside of external plugin. * Regenerate IDL and clients * Add create_v2 and create_colleciton_v2 basic structure * Regenerate IDL and clients * Add external plugin init info container * Regenerate IDL and clients * Add init and update structs for external plugins * Also limit hookable lifecycle events. * Resolve other PR comments. * Regenerate IDL and clients * Move key information into init structs (#65) * Move key info into external plugin init info * Regenerate clients and IDL * Danenbm/no init data (#66) * Do not allow data init on 3P plugins * Also no lifecycle checks for data store. * Regenerate IDL and clients * Change external plugin registry name (#67) * change external plugins to external registry * Regenerate IDL and clients * Update custom JS deserializer * Remove extra accounts type (#68) * External plugin key change (#73) * Adding WIP for adding external plugins. * Updating some permissions and adding more tests. * Adding new IXes and data auth. * Adding max size parameter. * Removing allocate. * Forgot to save. * Removing size and clear IX. * Updating based on feedback. * Updating based on feedback and adding to create.' * Adding tests and removing force approve. * Danenbm/update from main (#80) * Serde Pubkey DisplayFromStr (#78) * Update kinobi version * Regenerate clients * chore: Release mpl-core version 0.5.0 --------- Co-authored-by: danenbm * Adding remove plugin. * Removing prints. * Adding for collection. * Nhan/js sdk v1 (#75) - sdk v1 - retain mostly backwards compatibility some types have changed name: BasePluginAuthority -> PluginAuthority - many generated types have changed from Type -> BaseType - support external plugins * Oracle account type (#82) * Regenerate IDL and clients * Add Oracle validation type * Regenerate IDL and clients * Update JS SDK for ValidationResultsOffset * fix minor type issues --------- Co-authored-by: Nhan Phan * formatting * Fixing deser. * Testing fo realz. * Adding ExternalPluginNotFound Error. * Moving duped code to helper function. * Add external plugin validation functions (#85) * Add validations for external plugins on lifecycle events * Remove external_plugin_validate from plugin_checks * WIP. * Moving data offset and length to the record level. * Updating JS hooks. * Call external plugin validations (#86) * Add validations for external plugins on lifecycle events * Remove external_plugin_validate from plugin_checks * Call external plugin validations * Discard ForceApproved in external plugin validation * Fast exit if external validation rejects * Also mark ForceApproved as unreachable for external plugins * Adding missing params to function after rebase * Add missing ForceApproved check * Update ExternalRegistryRecordSafe and fix tests (#88) * Finishing up update external plugin. * Removing prints. * Adding check to prevent duplicate external plugins. * Whoops missed collections. * Adding resize optimization. * Oracle lifecycle validations (#89) * Update ExternalRegistryRecordSafe and fix tests * Add accounts, asset, and collection to PluginValidationContext * Implement Oracle lifecycle validations * Add new error codes * Regenerate IDL and clients * Derive custom PDA using Seeds Vec * Make sure third party plugins can approve or reject before using output * Use user program for Program Seed * Change ExternalCheckResult to u32 * Regenerate IDL and clients * Fixup add accounts (#94) * Add missing accounts * Format fix * Add Address seed and remove Program seed (#95) * Add missing accounts * Format fix * Remove program seed and add address seed * Regenerate IDL and clients * Update JS SDK Seed type * Format fix * Format fix * Prevent Oracle from approving lifecycle events (#98) * Prevent Oracle from approving lifecycle events * Regenerate IDL and clients * Change error to be Oracle specific and prevent Oracle from listening * Lint fix (#97) * Add default case for extraAccount seed handler * Regenerate JS client * Format fix * Danenbm/oracle error code (#100) * Regenerate IDL with changed error code * Make check tighter to not allow zero checks Also fix Rust client tests * oracle test wip (#90) * Require passing in oracle lifecycle checks Vec (#101) * Regenerate IDL with changed error code * Make check tighter to not allow zero checks Also fix Rust client tests * Require Oracle to provide lifecycle checks at init * Regenerate IDL and clients * update oracle init helper * Fix Rust tests * Require non-empty Vec * Regenerate IDL and clients * Change CanDeny to CanReject * Regenerate IDL and clients * Format fix * Add test * Add more tests * More tests for invalid checks and multiple oracle test * Prevent duplicate lifecycle checks in external plugins (#104) * Prevent duplicate lifecycle check on external plugins * Regenerate IDL and clients * Add Rust tests * Danenbm/lifecycle check requirements (#105) * Require LifecycleHook to have lifecycle checks Also do not update lifecycle checks when update value is None * Regenerate IDL and clients * Fix JS client and fix Rust tests * Add tests * Add Rust tests for updating with duplicate lifecycle events * fix cyclic dep, create with plugin, update with key --------- Co-authored-by: Nhan Phan * Block `LifecycleHook` and `DataStore` from being created or added later (#106) * Block LifecycleHook and DataStore for now * Skip tests for deactivated external plugins * Add more Oracle update tests * Add tests to verify LifecycleHook and DataStore cannot be added * Ignore Rust update test * Add collection tests * fix external plugin offsets, better types, new tests (#109) * Adding anchor types. * Update. * removed audit warning (#108) * better types, new test * Updating Kinobi dependency. * Fixing kinobi reference. * Bumping Kinobi version and fixing clients. * Fixing mpl-utils version. * Immutability plugins (#96) * Add ImmutableMetadata && AddBlocker plugins * Update autogenerated parts * add tests * change the order of enums * update generated part * added tests for ensuring that UA is the only one who can add the plugin * added tests for ensuring that UA is the only one who can add the plugin for collection and nested plugins * update tests * add audit details to readme (#103) * removed audit warning (#108) * regenerated clients * updated tests * updated rust clients * chore: Release mpl-core version 0.6.0 * Modify `update` and `update_plugin` to move external plugin offsets (#112) Notes Also combine asset and collection update functions to use the same processors. Also fix offset calculations when updating external plugins (added test for this case). More advanced refactor: Combine plugin updating into single utility function #113 Note this PR is built upon #109 so that it can be tested with the new tests from that branch. * Deploy JS client v0.4.7 * update ava version, allow assertions on external plugins, check oracles in oracle tests * minor add plugin interface change * add additional test assert, formatting --------- Co-authored-by: blockiosaurus Co-authored-by: Tony Boyle <81017245+tonyboylehub@users.noreply.github.com> Co-authored-by: Kyrylo Stepanov Co-authored-by: blockiosaurus <90809591+blockiosaurus@users.noreply.github.com> Co-authored-by: blockiosaurus Co-authored-by: Michael Danenberg <56533526+danenbm@users.noreply.github.com> * Improve Oracle account deserialization error handling (#115) * Remap oracle account deserialization errors Also fix extra space on last error message * Regenerate IDL and clients * Add tests and more error handling to Oracle borrowing and slicing * Change test name * Oracle uninitialized variant (#116) * Add uninitialized variant to OracleValidation * Regenerate IDL and clients * Handle uninitialized kind in JS * Add test for empty account * Add ability to specify custom program for custom PDA (#117) * Add ability to specify custom program for custom PDA * Regenerate IDL and JS SDK * Add customProgramId JS handling * Add test * Update oracle example js package * bump version v1-alpha * bump version v1-alpha * Run CI on third-party-plugins-staging (#122) * Run on third-party-plugins-staging for now * Format fix * Nhan/rename external plugin (#119) * bump version v1-alpha * bump version v1-alpha * initial rename * Rename Oracle pda - stacked onto rename external plugin (#121) * Format fix * Rename oracle pda to base_address_config in program * Regenerate IDL and clients * Update oracle SDK functions * Format JS and fix all tests * rename to external plugin adapater * formatting * rename back to external validation result * use internal validationresult type * rename a few more things * minor nits, comments --------- Co-authored-by: Michael Danenberg <56533526+danenbm@users.noreply.github.com> * remove unused remove collection plugin type field (#123) * bump version v1-alpha * remove unused type * Add simple test to use new SDK to add Oracle plugin (#125) * slightly refactor collection plugin types, add test (#124) * bump alpha version * bump version v1-alpha * add types, bump version * add fetch helpers, reduce strictness for umi context ix types, fix derive bug, better asset vs. collection plugin types (#126) * bump version v1-alpha * better deprecation message * lint, more flexible arg --------- Co-authored-by: blockiosaurus Co-authored-by: blockiosaurus <90809591+blockiosaurus@users.noreply.github.com> Co-authored-by: danenbm Co-authored-by: Nhan Phan Co-authored-by: Nhan Phan Co-authored-by: Tony Boyle <81017245+tonyboylehub@users.noreply.github.com> Co-authored-by: Kyrylo Stepanov Co-authored-by: blockiosaurus --- .github/workflows/main.yml | 4 +- Cargo.lock | 11 +- clients/js/README.md | 258 +- clients/js/package.json | 14 +- clients/js/pnpm-lock.yaml | 528 ++-- clients/js/src/authority.ts | 33 +- clients/js/src/demo.ts | 104 +- clients/js/src/generated/accounts/assetV1.ts | 8 +- .../generated/accounts/pluginRegistryV1.ts | 8 +- clients/js/src/generated/errors/mplCore.ts | 149 + .../addCollectionExternalPluginAdapterV1.ts | 167 + .../instructions/addCollectionPluginV1.ts | 12 +- .../addExternalPluginAdapterV1.ts | 172 + .../src/generated/instructions/addPluginV1.ts | 12 +- .../approveCollectionPluginAuthorityV1.ts | 12 +- .../instructions/approvePluginAuthorityV1.ts | 12 +- .../instructions/burnCollectionV1.ts | 2 +- .../js/src/generated/instructions/burnV1.ts | 2 +- .../instructions/createCollectionV2.ts | 185 ++ .../js/src/generated/instructions/createV2.ts | 214 ++ .../js/src/generated/instructions/index.ts | 10 + ...removeCollectionExternalPluginAdapterV1.ts | 167 + .../removeExternalPluginAdapterV1.ts | 174 ++ ...updateCollectionExternalPluginAdapterV1.ts | 173 + .../instructions/updateCollectionV1.ts | 12 +- .../updateExternalPluginAdapterV1.ts | 180 ++ .../js/src/generated/instructions/updateV1.ts | 18 +- ...teCollectionExternalPluginAdapterDataV1.ts | 174 ++ .../writeExternalPluginAdapterDataV1.ts | 179 ++ .../src/generated/types/assetV1AccountData.ts | 12 +- .../js/src/generated/types/baseDataStore.ts | 40 + .../generated/types/baseDataStoreInitInfo.ts | 48 + .../types/baseDataStoreUpdateInfo.ts | 37 + .../baseExternalPluginAdapterInitInfo.ts | 113 + .../types/baseExternalPluginAdapterKey.ts | 106 + .../baseExternalPluginAdapterUpdateInfo.ts | 116 + .../src/generated/types/baseExtraAccount.ts | 182 ++ .../src/generated/types/baseLifecycleHook.ts | 56 + .../types/baseLifecycleHookInitInfo.ts | 77 + .../types/baseLifecycleHookUpdateInfo.ts | 68 + ...{masterEdition.ts => baseMasterEdition.ts} | 16 +- clients/js/src/generated/types/baseOracle.ts | 49 + .../src/generated/types/baseOracleInitInfo.ts | 74 + .../generated/types/baseOracleUpdateInfo.ts | 68 + .../generated/types/basePluginAuthority.ts | 74 + .../types/{royalties.ts => baseRoyalties.ts} | 27 +- .../types/{ruleSet.ts => baseRuleSet.ts} | 45 +- clients/js/src/generated/types/baseSeed.ts | 87 + .../generated/types/baseUpdateAuthority.ts | 76 + .../types/baseValidationResultsOffset.ts | 80 + .../src/generated/types/compressionProof.ts | 12 +- .../generated/types/externalCheckResult.ts | 22 + .../generated/types/externalPluginAdapter.ts | 98 + .../types/externalPluginAdapterSchema.ts | 29 + .../types/externalPluginAdapterType.ts | 26 + .../generated/types/externalPluginRecord.ts | 37 - .../generated/types/externalRegistryRecord.ts | 78 + .../types/externalValidationResult.ts | 26 + .../js/src/generated/types/extraAccounts.ts | 92 - .../generated/types/hashablePluginSchema.ts | 12 +- .../generated/types/hookableLifecycleEvent.ts | 27 + clients/js/src/generated/types/index.ts | 36 +- .../src/generated/types/oracleValidation.ts | 85 + clients/js/src/generated/types/plugin.ts | 24 +- .../js/src/generated/types/pluginAuthority.ts | 75 - .../generated/types/pluginAuthorityPair.ts | 12 +- .../types/pluginRegistryV1AccountData.ts | 12 +- .../js/src/generated/types/registryRecord.ts | 12 +- .../js/src/generated/types/updateAuthority.ts | 77 - .../src/generated/types/validationResult.ts | 27 + clients/js/src/helpers/authority.ts | 14 +- clients/js/src/helpers/fetch.ts | 162 + clients/js/src/helpers/index.ts | 1 + clients/js/src/helpers/lifecycle.ts | 361 ++- clients/js/src/helpers/plugin.ts | 4 +- clients/js/src/helpers/state.ts | 75 +- clients/js/src/hooked/assetAccountData.ts | 31 +- .../js/src/hooked/collectionAccountData.ts | 23 +- clients/js/src/hooked/pluginRegistryV1Data.ts | 106 +- clients/js/src/index.ts | 1 - clients/js/src/instructions/addPlugin.ts | 43 + .../instructions/approvePluginAuthority.ts | 25 + clients/js/src/instructions/burn.ts | 39 + .../collection/addCollectionPlugin.ts | 49 + .../approveCollectionPluginAuthority.ts | 26 + .../instructions/collection/burnCollection.ts | 3 + .../collection/createCollection.ts | 47 + .../js/src/instructions/collection/index.ts | 8 + .../collection/removeCollectionPlugin.ts | 42 + .../revokeCollectionPluginAuthority.ts | 20 + .../collection/updateCollection.ts | 23 + .../collection/updateCollectionPlugin.ts | 43 + clients/js/src/instructions/create.ts | 109 + clients/js/src/instructions/freeze.ts | 5 +- clients/js/src/instructions/index.ts | 10 + clients/js/src/instructions/legacyDelegate.ts | 24 +- clients/js/src/instructions/legacyRevoke.ts | 5 +- clients/js/src/instructions/removePlugin.ts | 41 + .../src/instructions/revokePluginAuthority.ts | 22 + clients/js/src/instructions/transfer.ts | 40 + clients/js/src/instructions/update.ts | 48 + clients/js/src/instructions/updatePlugin.ts | 40 + clients/js/src/plugins.ts | 181 -- clients/js/src/plugins/dataStore.ts | 93 + .../src/plugins/externalPluginAdapterKey.ts | 38 + .../plugins/externalPluginAdapterManifest.ts | 20 + .../js/src/plugins/externalPluginAdapters.ts | 228 ++ clients/js/src/plugins/extraAccount.ts | 254 ++ clients/js/src/plugins/index.ts | 15 + clients/js/src/plugins/lib.ts | 219 ++ clients/js/src/plugins/lifecycleChecks.ts | 101 + clients/js/src/plugins/lifecycleHook.ts | 131 + clients/js/src/plugins/masterEdition.ts | 26 + clients/js/src/plugins/oracle.ts | 156 + clients/js/src/plugins/pluginAuthority.ts | 30 + clients/js/src/plugins/royalties.ts | 79 + clients/js/src/plugins/seed.ts | 51 + clients/js/src/plugins/types.ts | 195 ++ clients/js/src/plugins/updateAuthority.ts | 21 + .../js/src/plugins/validationResultsOffset.ts | 36 + clients/js/src/types.ts | 68 - clients/js/src/utils.ts | 20 + clients/js/test/{_setup.ts => _setupRaw.ts} | 30 +- clients/js/test/_setupSdk.ts | 126 + clients/js/test/addPlugin.test.ts | 2 +- clients/js/test/approveAuthority.test.ts | 2 +- clients/js/test/asset.test.ts | 2 +- clients/js/test/burn.test.ts | 49 +- clients/js/test/collect.test.ts | 2 +- clients/js/test/collection.test.ts | 2 +- clients/js/test/collectionSize.test.ts | 2 +- clients/js/test/compress.test.ts | 2 +- clients/js/test/create.test.ts | 4 +- clients/js/test/createCollection.test.ts | 2 +- clients/js/test/decompress.test.ts | 2 +- .../externalPlugins/lifecycleHook.test.ts | 111 + .../js/test/externalPlugins/oracle.test.ts | 2778 +++++++++++++++++ clients/js/test/getProgram.test.ts | 2 +- clients/js/test/helps/authority.test.ts | 2 +- clients/js/test/helps/fetch.test.ts | 159 + clients/js/test/helps/lifecycle.test.ts | 599 +++- clients/js/test/helps/plugin.test.ts | 2 +- clients/js/test/helps/state.test.ts | 6 +- clients/js/test/info.test.ts | 4 +- clients/js/test/instructions/freeze.test.ts | 2 +- .../test/instructions/legacyDelegate.test.ts | 2 +- .../js/test/instructions/legacyRevoke.test.ts | 2 +- .../js/test/plugins/asset/addBlocker.test.ts | 76 +- .../js/test/plugins/asset/attributes.test.ts | 2 +- .../js/test/plugins/asset/delegate.test.ts | 2 +- .../plugins/asset/delegateTransfer.test.ts | 2 +- clients/js/test/plugins/asset/edition.test.ts | 2 +- clients/js/test/plugins/asset/freeze.test.ts | 2 +- .../plugins/asset/immutableMetadata.test.ts | 65 +- .../test/plugins/asset/permanentBurn.test.ts | 9 +- .../plugins/asset/permanentFreeze.test.ts | 2 +- .../plugins/asset/permanentTransfer.test.ts | 2 +- .../js/test/plugins/asset/royalties.test.ts | 2 +- .../test/plugins/asset/updateDelegate.test.ts | 2 +- .../plugins/collection/addBlocker.test.ts | 77 +- .../collection/immutableMetadata.test.ts | 77 +- .../plugins/collection/masterEdition.test.ts | 22 +- .../plugins/collection/permanentBurn.test.ts | 2 +- .../collection/permanentFreeze.test.ts | 2 +- .../collection/permanentTransfer.test.ts | 2 +- .../plugins/collection/updateDelegate.test.ts | 2 +- clients/js/test/removePlugin.test.ts | 2 +- clients/js/test/revokeAuthority.test.ts | 2 +- clients/js/test/sdkv1.test.ts | 1033 ++++++ clients/js/test/signers/burn.test.ts | 2 +- clients/js/test/signers/create.test.ts | 2 +- .../js/test/signers/createCollection.test.ts | 2 +- clients/js/test/signers/transfer.test.ts | 2 +- clients/js/test/signers/update.test.ts | 2 +- clients/js/test/transfer.test.ts | 14 +- clients/js/test/update.test.ts | 2 +- clients/js/test/updatePlugin.test.ts | 2 +- .../generated/accounts/plugin_registry_v1.rs | 4 +- clients/rust/src/generated/errors/mpl_core.rs | 27 + ...d_collection_external_plugin_adapter_v1.rs | 530 ++++ .../add_external_plugin_adapter_v1.rs | 582 ++++ .../instructions/burn_collection_v1.rs | 8 +- .../src/generated/instructions/burn_v1.rs | 8 +- .../instructions/create_collection_v2.rs | 529 ++++ .../src/generated/instructions/create_v2.rs | 771 +++++ .../rust/src/generated/instructions/mod.rs | 20 + ...e_collection_external_plugin_adapter_v1.rs | 528 ++++ .../remove_external_plugin_adapter_v1.rs | 578 ++++ ...e_collection_external_plugin_adapter_v1.rs | 549 ++++ .../update_external_plugin_adapter_v1.rs | 599 ++++ ...lection_external_plugin_adapter_data_v1.rs | 544 ++++ .../write_external_plugin_adapter_data_v1.rs | 594 ++++ .../rust/src/generated/types/data_store.rs | 22 + .../generated/types/data_store_init_info.rs | 23 + .../generated/types/data_store_update_info.rs | 20 + ...gin_record.rs => external_check_result.rs} | 6 +- .../types/external_plugin_adapter.rs | 24 + .../external_plugin_adapter_init_info.rs | 24 + .../types/external_plugin_adapter_key.rs | 31 + .../types/external_plugin_adapter_schema.rs | 22 + .../types/external_plugin_adapter_type.rs | 22 + .../external_plugin_adapter_update_info.rs | 24 + .../types/external_registry_record.rs | 28 + .../types/external_validation_result.rs | 22 + .../rust/src/generated/types/extra_account.rs | 55 + .../types/hookable_lifecycle_event.rs | 23 + .../src/generated/types/lifecycle_hook.rs | 30 + .../types/lifecycle_hook_init_info.rs | 34 + .../types/lifecycle_hook_update_info.rs | 25 + clients/rust/src/generated/types/mod.rs | 52 +- clients/rust/src/generated/types/oracle.rs | 28 + .../src/generated/types/oracle_init_info.rs | 33 + .../src/generated/types/oracle_update_info.rs | 25 + .../src/generated/types/oracle_validation.rs | 26 + .../types/{extra_accounts.rs => seed.rs} | 25 +- .../src/generated/types/validation_result.rs | 23 + .../types/validation_results_offset.rs | 21 + clients/rust/src/hooked/advanced_types.rs | 75 +- clients/rust/src/hooked/asset.rs | 20 +- clients/rust/src/hooked/plugin.rs | 50 +- clients/rust/tests/add_external_plugins.rs | 851 +++++ clients/rust/tests/create.rs | 10 + clients/rust/tests/create_collection.rs | 5 + .../tests/create_with_external_plugins.rs | 404 +++ clients/rust/tests/plugins.rs | 6 + clients/rust/tests/remove_external_plugins.rs | 271 ++ clients/rust/tests/setup/mod.rs | 73 +- clients/rust/tests/transfer.rs | 4 + clients/rust/tests/update_external_plugins.rs | 493 +++ configs/kinobi.cjs | 107 +- configs/scripts/program/build.sh | 4 + .../scripts/program/dump_oracle_example.sh | 84 + configs/validator.cjs | 5 + idls/mpl_core.json | 2205 +++++++++++-- programs/mpl-core/Cargo.toml | 1 + programs/mpl-core/src/error.rs | 36 + programs/mpl-core/src/instruction.rs | 106 +- programs/mpl-core/src/plugins/data_store.rs | 74 + .../src/plugins/external_plugin_adapters.rs | 545 ++++ programs/mpl-core/src/plugins/lifecycle.rs | 374 ++- .../mpl-core/src/plugins/lifecycle_hook.rs | 96 + programs/mpl-core/src/plugins/mod.rs | 18 +- programs/mpl-core/src/plugins/oracle.rs | 215 ++ .../mpl-core/src/plugins/plugin_registry.rs | 90 +- programs/mpl-core/src/plugins/utils.rs | 323 +- .../processor/add_external_plugin_adapter.rs | 192 ++ programs/mpl-core/src/processor/add_plugin.rs | 19 +- .../src/processor/approve_plugin_authority.rs | 8 + programs/mpl-core/src/processor/burn.rs | 10 +- programs/mpl-core/src/processor/compress.rs | 4 + programs/mpl-core/src/processor/create.rs | 175 +- .../src/processor/create_collection.rs | 110 +- programs/mpl-core/src/processor/decompress.rs | 4 + programs/mpl-core/src/processor/mod.rs | 105 +- .../remove_external_plugin_adapter.rs | 167 + .../mpl-core/src/processor/remove_plugin.rs | 8 + .../src/processor/revoke_plugin_authority.rs | 8 + programs/mpl-core/src/processor/transfer.rs | 6 +- programs/mpl-core/src/processor/update.rs | 45 +- .../update_external_plugin_adapter.rs | 251 ++ .../mpl-core/src/processor/update_plugin.rs | 196 +- .../write_external_plugin_adapter_data.rs | 36 + programs/mpl-core/src/state/asset.rs | 73 +- programs/mpl-core/src/state/collection.rs | 73 +- programs/mpl-core/src/state/mod.rs | 24 +- .../mpl-core/src/state/update_authority.rs | 8 +- programs/mpl-core/src/utils.rs | 177 +- 267 files changed, 26661 insertions(+), 2151 deletions(-) create mode 100644 clients/js/src/generated/instructions/addCollectionExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/addExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/createCollectionV2.ts create mode 100644 clients/js/src/generated/instructions/createV2.ts create mode 100644 clients/js/src/generated/instructions/removeCollectionExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/removeExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/updateCollectionExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/updateExternalPluginAdapterV1.ts create mode 100644 clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts create mode 100644 clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts create mode 100644 clients/js/src/generated/types/baseDataStore.ts create mode 100644 clients/js/src/generated/types/baseDataStoreInitInfo.ts create mode 100644 clients/js/src/generated/types/baseDataStoreUpdateInfo.ts create mode 100644 clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts create mode 100644 clients/js/src/generated/types/baseExternalPluginAdapterKey.ts create mode 100644 clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts create mode 100644 clients/js/src/generated/types/baseExtraAccount.ts create mode 100644 clients/js/src/generated/types/baseLifecycleHook.ts create mode 100644 clients/js/src/generated/types/baseLifecycleHookInitInfo.ts create mode 100644 clients/js/src/generated/types/baseLifecycleHookUpdateInfo.ts rename clients/js/src/generated/types/{masterEdition.ts => baseMasterEdition.ts} (69%) create mode 100644 clients/js/src/generated/types/baseOracle.ts create mode 100644 clients/js/src/generated/types/baseOracleInitInfo.ts create mode 100644 clients/js/src/generated/types/baseOracleUpdateInfo.ts create mode 100644 clients/js/src/generated/types/basePluginAuthority.ts rename clients/js/src/generated/types/{royalties.ts => baseRoyalties.ts} (57%) rename clients/js/src/generated/types/{ruleSet.ts => baseRuleSet.ts} (51%) create mode 100644 clients/js/src/generated/types/baseSeed.ts create mode 100644 clients/js/src/generated/types/baseUpdateAuthority.ts create mode 100644 clients/js/src/generated/types/baseValidationResultsOffset.ts create mode 100644 clients/js/src/generated/types/externalCheckResult.ts create mode 100644 clients/js/src/generated/types/externalPluginAdapter.ts create mode 100644 clients/js/src/generated/types/externalPluginAdapterSchema.ts create mode 100644 clients/js/src/generated/types/externalPluginAdapterType.ts delete mode 100644 clients/js/src/generated/types/externalPluginRecord.ts create mode 100644 clients/js/src/generated/types/externalRegistryRecord.ts create mode 100644 clients/js/src/generated/types/externalValidationResult.ts delete mode 100644 clients/js/src/generated/types/extraAccounts.ts create mode 100644 clients/js/src/generated/types/hookableLifecycleEvent.ts create mode 100644 clients/js/src/generated/types/oracleValidation.ts delete mode 100644 clients/js/src/generated/types/pluginAuthority.ts delete mode 100644 clients/js/src/generated/types/updateAuthority.ts create mode 100644 clients/js/src/generated/types/validationResult.ts create mode 100644 clients/js/src/helpers/fetch.ts create mode 100644 clients/js/src/instructions/addPlugin.ts create mode 100644 clients/js/src/instructions/approvePluginAuthority.ts create mode 100644 clients/js/src/instructions/burn.ts create mode 100644 clients/js/src/instructions/collection/addCollectionPlugin.ts create mode 100644 clients/js/src/instructions/collection/approveCollectionPluginAuthority.ts create mode 100644 clients/js/src/instructions/collection/burnCollection.ts create mode 100644 clients/js/src/instructions/collection/createCollection.ts create mode 100644 clients/js/src/instructions/collection/index.ts create mode 100644 clients/js/src/instructions/collection/removeCollectionPlugin.ts create mode 100644 clients/js/src/instructions/collection/revokeCollectionPluginAuthority.ts create mode 100644 clients/js/src/instructions/collection/updateCollection.ts create mode 100644 clients/js/src/instructions/collection/updateCollectionPlugin.ts create mode 100644 clients/js/src/instructions/create.ts create mode 100644 clients/js/src/instructions/removePlugin.ts create mode 100644 clients/js/src/instructions/revokePluginAuthority.ts create mode 100644 clients/js/src/instructions/transfer.ts create mode 100644 clients/js/src/instructions/update.ts create mode 100644 clients/js/src/instructions/updatePlugin.ts delete mode 100644 clients/js/src/plugins.ts create mode 100644 clients/js/src/plugins/dataStore.ts create mode 100644 clients/js/src/plugins/externalPluginAdapterKey.ts create mode 100644 clients/js/src/plugins/externalPluginAdapterManifest.ts create mode 100644 clients/js/src/plugins/externalPluginAdapters.ts create mode 100644 clients/js/src/plugins/extraAccount.ts create mode 100644 clients/js/src/plugins/index.ts create mode 100644 clients/js/src/plugins/lib.ts create mode 100644 clients/js/src/plugins/lifecycleChecks.ts create mode 100644 clients/js/src/plugins/lifecycleHook.ts create mode 100644 clients/js/src/plugins/masterEdition.ts create mode 100644 clients/js/src/plugins/oracle.ts create mode 100644 clients/js/src/plugins/pluginAuthority.ts create mode 100644 clients/js/src/plugins/royalties.ts create mode 100644 clients/js/src/plugins/seed.ts create mode 100644 clients/js/src/plugins/types.ts create mode 100644 clients/js/src/plugins/updateAuthority.ts create mode 100644 clients/js/src/plugins/validationResultsOffset.ts delete mode 100644 clients/js/src/types.ts rename clients/js/test/{_setup.ts => _setupRaw.ts} (90%) create mode 100644 clients/js/test/_setupSdk.ts create mode 100644 clients/js/test/externalPlugins/lifecycleHook.test.ts create mode 100644 clients/js/test/externalPlugins/oracle.test.ts create mode 100644 clients/js/test/helps/fetch.test.ts create mode 100644 clients/js/test/sdkv1.test.ts create mode 100644 clients/rust/src/generated/instructions/add_collection_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/add_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/create_collection_v2.rs create mode 100644 clients/rust/src/generated/instructions/create_v2.rs create mode 100644 clients/rust/src/generated/instructions/remove_collection_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/remove_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/update_collection_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/update_external_plugin_adapter_v1.rs create mode 100644 clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs create mode 100644 clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs create mode 100644 clients/rust/src/generated/types/data_store.rs create mode 100644 clients/rust/src/generated/types/data_store_init_info.rs create mode 100644 clients/rust/src/generated/types/data_store_update_info.rs rename clients/rust/src/generated/types/{external_plugin_record.rs => external_check_result.rs} (83%) create mode 100644 clients/rust/src/generated/types/external_plugin_adapter.rs create mode 100644 clients/rust/src/generated/types/external_plugin_adapter_init_info.rs create mode 100644 clients/rust/src/generated/types/external_plugin_adapter_key.rs create mode 100644 clients/rust/src/generated/types/external_plugin_adapter_schema.rs create mode 100644 clients/rust/src/generated/types/external_plugin_adapter_type.rs create mode 100644 clients/rust/src/generated/types/external_plugin_adapter_update_info.rs create mode 100644 clients/rust/src/generated/types/external_registry_record.rs create mode 100644 clients/rust/src/generated/types/external_validation_result.rs create mode 100644 clients/rust/src/generated/types/extra_account.rs create mode 100644 clients/rust/src/generated/types/hookable_lifecycle_event.rs create mode 100644 clients/rust/src/generated/types/lifecycle_hook.rs create mode 100644 clients/rust/src/generated/types/lifecycle_hook_init_info.rs create mode 100644 clients/rust/src/generated/types/lifecycle_hook_update_info.rs create mode 100644 clients/rust/src/generated/types/oracle.rs create mode 100644 clients/rust/src/generated/types/oracle_init_info.rs create mode 100644 clients/rust/src/generated/types/oracle_update_info.rs create mode 100644 clients/rust/src/generated/types/oracle_validation.rs rename clients/rust/src/generated/types/{extra_accounts.rs => seed.rs} (66%) create mode 100644 clients/rust/src/generated/types/validation_result.rs create mode 100644 clients/rust/src/generated/types/validation_results_offset.rs create mode 100644 clients/rust/tests/add_external_plugins.rs create mode 100644 clients/rust/tests/create_with_external_plugins.rs create mode 100644 clients/rust/tests/remove_external_plugins.rs create mode 100644 clients/rust/tests/update_external_plugins.rs create mode 100755 configs/scripts/program/dump_oracle_example.sh create mode 100644 programs/mpl-core/src/plugins/data_store.rs create mode 100644 programs/mpl-core/src/plugins/external_plugin_adapters.rs create mode 100644 programs/mpl-core/src/plugins/lifecycle_hook.rs create mode 100644 programs/mpl-core/src/plugins/oracle.rs create mode 100644 programs/mpl-core/src/processor/add_external_plugin_adapter.rs create mode 100644 programs/mpl-core/src/processor/remove_external_plugin_adapter.rs create mode 100644 programs/mpl-core/src/processor/update_external_plugin_adapter.rs create mode 100644 programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60ef30a2..16a2cd25 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: Main on: push: - branches: [main] + branches: [main, third-party-plugins-staging] pull_request: - branches: [main] + branches: [main, third-party-plugins-staging] env: CACHE: true diff --git a/Cargo.lock b/Cargo.lock index b5389ec5..5dd5ce79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -616,9 +616,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -2303,6 +2303,7 @@ version = "0.1.0" dependencies = [ "borsh 0.10.3", "bytemuck", + "modular-bitfield", "mpl-utils", "num-derive 0.3.3", "num-traits", @@ -3181,7 +3182,7 @@ version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -4067,7 +4068,7 @@ dependencies = [ "ark-serialize", "base64 0.21.7", "bincode", - "bitflags 2.4.2", + "bitflags 2.5.0", "blake3", "borsh 0.10.3", "borsh 0.9.3", @@ -4395,7 +4396,7 @@ dependencies = [ "assert_matches", "base64 0.21.7", "bincode", - "bitflags 2.4.2", + "bitflags 2.5.0", "borsh 0.10.3", "bs58 0.4.0", "bytemuck", diff --git a/clients/js/README.md b/clients/js/README.md index 57047b31..5f2fc41e 100644 --- a/clients/js/README.md +++ b/clients/js/README.md @@ -38,138 +38,146 @@ A Umi-compatible JavaScript library for the project. 4. Examples ```ts // Create an asset - const assetAddress = generateSigner(umi); - const owner = generateSigner(umi); - - await createV1(umi, { - name: 'Test Asset', - uri: 'https://example.com/asset.json', - asset: assetAddress, - owner: owner.publicKey, // optional, will default to payer - }).sendAndConfirm(umi); - - // Create a collection - const collectionUpdateAuthority = generateSigner(umi); - const collectionAddress = generateSigner(umi); - await createCollectionV1(umi, { - name: 'Test Collection', - uri: 'https://example.com/collection.json', - collection: collectionAddress, - updateAuthority: collectionUpdateAuthority.publicKey, // optional, defaults to payer - }).sendAndConfirm(umi); - - // Create an asset in a collection, the authority must be the updateAuthority of the collection - await createV1(umi, { - name: 'Test Asset', - uri: 'https://example.com/asset.json', - asset: assetAddress, - collection: collectionAddress.publicKey, - authority: collectionUpdateAuthority, // optional, defaults to payer - }).sendAndConfirm(umi); - - // Transfer an asset - const recipient = generateSigner(umi); - await transferV1(umi, { - asset: assetAddress.publicKey, - newOwner: recipient.publicKey, - }).sendAndConfirm(umi); - - // Transfer an asset in a collection - await transferV1(umi, { - asset: assetAddress.publicKey, - newOwner: recipient.publicKey, - collection: collectionAddress.publicKey, - }).sendAndConfirm(umi); - - // Fetch an asset - const asset = await fetchAssetV1(umi, assetAddress.publicKey); - - // GPA fetch assets by owner - const assetsByOwner = await getAssetV1GpaBuilder(umi) - .whereField('key', Key.AssetV1) - .whereField('owner', owner.publicKey) - .getDeserialized(); - - // GPA fetch assets by collection - const assetsByCollection = await getAssetV1GpaBuilder(umi) - .whereField('key', Key.AssetV1) - .whereField( - 'updateAuthority', - updateAuthority('Collection', [collectionAddress.publicKey]) - ) - .getDeserialized(); - - // DAS API (RPC based indexing) fetch assets by owner/collection - // Coming soon + const assetAddress = generateSigner(umi); + const owner = generateSigner(umi); + + await create(umi, { + name: 'Test Asset', + uri: 'https://example.com/asset.json', + asset: assetAddress, + owner: owner.publicKey, // optional, will default to payer + }).sendAndConfirm(umi); + + // Fetch an asset + const asset = await fetchAssetV1(umi, assetAddress.publicKey); + + // Create a collection + const collectionUpdateAuthority = generateSigner(umi); + const collectionAddress = generateSigner(umi); + await createCollection(umi, { + name: 'Test Collection', + uri: 'https://example.com/collection.json', + collection: collectionAddress, + updateAuthority: collectionUpdateAuthority.publicKey, // optional, defaults to payer + }).sendAndConfirm(umi); + + // Fetch a collection + const collection = await fetchCollectionV1(umi, collectionAddress.publicKey); + + // Create an asset in a collection, the authority must be the updateAuthority of the collection + await create(umi, { + name: 'Test Asset', + uri: 'https://example.com/asset.json', + asset: assetAddress, + collection, + authority: collectionUpdateAuthority, // optional, defaults to payer + }).sendAndConfirm(umi); + + // Transfer an asset + const recipient = generateSigner(umi); + await transfer(umi, { + asset, + newOwner: recipient.publicKey, + }).sendAndConfirm(umi); + + // Transfer an asset in a collection + await transfer(umi, { + asset, + newOwner: recipient.publicKey, + collection, + }).sendAndConfirm(umi); + + // GPA fetch assets by owner + const assetsByOwner = await getAssetV1GpaBuilder(umi) + .whereField('key', Key.AssetV1) + .whereField('owner', owner.publicKey) + .getDeserialized(); + + // GPA fetch assets by collection + const assetsByCollection = await getAssetV1GpaBuilder(umi) + .whereField('key', Key.AssetV1) + .whereField( + 'updateAuthority', + updateAuthority('Collection', [collectionAddress.publicKey]) + ) + .getDeserialized(); + + // DAS API (RPC based indexing) fetch assets by owner/collection + // Coming soon ``` 5. Some advanced examples ```ts - // Freezing an asset - const assetAddress = generateSigner(umi); - const freezeDelegate = generateSigner(umi); - - await addPluginV1(umi, { - asset: assetAddress.publicKey, - // adds the owner-managed freeze plugin to the asset - plugin: createPlugin({ - type: 'FreezeDelegate', - data: { - frozen: true, - }, - }), + const umi = await createUmi(); + + // Freezing an asset + const assetAddress = generateSigner(umi); + const freezeDelegate = generateSigner(umi); + + await addPlugin(umi, { + asset: assetAddress.publicKey, + // adds the owner-managed freeze plugin to the asset + plugin: { + type: 'FreezeDelegate', + frozen: true, + // Optionally set the authority to a delegate who can unfreeze. If unset, this will be the Owner // This is functionally the same as calling addPlugin and approvePluginAuthority separately. // Freezing with a delegate is commonly used for escrowless staking programs. - initAuthority: addressPluginAuthority(freezeDelegate.publicKey), - }).sendAndConfirm(umi); - - // Unfreezing an asset with a delegate - // Revoking an authority will revert the authority back to the owner for owner-managed plugins - await revokePluginAuthorityV1(umi, { - asset: assetAddress.publicKey, - pluginType: PluginType.FreezeDelegate, - authority: freezeDelegate, - }).sendAndConfirm(umi); - - // Create a collection with royalties - const collectionAddress = generateSigner(umi); - const creator1 = generateSigner(umi); - const creator2 = generateSigner(umi); - - await createCollectionV1(umi, { - name: 'Test Collection', - uri: 'https://example.com/collection.json', - collection: collectionAddress, - plugins: [ - pluginAuthorityPair({ - type: 'Royalties', - data: { - basisPoints: 500, - creators: [ - { - address: creator1.publicKey, - percentage: 20, - }, - { - address: creator2.publicKey, - percentage: 80, - }, - ], - ruleSet: ruleSet('None'), // Compatibility rule set - }, - }), - ], - }).sendAndConfirm(umi); - - // Create an asset in a collection. - // Assets in a collection will inherit the collection's authority-managed plugins, in this case the royalties plugin - await createV1(umi, { - name: 'Test Asset', - uri: 'https://example.com/asset.json', - asset: assetAddress, - collection: collectionAddress.publicKey, - }).sendAndConfirm(umi); + authority: { + type: 'Address', + address: freezeDelegate.publicKey, + }, + } + }).sendAndConfirm(umi); + + // Unfreezing an asset with a delegate + // Revoking an authority will revert the authority back to the owner for owner-managed plugins + await revokePluginAuthority(umi, { + asset: assetAddress.publicKey, + plugin: { + type: 'FreezeDelegate', + }, + authority: freezeDelegate, + }).sendAndConfirm(umi); + + // Create a collection with royalties + const collectionAddress = generateSigner(umi); + const creator1 = generateSigner(umi); + const creator2 = generateSigner(umi); + + await createCollection(umi, { + name: 'Test Collection', + uri: 'https://example.com/collection.json', + collection: collectionAddress, + plugins: [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: creator1.publicKey, + percentage: 20, + }, + { + address: creator2.publicKey, + percentage: 80, + }, + ], + ruleSet: ruleSet('None'), // Compatibility rule set + + }, + ], + }).sendAndConfirm(umi); + + // Create an asset in a collection. + // Assets in a collection will inherit the collection's authority-managed plugins, in this case the royalties plugin + await create(umi, { + name: 'Test Asset', + uri: 'https://example.com/asset.json', + asset: assetAddress, + collection: await fetchCollectionV1(umi, collectionAddress.publicKey), + }).sendAndConfirm(umi); ``` You can learn more about this library's API by reading its generated [TypeDoc documentation](https://mpl-core-js-docs.vercel.app). diff --git a/clients/js/package.json b/clients/js/package.json index 3716bd2f..1cb9599f 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "@metaplex-foundation/mpl-core", - "version": "0.4.7", + "version": "1.0.0-alpha.6", "description": "Digital Assets", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -25,27 +25,27 @@ "repository": "https://github.com/metaplex-foundation/mpl-core.git", "author": "Metaplex Maintainers ", "license": "Apache-2.0", - "dependencies": {}, "peerDependencies": { "@metaplex-foundation/umi": ">=0.8.2 < 1", "@noble/hashes": "^1.3.1" }, "devDependencies": { + "@ava/typescript": "^5.0.0", + "@metaplex-foundation/mpl-core-oracle-example": "^0.0.2", "@metaplex-foundation/mpl-toolbox": "^0.8.0", - "@ava/typescript": "^3.0.1", - "@metaplex-foundation/umi": "^0.8.2", - "@metaplex-foundation/umi-bundle-tests": "^0.8.2", + "@metaplex-foundation/umi": "^0.8.10", + "@metaplex-foundation/umi-bundle-tests": "^0.8.10", "@solana/web3.js": "^1.73.0", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.46.1", - "ava": "^5.1.0", + "ava": "^6.1.3", "bs58": "5.0.0", "eslint": "^8.0.1", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", + "prettier": "^3.2.5", "rimraf": "^3.0.2", "typedoc": "^0.23.16", "typedoc-plugin-expand-object-like-types": "^0.1.1", diff --git a/clients/js/pnpm-lock.yaml b/clients/js/pnpm-lock.yaml index 46938c41..5a66d381 100644 --- a/clients/js/pnpm-lock.yaml +++ b/clients/js/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: '@noble/hashes': specifier: ^1.3.1 @@ -7,17 +11,20 @@ dependencies: devDependencies: '@ava/typescript': - specifier: ^3.0.1 - version: 3.0.1 + specifier: ^5.0.0 + version: 5.0.0 + '@metaplex-foundation/mpl-core-oracle-example': + specifier: ^0.0.2 + version: 0.0.2(@metaplex-foundation/umi@0.8.10)(@noble/hashes@1.3.1) '@metaplex-foundation/mpl-toolbox': specifier: ^0.8.0 - version: 0.8.0(@metaplex-foundation/umi@0.8.2) + version: 0.8.0(@metaplex-foundation/umi@0.8.10) '@metaplex-foundation/umi': - specifier: ^0.8.2 - version: 0.8.2 + specifier: ^0.8.10 + version: 0.8.10 '@metaplex-foundation/umi-bundle-tests': - specifier: ^0.8.2 - version: 0.8.2(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) + specifier: ^0.8.10 + version: 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) '@solana/web3.js': specifier: ^1.73.0 version: 1.73.0 @@ -28,8 +35,8 @@ devDependencies: specifier: ^5.46.1 version: 5.46.1(eslint@8.0.1)(typescript@4.9.4) ava: - specifier: ^5.1.0 - version: 5.1.0(@ava/typescript@3.0.1) + specifier: ^6.1.3 + version: 6.1.3(@ava/typescript@5.0.0) bs58: specifier: 5.0.0 version: 5.0.0 @@ -47,10 +54,10 @@ devDependencies: version: 2.26.0(@typescript-eslint/parser@5.46.1)(eslint@8.0.1) eslint-plugin-prettier: specifier: ^4.2.1 - version: 4.2.1(eslint-config-prettier@8.5.0)(eslint@8.0.1)(prettier@2.8.8) + version: 4.2.1(eslint-config-prettier@8.5.0)(eslint@8.0.1)(prettier@3.2.5) prettier: - specifier: ^2.8.8 - version: 2.8.8 + specifier: ^3.2.5 + version: 3.2.5 rimraf: specifier: ^3.0.2 version: 3.0.2 @@ -85,12 +92,12 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true - /@ava/typescript@3.0.1: - resolution: {integrity: sha512-/JXIUuKsvkaneaiA9ckk3ksFTqvu0mDNlChASrTe2BnDsvMbhQdPWyqQjJ9WRJWVhhs5TWn1/0Pp1G6Rv8Syrw==} - engines: {node: '>=12.22 <13 || >=14.17 <15 || >=16.4 <17 || >=17'} + /@ava/typescript@5.0.0: + resolution: {integrity: sha512-2twsQz2fUd95QK1MtKuEnjkiN47SKHZfi/vWj040EN6Eo2ZW3SNcAwncJqXXoMTYZTWtBRXYp3Fg8z+JkFI9aQ==} + engines: {node: ^18.18 || ^20.8 || ^21 || ^22} dependencies: escape-string-regexp: 5.0.0 - execa: 5.1.1 + execa: 8.0.1 dev: true /@babel/code-frame@7.23.5: @@ -1857,51 +1864,61 @@ packages: - supports-color dev: true - /@metaplex-foundation/mpl-toolbox@0.8.0(@metaplex-foundation/umi@0.8.2): + /@metaplex-foundation/mpl-core-oracle-example@0.0.2(@metaplex-foundation/umi@0.8.10)(@noble/hashes@1.3.1): + resolution: {integrity: sha512-DxHKLaM04YGMv/EGgIzzxSJnBPv+OTBKeekL7mm7Pcu/DqH+xIuy4K7h9mMhI3E1gSbWIaxtzlbitbvwXvYRcw==} + peerDependencies: + '@metaplex-foundation/umi': '>=0.8.2 < 1' + '@noble/hashes': ^1.3.1 + dependencies: + '@metaplex-foundation/umi': 0.8.10 + '@noble/hashes': 1.3.1 + dev: true + + /@metaplex-foundation/mpl-toolbox@0.8.0(@metaplex-foundation/umi@0.8.10): resolution: {integrity: sha512-SK1VUPU4hCaL3sozgtoVjjbZxqx2gWiRt0YTFbwEt5LAHWOlCb7J7rcrrA5XwymX4iV2bIWygYs0yz7hYyx2rg==} peerDependencies: '@metaplex-foundation/umi': ^0.8.2 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 dev: true - /@metaplex-foundation/umi-bundle-tests@0.8.2(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0): - resolution: {integrity: sha512-FO8rXJw3ZBKE8YHW+4zOodJmB+Lh50kCMtAA+DfZ9x6JKKMtk1kbmz2186Gclrx3/bl7Ep5FwokSAmoBiOys0A==} + /@metaplex-foundation/umi-bundle-tests@0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0): + resolution: {integrity: sha512-12BLhBF5lXLG7K1m4J8mEArcc/l4a4iRCmZt2VKSD0tA+i+b3ou1RA+e+262nhrEZ/uhYx0ChUzrJlBk0a1aXQ==} peerDependencies: - '@metaplex-foundation/umi': ^0.8.2 + '@metaplex-foundation/umi': ^0.8.10 '@solana/web3.js': ^1.72.0 dependencies: - '@metaplex-foundation/umi': 0.8.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) - '@metaplex-foundation/umi-http-fetch': 0.8.10(@metaplex-foundation/umi@0.8.2) - '@metaplex-foundation/umi-program-repository': 0.8.10(@metaplex-foundation/umi@0.8.2) - '@metaplex-foundation/umi-rpc-web3js': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) - '@metaplex-foundation/umi-serializer-data-view': 0.8.10(@metaplex-foundation/umi@0.8.2) - '@metaplex-foundation/umi-storage-mock': 0.8.10(@metaplex-foundation/umi@0.8.2) - '@metaplex-foundation/umi-transaction-factory-web3js': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi': 0.8.10 + '@metaplex-foundation/umi-eddsa-web3js': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi-http-fetch': 0.8.10(@metaplex-foundation/umi@0.8.10) + '@metaplex-foundation/umi-program-repository': 0.8.10(@metaplex-foundation/umi@0.8.10) + '@metaplex-foundation/umi-rpc-web3js': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi-serializer-data-view': 0.8.10(@metaplex-foundation/umi@0.8.10) + '@metaplex-foundation/umi-storage-mock': 0.8.10(@metaplex-foundation/umi@0.8.10) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) '@solana/web3.js': 1.73.0 transitivePeerDependencies: - encoding dev: true - /@metaplex-foundation/umi-eddsa-web3js@0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0): + /@metaplex-foundation/umi-eddsa-web3js@0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0): resolution: {integrity: sha512-2CLsuQ67oPKczB7RuG07/Ro1rrW25vGZArren20/zBTgHubGIuFW7V8e9qZDqkHDt1nFecxrX55g0fNtQaU40g==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 '@solana/web3.js': ^1.72.0 dependencies: - '@metaplex-foundation/umi': 0.8.2 - '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi': 0.8.10 + '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) '@noble/curves': 1.4.0 '@solana/web3.js': 1.73.0 dev: true - /@metaplex-foundation/umi-http-fetch@0.8.10(@metaplex-foundation/umi@0.8.2): + /@metaplex-foundation/umi-http-fetch@0.8.10(@metaplex-foundation/umi@0.8.10): resolution: {integrity: sha512-9QkVMaM8A8JFaTSanVJT3R7IzeLn05xrK7fFnfCTASaTvk+7Dj8ZsgXQjuyCi1pXQrMN+EsREn2NE/tn48ffMQ==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 node-fetch: 2.7.0 transitivePeerDependencies: - encoding @@ -1911,12 +1928,12 @@ packages: resolution: {integrity: sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A==} dev: true - /@metaplex-foundation/umi-program-repository@0.8.10(@metaplex-foundation/umi@0.8.2): + /@metaplex-foundation/umi-program-repository@0.8.10(@metaplex-foundation/umi@0.8.10): resolution: {integrity: sha512-zw+UMOg9z3xqqeosRfctmcnaDYkaoMMvqhu7Vpwt7K9tpVtMaMT5yei29ORvaYpiEVG5hZSbywr/vQsNY0EV9g==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 dev: true /@metaplex-foundation/umi-public-keys@0.8.9: @@ -1925,23 +1942,23 @@ packages: '@metaplex-foundation/umi-serializers-encodings': 0.8.9 dev: true - /@metaplex-foundation/umi-rpc-web3js@0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0): + /@metaplex-foundation/umi-rpc-web3js@0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0): resolution: {integrity: sha512-+60uxlX1OcThL2UIW9dbcy0Ihr011ggxkQYSxc1qh4qitgD9wrZZ/v9nX4tzEN88a52UJGhH5G3SF6CNmqy4aw==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 '@solana/web3.js': ^1.72.0 dependencies: - '@metaplex-foundation/umi': 0.8.2 - '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi': 0.8.10 + '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) '@solana/web3.js': 1.73.0 dev: true - /@metaplex-foundation/umi-serializer-data-view@0.8.10(@metaplex-foundation/umi@0.8.2): + /@metaplex-foundation/umi-serializer-data-view@0.8.10(@metaplex-foundation/umi@0.8.10): resolution: {integrity: sha512-DVKUQw7FEfpRIE9RF4YU73Mwlaf42RAWDgcycNiYFWpj/FLRebNr+2NT3ALYlOqXsAchnCAYjlQVxCB0Sopn9Q==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 dev: true /@metaplex-foundation/umi-serializers-core@0.8.9: @@ -1970,38 +1987,38 @@ packages: '@metaplex-foundation/umi-serializers-numbers': 0.8.9 dev: true - /@metaplex-foundation/umi-storage-mock@0.8.10(@metaplex-foundation/umi@0.8.2): + /@metaplex-foundation/umi-storage-mock@0.8.10(@metaplex-foundation/umi@0.8.10): resolution: {integrity: sha512-YOg/ZeeyZoD9vywumL8rpQmgcm4PcWAy1QpEx6Q7ocVqMBlbD08Hrn9/F0cjvjUHhy8NYG7QrbNXdGnklavtuw==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 dev: true - /@metaplex-foundation/umi-transaction-factory-web3js@0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0): + /@metaplex-foundation/umi-transaction-factory-web3js@0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0): resolution: {integrity: sha512-hnkDKdtuZgY6DKH6aSEd6UrZGi5/WvfYqJUWeM8SVl1/8GhNypNKWvuppvFHpU9X2tLXuF4JSG4TVxAuIs9LBQ==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 '@solana/web3.js': ^1.72.0 dependencies: - '@metaplex-foundation/umi': 0.8.2 - '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0) + '@metaplex-foundation/umi': 0.8.10 + '@metaplex-foundation/umi-web3js-adapters': 0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0) '@solana/web3.js': 1.73.0 dev: true - /@metaplex-foundation/umi-web3js-adapters@0.8.10(@metaplex-foundation/umi@0.8.2)(@solana/web3.js@1.73.0): + /@metaplex-foundation/umi-web3js-adapters@0.8.10(@metaplex-foundation/umi@0.8.10)(@solana/web3.js@1.73.0): resolution: {integrity: sha512-TGF+vzO/v0HMRM8sz1M1yh+wfZ5l4RCFm1Kkcy5MC9FrNDookPP6y9aYZ3R0QkEVVV3u+8QLptrTUrxr3EGmyQ==} peerDependencies: '@metaplex-foundation/umi': ^0.8.10 '@solana/web3.js': ^1.72.0 dependencies: - '@metaplex-foundation/umi': 0.8.2 + '@metaplex-foundation/umi': 0.8.10 '@solana/web3.js': 1.73.0 buffer: 6.0.3 dev: true - /@metaplex-foundation/umi@0.8.2: - resolution: {integrity: sha512-HfMcV9nILwavrS+Rv7FO5k7eRp4NMDxKAcx3ebeRF4fOvZ1rx5B/IthpN1G1frWkdqOOke1uWH112imEJHpqmQ==} + /@metaplex-foundation/umi@0.8.10: + resolution: {integrity: sha512-iGuGIfJh2+YFvUIkZ0nB/69EsQcaoq89DDPRPYvPBtOunfv8TH8SNrwcsXHlp9aBtn5FGKCPx3V3X/eurB32Fg==} dependencies: '@metaplex-foundation/umi-options': 0.8.9 '@metaplex-foundation/umi-public-keys': 0.8.9 @@ -2285,6 +2302,11 @@ packages: engines: {node: '>=10'} dev: true + /@sindresorhus/merge-streams@2.3.0: + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + dev: true + /@solana/buffer-layout@4.0.1: resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} engines: {node: '>=5.10'} @@ -2760,6 +2782,28 @@ packages: - supports-color dev: true + /@vercel/nft@0.26.5: + resolution: {integrity: sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + '@rollup/pluginutils': 4.2.1 + acorn: 8.11.3 + acorn-import-attributes: 1.9.5(acorn@8.11.3) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + node-gyp-build: 4.8.1 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /@vercel/node-bridge@3.1.11: resolution: {integrity: sha512-LGbj+kPGgRnIlKo3949z01mLbHVi4BnRE7V5R6+J4E3f7xpQ12I9Wek10V7ivLB+LyS1+ATdjasdXAF4HOhqQw==} dev: true @@ -2878,6 +2922,14 @@ packages: negotiator: 0.6.3 dev: true + /acorn-import-attributes@1.9.5(acorn@8.11.3): + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.11.3 + dev: true + /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2921,14 +2973,6 @@ packages: indent-string: 4.0.0 dev: true - /aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - dev: true - /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -3125,9 +3169,9 @@ packages: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} dev: true - /ava@5.1.0(@ava/typescript@3.0.1): - resolution: {integrity: sha512-e5VFrSQ0WBPyZJWRXVrO7RFOizFeNM0t2PORwrPvWtApgkORI6cvGnY3GX1G+lzpd0HjqNx5Jus22AhxVnUMNA==} - engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'} + /ava@6.1.3(@ava/typescript@5.0.0): + resolution: {integrity: sha512-tkKbpF1pIiC+q09wNU9OfyTDYZa8yuWvU2up3+lFJ3lr1RmnYh2GBpPwzYUEB0wvTPIUysGjcZLNZr7STDviRA==} + engines: {node: ^18.18 || ^20.8 || ^21 || ^22} hasBin: true peerDependencies: '@ava/typescript': '*' @@ -3135,46 +3179,41 @@ packages: '@ava/typescript': optional: true dependencies: - '@ava/typescript': 3.0.1 + '@ava/typescript': 5.0.0 + '@vercel/nft': 0.26.5 acorn: 8.11.3 acorn-walk: 8.3.2 ansi-styles: 6.2.1 arrgv: 1.0.2 arrify: 3.0.0 callsites: 4.1.0 - cbor: 8.1.0 + cbor: 9.0.2 chalk: 5.3.0 - chokidar: 3.6.0 chunkd: 2.0.1 - ci-info: 3.9.0 + ci-info: 4.0.0 ci-parallel-vars: 1.0.1 - clean-yaml-object: 0.1.0 - cli-truncate: 3.1.0 + cli-truncate: 4.0.0 code-excerpt: 4.0.0 common-path-prefix: 3.0.0 concordance: 5.0.4 currently-unhandled: 0.4.1 debug: 4.3.4 - del: 7.1.0 emittery: 1.0.3 - figures: 5.0.0 - globby: 13.2.2 + figures: 6.1.0 + globby: 14.0.1 ignore-by-default: 2.1.0 indent-string: 5.0.0 - is-error: 2.2.2 is-plain-object: 5.0.0 is-promise: 4.0.0 matcher: 5.0.0 - mem: 9.0.2 + memoize: 10.0.0 ms: 2.1.3 - p-event: 5.0.1 - p-map: 5.5.0 - picomatch: 2.3.1 - pkg-conf: 4.0.0 + p-map: 7.0.2 + package-config: 5.0.0 + picomatch: 3.0.1 plur: 5.1.0 - pretty-ms: 8.0.0 + pretty-ms: 9.0.0 resolve-cwd: 3.0.0 - slash: 3.0.0 stack-utils: 2.0.6 strip-ansi: 7.1.0 supertap: 3.0.1 @@ -3182,6 +3221,7 @@ packages: write-file-atomic: 5.0.1 yargs: 17.7.2 transitivePeerDependencies: + - encoding - supports-color dev: true @@ -3478,9 +3518,9 @@ packages: resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} dev: true - /cbor@8.1.0: - resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} - engines: {node: '>=12.19'} + /cbor@9.0.2: + resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==} + engines: {node: '>=16'} dependencies: nofilter: 3.1.0 dev: true @@ -3555,8 +3595,8 @@ packages: resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} dev: true - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + /ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} engines: {node: '>=8'} dev: true @@ -3569,18 +3609,6 @@ packages: engines: {node: '>=6'} dev: true - /clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - dependencies: - escape-string-regexp: 5.0.0 - dev: true - - /clean-yaml-object@0.1.0: - resolution: {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==} - engines: {node: '>=0.10.0'} - dev: true - /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -3593,12 +3621,12 @@ packages: engines: {node: '>=6'} dev: true - /cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} dependencies: slice-ansi: 5.0.0 - string-width: 5.1.2 + string-width: 7.1.0 dev: true /cli-width@3.0.0: @@ -3689,7 +3717,7 @@ packages: js-string-escape: 1.0.1 lodash: 4.17.21 md5-hex: 3.0.1 - semver: 7.6.0 + semver: 7.6.1 well-known-symbols: 2.0.0 dev: true @@ -3934,20 +3962,6 @@ packages: vm2: 3.9.19 dev: true - /del@7.1.0: - resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} - engines: {node: '>=14.16'} - dependencies: - globby: 13.2.2 - graceful-fs: 4.2.11 - is-glob: 4.0.3 - is-path-cwd: 3.0.0 - is-path-inside: 4.0.0 - p-map: 5.5.0 - rimraf: 3.0.2 - slash: 4.0.0 - dev: true - /delay@5.0.0: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} @@ -4032,10 +4046,6 @@ packages: stream-shift: 1.0.3 dev: true - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /edge-runtime@2.0.0: resolution: {integrity: sha512-TmRJhKi4mlM1e+zgF4CSzVU5gJ1sWj7ia+XhVgZ8PYyYUxk4PPjJU8qScpSLsAbdSxoBghLxdMuwuCzdYLd1sQ==} hasBin: true @@ -4064,12 +4074,12 @@ packages: engines: {node: '>=14.16'} dev: true - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + /emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} dev: true - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true /emojis-list@3.0.0: @@ -4668,7 +4678,7 @@ packages: - supports-color dev: true - /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.5.0)(eslint@8.0.1)(prettier@2.8.8): + /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.5.0)(eslint@8.0.1)(prettier@3.2.5): resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -4681,7 +4691,7 @@ packages: dependencies: eslint: 8.0.1 eslint-config-prettier: 8.5.0(eslint@8.0.1) - prettier: 2.8.8 + prettier: 3.2.5 prettier-linter-helpers: 1.0.0 dev: true @@ -4894,6 +4904,21 @@ packages: strip-final-newline: 2.0.0 dev: true + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + /exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -5017,12 +5042,11 @@ packages: escape-string-regexp: 1.0.5 dev: true - /figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} + /figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} dependencies: - escape-string-regexp: 5.0.0 - is-unicode-supported: 1.3.0 + is-unicode-supported: 2.0.0 dev: true /file-entry-cache@6.0.1: @@ -5063,6 +5087,11 @@ packages: - supports-color dev: true + /find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + engines: {node: '>=18'} + dev: true + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -5071,14 +5100,6 @@ packages: path-exists: 4.0.0 dev: true - /find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - dev: true - /flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5224,6 +5245,11 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true + /get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + dev: true + /get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -5252,6 +5278,11 @@ packages: engines: {node: '>=10'} dev: true + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + /get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -5349,15 +5380,16 @@ packages: slash: 3.0.0 dev: true - /globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /globby@14.0.1: + resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} + engines: {node: '>=18'} dependencies: - dir-glob: 3.0.1 + '@sindresorhus/merge-streams': 2.3.0 fast-glob: 3.3.2 ignore: 5.3.1 - merge2: 1.4.1 - slash: 4.0.0 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 dev: true /gopd@1.0.1: @@ -5532,6 +5564,11 @@ packages: engines: {node: '>=10.17.0'} dev: true + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} dependencies: @@ -5745,10 +5782,6 @@ packages: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} dev: true - /is-error@2.2.2: - resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} - dev: true - /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5802,16 +5835,6 @@ packages: engines: {node: '>=0.12.0'} dev: true - /is-path-cwd@3.0.0: - resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - - /is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} - dev: true - /is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -5862,6 +5885,11 @@ packages: engines: {node: '>=8'} dev: true + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -5888,9 +5916,9 @@ packages: engines: {node: '>=10'} dev: true - /is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} + /is-unicode-supported@2.0.0: + resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} + engines: {node: '>=18'} dev: true /is-weakref@1.0.2: @@ -6115,13 +6143,6 @@ packages: p-locate: 5.0.0 dev: true - /locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-locate: 6.0.0 - dev: true - /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true @@ -6194,13 +6215,6 @@ packages: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /map-age-cleaner@0.1.3: - resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} - engines: {node: '>=6'} - dependencies: - p-defer: 1.0.0 - dev: true - /markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} @@ -6363,12 +6377,11 @@ packages: engines: {node: '>= 0.6'} dev: true - /mem@9.0.2: - resolution: {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==} - engines: {node: '>=12.20'} + /memoize@10.0.0: + resolution: {integrity: sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==} + engines: {node: '>=18'} dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 4.0.0 + mimic-function: 5.0.1 dev: true /merge-descriptors@1.0.1: @@ -6696,6 +6709,11 @@ packages: engines: {node: '>=12'} dev: true + /mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + dev: true + /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -6830,6 +6848,7 @@ packages: /node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + requiresBuild: true dev: true optional: true @@ -6862,6 +6881,11 @@ packages: hasBin: true dev: true + /node-gyp-build@4.8.1: + resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + hasBin: true + dev: true + /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true @@ -6896,6 +6920,13 @@ packages: path-key: 3.1.1 dev: true + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: @@ -6967,6 +6998,13 @@ packages: mimic-fn: 2.1.0 dev: true + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /optionator@0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} engines: {node: '>= 0.8.0'} @@ -7020,18 +7058,6 @@ packages: engines: {node: '>=8'} dev: true - /p-defer@1.0.0: - resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} - engines: {node: '>=4'} - dev: true - - /p-event@5.0.1: - resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-timeout: 5.1.0 - dev: true - /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -7039,13 +7065,6 @@ packages: yocto-queue: 0.1.0 dev: true - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - yocto-queue: 1.0.0 - dev: true - /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -7053,13 +7072,6 @@ packages: p-limit: 3.1.0 dev: true - /p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-limit: 4.0.0 - dev: true - /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -7067,16 +7079,9 @@ packages: aggregate-error: 3.1.0 dev: true - /p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} - dependencies: - aggregate-error: 4.0.1 - dev: true - - /p-timeout@5.1.0: - resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} - engines: {node: '>=12'} + /p-map@7.0.2: + resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} + engines: {node: '>=18'} dev: true /pac-proxy-agent@5.0.0: @@ -7105,6 +7110,14 @@ packages: netmask: 2.0.2 dev: true + /package-config@5.0.0: + resolution: {integrity: sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==} + engines: {node: '>=18'} + dependencies: + find-up-simple: 1.0.0 + load-json-file: 7.0.1 + dev: true + /pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} dev: true @@ -7134,9 +7147,9 @@ packages: engines: {node: '>=6'} dev: true - /parse-ms@3.0.0: - resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} - engines: {node: '>=12'} + /parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} dev: true /parseurl@1.3.3: @@ -7153,11 +7166,6 @@ packages: engines: {node: '>=8'} dev: true - /path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -7168,6 +7176,11 @@ packages: engines: {node: '>=8'} dev: true + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -7189,6 +7202,11 @@ packages: engines: {node: '>=8'} dev: true + /path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + dev: true + /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} dev: true @@ -7218,12 +7236,9 @@ packages: engines: {node: '>=8.6'} dev: true - /pkg-conf@4.0.0: - resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - find-up: 6.3.0 - load-json-file: 7.0.1 + /picomatch@3.0.1: + resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} + engines: {node: '>=10'} dev: true /pkg-types@1.0.3: @@ -7356,9 +7371,9 @@ packages: hasBin: true dev: true - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} hasBin: true dev: true @@ -7374,11 +7389,11 @@ packages: parse-ms: 2.1.0 dev: true - /pretty-ms@8.0.0: - resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} - engines: {node: '>=14.16'} + /pretty-ms@9.0.0: + resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} + engines: {node: '>=18'} dependencies: - parse-ms: 3.0.0 + parse-ms: 4.0.0 dev: true /process-nextick-args@2.0.1: @@ -7832,6 +7847,12 @@ packages: lru-cache: 6.0.0 dev: true + /semver@7.6.1: + resolution: {integrity: sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==} + engines: {node: '>=10'} + hasBin: true + dev: true + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -7950,9 +7971,9 @@ packages: engines: {node: '>=8'} dev: true - /slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} dev: true /slice-ansi@5.0.0: @@ -8071,12 +8092,12 @@ packages: strip-ansi: 6.0.1 dev: true - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + /string-width@7.1.0: + resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + engines: {node: '>=18'} dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 dev: true @@ -8153,6 +8174,11 @@ packages: engines: {node: '>=6'} dev: true + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -8542,6 +8568,11 @@ packages: engines: {node: '>=4'} dev: true + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: true + /unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -9030,15 +9061,6 @@ packages: engines: {node: '>=10'} dev: true - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: true - /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: true - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false diff --git a/clients/js/src/authority.ts b/clients/js/src/authority.ts index d38401d9..fb79a833 100644 --- a/clients/js/src/authority.ts +++ b/clients/js/src/authority.ts @@ -1,32 +1,35 @@ import { PublicKey } from '@metaplex-foundation/umi'; -import { - PluginAuthority, - pluginAuthority, -} from './generated/types/pluginAuthority'; -import { BasePluginAuthority } from './types'; +import { pluginAuthority } from './plugins'; -// Authorities data helpers +/** + * @deprecated use SDK v1 methods like `create` or `update` instead of `createV1` or `updateV1`. The new methods no longer require this helper + * + * @returns umi plugin authority with type 'None' + */ export function nonePluginAuthority() { return pluginAuthority('None'); } +/** + * @deprecated use SDK v1 methods like `create` or `update` instead of `createV1` or `updateV1`. The new methods no longer require this helper + * @returns umi plugin authority with type 'None' + */ export function ownerPluginAuthority() { return pluginAuthority('Owner'); } +/** + * @deprecated use SDK v1 methods like `create` or `update` instead of `createV1` or `updateV1`. The new methods no longer require this helper + * @returns umi plugin authority with type 'UpdateAuthority' + */ export function updatePluginAuthority() { return pluginAuthority('UpdateAuthority'); } +/** + * @deprecated use SDK v1 methods like `create` or `update` instead of `createV1` or `updateV1`. The new methods no longer require this helper + * @returns umi plugin authority with type 'Address' + */ export function addressPluginAuthority(address: PublicKey) { return pluginAuthority('Address', { address }); } - -export function mapPluginAuthority( - authority: PluginAuthority -): BasePluginAuthority { - return { - type: authority.__kind, - address: (authority as any).address, - }; -} diff --git a/clients/js/src/demo.ts b/clients/js/src/demo.ts index 761d6c5a..3c74a5f1 100644 --- a/clients/js/src/demo.ts +++ b/clients/js/src/demo.ts @@ -1,20 +1,17 @@ // @ts-nocheck import { createUmi, generateSigner } from '@metaplex-foundation/umi'; import { - PluginType, - addPluginV1, - createV1, - createCollectionV1, + create, + createCollection, fetchAssetV1, getAssetV1GpaBuilder, - addressPluginAuthority, - pluginAuthorityPair, - revokePluginAuthorityV1, ruleSet, - transferV1, + transfer, updateAuthority, - createPlugin, Key, + fetchCollectionV1, + addPlugin, + revokePluginAuthority, } from './index'; const example = async () => { @@ -24,49 +21,52 @@ const example = async () => { const assetAddress = generateSigner(umi); const owner = generateSigner(umi); - await createV1(umi, { + await create(umi, { name: 'Test Asset', uri: 'https://example.com/asset.json', asset: assetAddress, owner: owner.publicKey, // optional, will default to payer }).sendAndConfirm(umi); + // Fetch an asset + const asset = await fetchAssetV1(umi, assetAddress.publicKey); + // Create a collection const collectionUpdateAuthority = generateSigner(umi); const collectionAddress = generateSigner(umi); - await createCollectionV1(umi, { + await createCollection(umi, { name: 'Test Collection', uri: 'https://example.com/collection.json', collection: collectionAddress, updateAuthority: collectionUpdateAuthority.publicKey, // optional, defaults to payer }).sendAndConfirm(umi); + // Fetch a collection + const collection = await fetchCollectionV1(umi, collectionAddress.publicKey); + // Create an asset in a collection, the authority must be the updateAuthority of the collection - await createV1(umi, { + await create(umi, { name: 'Test Asset', uri: 'https://example.com/asset.json', asset: assetAddress, - collection: collectionAddress.publicKey, + collection, authority: collectionUpdateAuthority, // optional, defaults to payer }).sendAndConfirm(umi); // Transfer an asset const recipient = generateSigner(umi); - await transferV1(umi, { - asset: assetAddress.publicKey, + await transfer(umi, { + asset, newOwner: recipient.publicKey, }).sendAndConfirm(umi); // Transfer an asset in a collection - await transferV1(umi, { - asset: assetAddress.publicKey, + await transfer(umi, { + asset, newOwner: recipient.publicKey, - collection: collectionAddress.publicKey, + collection, }).sendAndConfirm(umi); - // Fetch an asset - const asset = await fetchAssetV1(umi, assetAddress.publicKey); - // GPA fetch assets by owner const assetsByOwner = await getAssetV1GpaBuilder(umi) .whereField('key', Key.AssetV1) @@ -93,26 +93,30 @@ const advancedExamples = async () => { const assetAddress = generateSigner(umi); const freezeDelegate = generateSigner(umi); - await addPluginV1(umi, { + await addPlugin(umi, { asset: assetAddress.publicKey, // adds the owner-managed freeze plugin to the asset - plugin: createPlugin({ + plugin: { type: 'FreezeDelegate', - data: { - frozen: true, + frozen: true, + + // Optionally set the authority to a delegate who can unfreeze. If unset, this will be the Owner + // This is functionally the same as calling addPlugin and approvePluginAuthority separately. + // Freezing with a delegate is commonly used for escrowless staking programs. + authority: { + type: 'Address', + address: freezeDelegate.publicKey, }, - }), - // Optionally set the authority to a delegate who can unfreeze. If unset, this will be the Owner - // This is functionally the same as calling addPlugin and approvePluginAuthority separately. - // Freezing with a delegate is commonly used for escrowless staking programs. - initAuthority: addressPluginAuthority(freezeDelegate.publicKey), + }, }).sendAndConfirm(umi); // Unfreezing an asset with a delegate // Revoking an authority will revert the authority back to the owner for owner-managed plugins - await revokePluginAuthorityV1(umi, { + await revokePluginAuthority(umi, { asset: assetAddress.publicKey, - pluginType: PluginType.FreezeDelegate, + plugin: { + type: 'FreezeDelegate', + }, authority: freezeDelegate, }).sendAndConfirm(umi); @@ -121,37 +125,35 @@ const advancedExamples = async () => { const creator1 = generateSigner(umi); const creator2 = generateSigner(umi); - await createCollectionV1(umi, { + await createCollection(umi, { name: 'Test Collection', uri: 'https://example.com/collection.json', collection: collectionAddress, plugins: [ - pluginAuthorityPair({ + { type: 'Royalties', - data: { - basisPoints: 500, - creators: [ - { - address: creator1.publicKey, - percentage: 20, - }, - { - address: creator2.publicKey, - percentage: 80, - }, - ], - ruleSet: ruleSet('None'), // Compatibility rule set - }, - }), + basisPoints: 500, + creators: [ + { + address: creator1.publicKey, + percentage: 20, + }, + { + address: creator2.publicKey, + percentage: 80, + }, + ], + ruleSet: ruleSet('None'), // Compatibility rule set + }, ], }).sendAndConfirm(umi); // Create an asset in a collection. // Assets in a collection will inherit the collection's authority-managed plugins, in this case the royalties plugin - await createV1(umi, { + await create(umi, { name: 'Test Asset', uri: 'https://example.com/asset.json', asset: assetAddress, - collection: collectionAddress.publicKey, + collection: await fetchCollectionV1(umi, collectionAddress.publicKey), }).sendAndConfirm(umi); }; diff --git a/clients/js/src/generated/accounts/assetV1.ts b/clients/js/src/generated/accounts/assetV1.ts index 3a1fc03a..67824c79 100644 --- a/clients/js/src/generated/accounts/assetV1.ts +++ b/clients/js/src/generated/accounts/assetV1.ts @@ -31,10 +31,10 @@ import { getAssetV1AccountDataSerializer, } from '../../hooked'; import { + BaseUpdateAuthorityArgs, KeyArgs, - UpdateAuthorityArgs, + getBaseUpdateAuthoritySerializer, getKeySerializer, - getUpdateAuthoritySerializer, } from '../types'; export type AssetV1 = Account; @@ -108,14 +108,14 @@ export function getAssetV1GpaBuilder( .registerFields<{ key: KeyArgs; owner: PublicKey; - updateAuthority: UpdateAuthorityArgs; + updateAuthority: BaseUpdateAuthorityArgs; name: string; uri: string; seq: OptionOrNullable; }>({ key: [0, getKeySerializer()], owner: [1, publicKeySerializer()], - updateAuthority: [33, getUpdateAuthoritySerializer()], + updateAuthority: [33, getBaseUpdateAuthoritySerializer()], name: [null, string()], uri: [null, string()], seq: [null, option(u64())], diff --git a/clients/js/src/generated/accounts/pluginRegistryV1.ts b/clients/js/src/generated/accounts/pluginRegistryV1.ts index 4edbbc33..92d2f87a 100644 --- a/clients/js/src/generated/accounts/pluginRegistryV1.ts +++ b/clients/js/src/generated/accounts/pluginRegistryV1.ts @@ -25,10 +25,10 @@ import { getPluginRegistryV1AccountDataSerializer, } from '../../hooked'; import { - ExternalPluginRecordArgs, + ExternalRegistryRecordArgs, KeyArgs, RegistryRecordArgs, - getExternalPluginRecordSerializer, + getExternalRegistryRecordSerializer, getKeySerializer, getRegistryRecordSerializer, } from '../types'; @@ -111,11 +111,11 @@ export function getPluginRegistryV1GpaBuilder( .registerFields<{ key: KeyArgs; registry: Array; - externalPlugins: Array; + externalRegistry: Array; }>({ key: [0, getKeySerializer()], registry: [1, array(getRegistryRecordSerializer())], - externalPlugins: [null, array(getExternalPluginRecordSerializer())], + externalRegistry: [null, array(getExternalRegistryRecordSerializer())], }) .deserializeUsing((account) => deserializePluginRegistryV1(account) diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index 54364142..a634ff54 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -433,6 +433,155 @@ export class InvalidLogWrapperProgramError extends ProgramError { codeToErrorMap.set(0x1e, InvalidLogWrapperProgramError); nameToErrorMap.set('InvalidLogWrapperProgram', InvalidLogWrapperProgramError); +/** ExternalPluginAdapterNotFound: External PluginExternalPluginAdapter not found */ +export class ExternalPluginAdapterNotFoundError extends ProgramError { + override readonly name: string = 'ExternalPluginAdapterNotFound'; + + readonly code: number = 0x1f; // 31 + + constructor(program: Program, cause?: Error) { + super('External PluginExternalPluginAdapter not found', program, cause); + } +} +codeToErrorMap.set(0x1f, ExternalPluginAdapterNotFoundError); +nameToErrorMap.set( + 'ExternalPluginAdapterNotFound', + ExternalPluginAdapterNotFoundError +); + +/** ExternalPluginAdapterAlreadyExists: External PluginExternalPluginAdapter already exists */ +export class ExternalPluginAdapterAlreadyExistsError extends ProgramError { + override readonly name: string = 'ExternalPluginAdapterAlreadyExists'; + + readonly code: number = 0x20; // 32 + + constructor(program: Program, cause?: Error) { + super( + 'External PluginExternalPluginAdapter already exists', + program, + cause + ); + } +} +codeToErrorMap.set(0x20, ExternalPluginAdapterAlreadyExistsError); +nameToErrorMap.set( + 'ExternalPluginAdapterAlreadyExists', + ExternalPluginAdapterAlreadyExistsError +); + +/** MissingAsset: Missing asset needed for extra account PDA derivation */ +export class MissingAssetError extends ProgramError { + override readonly name: string = 'MissingAsset'; + + readonly code: number = 0x21; // 33 + + constructor(program: Program, cause?: Error) { + super( + 'Missing asset needed for extra account PDA derivation', + program, + cause + ); + } +} +codeToErrorMap.set(0x21, MissingAssetError); +nameToErrorMap.set('MissingAsset', MissingAssetError); + +/** MissingExternalPluginAdapterAccount: Missing account needed for external plugin adapter */ +export class MissingExternalPluginAdapterAccountError extends ProgramError { + override readonly name: string = 'MissingExternalPluginAdapterAccount'; + + readonly code: number = 0x22; // 34 + + constructor(program: Program, cause?: Error) { + super('Missing account needed for external plugin adapter', program, cause); + } +} +codeToErrorMap.set(0x22, MissingExternalPluginAdapterAccountError); +nameToErrorMap.set( + 'MissingExternalPluginAdapterAccount', + MissingExternalPluginAdapterAccountError +); + +/** OracleCanRejectOnly: Oracle external plugin adapter can only be configured to reject */ +export class OracleCanRejectOnlyError extends ProgramError { + override readonly name: string = 'OracleCanRejectOnly'; + + readonly code: number = 0x23; // 35 + + constructor(program: Program, cause?: Error) { + super( + 'Oracle external plugin adapter can only be configured to reject', + program, + cause + ); + } +} +codeToErrorMap.set(0x23, OracleCanRejectOnlyError); +nameToErrorMap.set('OracleCanRejectOnly', OracleCanRejectOnlyError); + +/** RequiresLifecycleCheck: External plugin adapter must have at least one lifecycle check */ +export class RequiresLifecycleCheckError extends ProgramError { + override readonly name: string = 'RequiresLifecycleCheck'; + + readonly code: number = 0x24; // 36 + + constructor(program: Program, cause?: Error) { + super( + 'External plugin adapter must have at least one lifecycle check', + program, + cause + ); + } +} +codeToErrorMap.set(0x24, RequiresLifecycleCheckError); +nameToErrorMap.set('RequiresLifecycleCheck', RequiresLifecycleCheckError); + +/** DuplicateLifecycleChecks: Duplicate lifecycle checks were provided for external plugin adapter */ +export class DuplicateLifecycleChecksError extends ProgramError { + override readonly name: string = 'DuplicateLifecycleChecks'; + + readonly code: number = 0x25; // 37 + + constructor(program: Program, cause?: Error) { + super( + 'Duplicate lifecycle checks were provided for external plugin adapter ', + program, + cause + ); + } +} +codeToErrorMap.set(0x25, DuplicateLifecycleChecksError); +nameToErrorMap.set('DuplicateLifecycleChecks', DuplicateLifecycleChecksError); + +/** InvalidOracleAccountData: Could not read from oracle account */ +export class InvalidOracleAccountDataError extends ProgramError { + override readonly name: string = 'InvalidOracleAccountData'; + + readonly code: number = 0x26; // 38 + + constructor(program: Program, cause?: Error) { + super('Could not read from oracle account', program, cause); + } +} +codeToErrorMap.set(0x26, InvalidOracleAccountDataError); +nameToErrorMap.set('InvalidOracleAccountData', InvalidOracleAccountDataError); + +/** UninitializedOracleAccount: Oracle account is uninitialized */ +export class UninitializedOracleAccountError extends ProgramError { + override readonly name: string = 'UninitializedOracleAccount'; + + readonly code: number = 0x27; // 39 + + constructor(program: Program, cause?: Error) { + super('Oracle account is uninitialized', program, cause); + } +} +codeToErrorMap.set(0x27, UninitializedOracleAccountError); +nameToErrorMap.set( + 'UninitializedOracleAccount', + UninitializedOracleAccountError +); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/addCollectionExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/addCollectionExternalPluginAdapterV1.ts new file mode 100644 index 00000000..672e235b --- /dev/null +++ b/clients/js/src/generated/instructions/addCollectionExternalPluginAdapterV1.ts @@ -0,0 +1,167 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterInitInfo, + BaseExternalPluginAdapterInitInfoArgs, + getBaseExternalPluginAdapterInitInfoSerializer, +} from '../types'; + +// Accounts. +export type AddCollectionExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + collection: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type AddCollectionExternalPluginAdapterV1InstructionData = { + discriminator: number; + initInfo: BaseExternalPluginAdapterInitInfo; +}; + +export type AddCollectionExternalPluginAdapterV1InstructionDataArgs = { + initInfo: BaseExternalPluginAdapterInitInfoArgs; +}; + +export function getAddCollectionExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + AddCollectionExternalPluginAdapterV1InstructionDataArgs, + AddCollectionExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + AddCollectionExternalPluginAdapterV1InstructionDataArgs, + any, + AddCollectionExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['initInfo', getBaseExternalPluginAdapterInitInfoSerializer()], + ], + { description: 'AddCollectionExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 23 }) + ) as Serializer< + AddCollectionExternalPluginAdapterV1InstructionDataArgs, + AddCollectionExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type AddCollectionExternalPluginAdapterV1InstructionArgs = + AddCollectionExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function addCollectionExternalPluginAdapterV1( + context: Pick, + input: AddCollectionExternalPluginAdapterV1InstructionAccounts & + AddCollectionExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + collection: { + index: 0, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: AddCollectionExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getAddCollectionExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as AddCollectionExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addCollectionPluginV1.ts b/clients/js/src/generated/instructions/addCollectionPluginV1.ts index 099dc677..f612b44f 100644 --- a/clients/js/src/generated/instructions/addCollectionPluginV1.ts +++ b/clients/js/src/generated/instructions/addCollectionPluginV1.ts @@ -30,11 +30,11 @@ import { getAccountMetasAndSigners, } from '../shared'; import { + BasePluginAuthority, + BasePluginAuthorityArgs, Plugin, PluginArgs, - PluginAuthority, - PluginAuthorityArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginSerializer, } from '../types'; @@ -56,12 +56,12 @@ export type AddCollectionPluginV1InstructionAccounts = { export type AddCollectionPluginV1InstructionData = { discriminator: number; plugin: Plugin; - initAuthority: Option; + initAuthority: Option; }; export type AddCollectionPluginV1InstructionDataArgs = { plugin: PluginArgs; - initAuthority?: OptionOrNullable; + initAuthority?: OptionOrNullable; }; export function getAddCollectionPluginV1InstructionDataSerializer(): Serializer< @@ -77,7 +77,7 @@ export function getAddCollectionPluginV1InstructionDataSerializer(): Serializer< [ ['discriminator', u8()], ['plugin', getPluginSerializer()], - ['initAuthority', option(getPluginAuthoritySerializer())], + ['initAuthority', option(getBasePluginAuthoritySerializer())], ], { description: 'AddCollectionPluginV1InstructionData' } ), diff --git a/clients/js/src/generated/instructions/addExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/addExternalPluginAdapterV1.ts new file mode 100644 index 00000000..b8059a3d --- /dev/null +++ b/clients/js/src/generated/instructions/addExternalPluginAdapterV1.ts @@ -0,0 +1,172 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterInitInfo, + BaseExternalPluginAdapterInitInfoArgs, + getBaseExternalPluginAdapterInitInfoSerializer, +} from '../types'; + +// Accounts. +export type AddExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + asset: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type AddExternalPluginAdapterV1InstructionData = { + discriminator: number; + initInfo: BaseExternalPluginAdapterInitInfo; +}; + +export type AddExternalPluginAdapterV1InstructionDataArgs = { + initInfo: BaseExternalPluginAdapterInitInfoArgs; +}; + +export function getAddExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + AddExternalPluginAdapterV1InstructionDataArgs, + AddExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + AddExternalPluginAdapterV1InstructionDataArgs, + any, + AddExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['initInfo', getBaseExternalPluginAdapterInitInfoSerializer()], + ], + { description: 'AddExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 22 }) + ) as Serializer< + AddExternalPluginAdapterV1InstructionDataArgs, + AddExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type AddExternalPluginAdapterV1InstructionArgs = + AddExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function addExternalPluginAdapterV1( + context: Pick, + input: AddExternalPluginAdapterV1InstructionAccounts & + AddExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + asset: { + index: 0, + isWritable: true as boolean, + value: input.asset ?? null, + }, + collection: { + index: 1, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 3, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: AddExternalPluginAdapterV1InstructionArgs = { ...input }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getAddExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as AddExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/addPluginV1.ts b/clients/js/src/generated/instructions/addPluginV1.ts index 2c6bd77a..030a67f9 100644 --- a/clients/js/src/generated/instructions/addPluginV1.ts +++ b/clients/js/src/generated/instructions/addPluginV1.ts @@ -30,11 +30,11 @@ import { getAccountMetasAndSigners, } from '../shared'; import { + BasePluginAuthority, + BasePluginAuthorityArgs, Plugin, PluginArgs, - PluginAuthority, - PluginAuthorityArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginSerializer, } from '../types'; @@ -58,12 +58,12 @@ export type AddPluginV1InstructionAccounts = { export type AddPluginV1InstructionData = { discriminator: number; plugin: Plugin; - initAuthority: Option; + initAuthority: Option; }; export type AddPluginV1InstructionDataArgs = { plugin: PluginArgs; - initAuthority?: OptionOrNullable; + initAuthority?: OptionOrNullable; }; export function getAddPluginV1InstructionDataSerializer(): Serializer< @@ -79,7 +79,7 @@ export function getAddPluginV1InstructionDataSerializer(): Serializer< [ ['discriminator', u8()], ['plugin', getPluginSerializer()], - ['initAuthority', option(getPluginAuthoritySerializer())], + ['initAuthority', option(getBasePluginAuthoritySerializer())], ], { description: 'AddPluginV1InstructionData' } ), diff --git a/clients/js/src/generated/instructions/approveCollectionPluginAuthorityV1.ts b/clients/js/src/generated/instructions/approveCollectionPluginAuthorityV1.ts index 0613bef2..340730fe 100644 --- a/clients/js/src/generated/instructions/approveCollectionPluginAuthorityV1.ts +++ b/clients/js/src/generated/instructions/approveCollectionPluginAuthorityV1.ts @@ -26,11 +26,11 @@ import { getAccountMetasAndSigners, } from '../shared'; import { - PluginAuthority, - PluginAuthorityArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, PluginType, PluginTypeArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginTypeSerializer, } from '../types'; @@ -52,12 +52,12 @@ export type ApproveCollectionPluginAuthorityV1InstructionAccounts = { export type ApproveCollectionPluginAuthorityV1InstructionData = { discriminator: number; pluginType: PluginType; - newAuthority: PluginAuthority; + newAuthority: BasePluginAuthority; }; export type ApproveCollectionPluginAuthorityV1InstructionDataArgs = { pluginType: PluginTypeArgs; - newAuthority: PluginAuthorityArgs; + newAuthority: BasePluginAuthorityArgs; }; export function getApproveCollectionPluginAuthorityV1InstructionDataSerializer(): Serializer< @@ -73,7 +73,7 @@ export function getApproveCollectionPluginAuthorityV1InstructionDataSerializer() [ ['discriminator', u8()], ['pluginType', getPluginTypeSerializer()], - ['newAuthority', getPluginAuthoritySerializer()], + ['newAuthority', getBasePluginAuthoritySerializer()], ], { description: 'ApproveCollectionPluginAuthorityV1InstructionData' } ), diff --git a/clients/js/src/generated/instructions/approvePluginAuthorityV1.ts b/clients/js/src/generated/instructions/approvePluginAuthorityV1.ts index 00bb4744..6d92528a 100644 --- a/clients/js/src/generated/instructions/approvePluginAuthorityV1.ts +++ b/clients/js/src/generated/instructions/approvePluginAuthorityV1.ts @@ -26,11 +26,11 @@ import { getAccountMetasAndSigners, } from '../shared'; import { - PluginAuthority, - PluginAuthorityArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, PluginType, PluginTypeArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginTypeSerializer, } from '../types'; @@ -54,12 +54,12 @@ export type ApprovePluginAuthorityV1InstructionAccounts = { export type ApprovePluginAuthorityV1InstructionData = { discriminator: number; pluginType: PluginType; - newAuthority: PluginAuthority; + newAuthority: BasePluginAuthority; }; export type ApprovePluginAuthorityV1InstructionDataArgs = { pluginType: PluginTypeArgs; - newAuthority: PluginAuthorityArgs; + newAuthority: BasePluginAuthorityArgs; }; export function getApprovePluginAuthorityV1InstructionDataSerializer(): Serializer< @@ -75,7 +75,7 @@ export function getApprovePluginAuthorityV1InstructionDataSerializer(): Serializ [ ['discriminator', u8()], ['pluginType', getPluginTypeSerializer()], - ['newAuthority', getPluginAuthoritySerializer()], + ['newAuthority', getBasePluginAuthoritySerializer()], ], { description: 'ApprovePluginAuthorityV1InstructionData' } ), diff --git a/clients/js/src/generated/instructions/burnCollectionV1.ts b/clients/js/src/generated/instructions/burnCollectionV1.ts index 1aeff52f..94be0b72 100644 --- a/clients/js/src/generated/instructions/burnCollectionV1.ts +++ b/clients/js/src/generated/instructions/burnCollectionV1.ts @@ -108,7 +108,7 @@ export function burnCollectionV1( }, authority: { index: 2, - isWritable: false as boolean, + isWritable: true as boolean, value: input.authority ?? null, }, logWrapper: { diff --git a/clients/js/src/generated/instructions/burnV1.ts b/clients/js/src/generated/instructions/burnV1.ts index f4c7bec0..32cc9490 100644 --- a/clients/js/src/generated/instructions/burnV1.ts +++ b/clients/js/src/generated/instructions/burnV1.ts @@ -114,7 +114,7 @@ export function burnV1( }, authority: { index: 3, - isWritable: false as boolean, + isWritable: true as boolean, value: input.authority ?? null, }, systemProgram: { diff --git a/clients/js/src/generated/instructions/createCollectionV2.ts b/clients/js/src/generated/instructions/createCollectionV2.ts new file mode 100644 index 00000000..79555332 --- /dev/null +++ b/clients/js/src/generated/instructions/createCollectionV2.ts @@ -0,0 +1,185 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Option, + OptionOrNullable, + Pda, + PublicKey, + Signer, + TransactionBuilder, + none, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + mapSerializer, + option, + string, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterInitInfo, + BaseExternalPluginAdapterInitInfoArgs, + PluginAuthorityPair, + PluginAuthorityPairArgs, + getBaseExternalPluginAdapterInitInfoSerializer, + getPluginAuthorityPairSerializer, +} from '../types'; + +// Accounts. +export type CreateCollectionV2InstructionAccounts = { + /** The address of the new asset */ + collection: Signer; + /** The authority of the new asset */ + updateAuthority?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; +}; + +// Data. +export type CreateCollectionV2InstructionData = { + discriminator: number; + name: string; + uri: string; + plugins: Option>; + externalPluginAdapters: Option>; +}; + +export type CreateCollectionV2InstructionDataArgs = { + name: string; + uri: string; + plugins?: OptionOrNullable>; + externalPluginAdapters?: OptionOrNullable< + Array + >; +}; + +export function getCreateCollectionV2InstructionDataSerializer(): Serializer< + CreateCollectionV2InstructionDataArgs, + CreateCollectionV2InstructionData +> { + return mapSerializer< + CreateCollectionV2InstructionDataArgs, + any, + CreateCollectionV2InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['name', string()], + ['uri', string()], + ['plugins', option(array(getPluginAuthorityPairSerializer()))], + [ + 'externalPluginAdapters', + option(array(getBaseExternalPluginAdapterInitInfoSerializer())), + ], + ], + { description: 'CreateCollectionV2InstructionData' } + ), + (value) => ({ + ...value, + discriminator: 21, + plugins: value.plugins ?? none(), + externalPluginAdapters: value.externalPluginAdapters ?? [], + }) + ) as Serializer< + CreateCollectionV2InstructionDataArgs, + CreateCollectionV2InstructionData + >; +} + +// Args. +export type CreateCollectionV2InstructionArgs = + CreateCollectionV2InstructionDataArgs; + +// Instruction. +export function createCollectionV2( + context: Pick, + input: CreateCollectionV2InstructionAccounts & + CreateCollectionV2InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + collection: { + index: 0, + isWritable: true as boolean, + value: input.collection ?? null, + }, + updateAuthority: { + index: 1, + isWritable: false as boolean, + value: input.updateAuthority ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: CreateCollectionV2InstructionArgs = { ...input }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getCreateCollectionV2InstructionDataSerializer().serialize( + resolvedArgs as CreateCollectionV2InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/createV2.ts b/clients/js/src/generated/instructions/createV2.ts new file mode 100644 index 00000000..a5ca8707 --- /dev/null +++ b/clients/js/src/generated/instructions/createV2.ts @@ -0,0 +1,214 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Option, + OptionOrNullable, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + mapSerializer, + option, + string, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterInitInfo, + BaseExternalPluginAdapterInitInfoArgs, + DataState, + DataStateArgs, + PluginAuthorityPair, + PluginAuthorityPairArgs, + getBaseExternalPluginAdapterInitInfoSerializer, + getDataStateSerializer, + getPluginAuthorityPairSerializer, +} from '../types'; + +// Accounts. +export type CreateV2InstructionAccounts = { + /** The address of the new asset */ + asset: Signer; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The authority signing for creation */ + authority?: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner of the new asset. Defaults to the authority if not present. */ + owner?: PublicKey | Pda; + /** The authority on the new asset */ + updateAuthority?: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type CreateV2InstructionData = { + discriminator: number; + dataState: DataState; + name: string; + uri: string; + plugins: Option>; + externalPluginAdapters: Option>; +}; + +export type CreateV2InstructionDataArgs = { + dataState?: DataStateArgs; + name: string; + uri: string; + plugins?: OptionOrNullable>; + externalPluginAdapters?: OptionOrNullable< + Array + >; +}; + +export function getCreateV2InstructionDataSerializer(): Serializer< + CreateV2InstructionDataArgs, + CreateV2InstructionData +> { + return mapSerializer< + CreateV2InstructionDataArgs, + any, + CreateV2InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['dataState', getDataStateSerializer()], + ['name', string()], + ['uri', string()], + ['plugins', option(array(getPluginAuthorityPairSerializer()))], + [ + 'externalPluginAdapters', + option(array(getBaseExternalPluginAdapterInitInfoSerializer())), + ], + ], + { description: 'CreateV2InstructionData' } + ), + (value) => ({ + ...value, + discriminator: 20, + dataState: value.dataState ?? DataState.AccountState, + plugins: value.plugins ?? [], + externalPluginAdapters: value.externalPluginAdapters ?? [], + }) + ) as Serializer; +} + +// Args. +export type CreateV2InstructionArgs = CreateV2InstructionDataArgs; + +// Instruction. +export function createV2( + context: Pick, + input: CreateV2InstructionAccounts & CreateV2InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + asset: { + index: 0, + isWritable: true as boolean, + value: input.asset ?? null, + }, + collection: { + index: 1, + isWritable: true as boolean, + value: input.collection ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + payer: { + index: 3, + isWritable: true as boolean, + value: input.payer ?? null, + }, + owner: { + index: 4, + isWritable: false as boolean, + value: input.owner ?? null, + }, + updateAuthority: { + index: 5, + isWritable: false as boolean, + value: input.updateAuthority ?? null, + }, + systemProgram: { + index: 6, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 7, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: CreateV2InstructionArgs = { ...input }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getCreateV2InstructionDataSerializer().serialize( + resolvedArgs as CreateV2InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index b15a1f56..6b62997e 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -6,7 +6,9 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './addCollectionExternalPluginAdapterV1'; export * from './addCollectionPluginV1'; +export * from './addExternalPluginAdapterV1'; export * from './addPluginV1'; export * from './approveCollectionPluginAuthorityV1'; export * from './approvePluginAuthorityV1'; @@ -15,14 +17,22 @@ export * from './burnV1'; export * from './collect'; export * from './compressV1'; export * from './createCollectionV1'; +export * from './createCollectionV2'; export * from './createV1'; +export * from './createV2'; export * from './decompressV1'; +export * from './removeCollectionExternalPluginAdapterV1'; export * from './removeCollectionPluginV1'; +export * from './removeExternalPluginAdapterV1'; export * from './removePluginV1'; export * from './revokeCollectionPluginAuthorityV1'; export * from './revokePluginAuthorityV1'; export * from './transferV1'; +export * from './updateCollectionExternalPluginAdapterV1'; export * from './updateCollectionPluginV1'; export * from './updateCollectionV1'; +export * from './updateExternalPluginAdapterV1'; export * from './updatePluginV1'; export * from './updateV1'; +export * from './writeCollectionExternalPluginAdapterDataV1'; +export * from './writeExternalPluginAdapterDataV1'; diff --git a/clients/js/src/generated/instructions/removeCollectionExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/removeCollectionExternalPluginAdapterV1.ts new file mode 100644 index 00000000..e414ba71 --- /dev/null +++ b/clients/js/src/generated/instructions/removeCollectionExternalPluginAdapterV1.ts @@ -0,0 +1,167 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type RemoveCollectionExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + collection: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type RemoveCollectionExternalPluginAdapterV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; +}; + +export type RemoveCollectionExternalPluginAdapterV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; +}; + +export function getRemoveCollectionExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + RemoveCollectionExternalPluginAdapterV1InstructionDataArgs, + RemoveCollectionExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + RemoveCollectionExternalPluginAdapterV1InstructionDataArgs, + any, + RemoveCollectionExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ], + { description: 'RemoveCollectionExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 25 }) + ) as Serializer< + RemoveCollectionExternalPluginAdapterV1InstructionDataArgs, + RemoveCollectionExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type RemoveCollectionExternalPluginAdapterV1InstructionArgs = + RemoveCollectionExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function removeCollectionExternalPluginAdapterV1( + context: Pick, + input: RemoveCollectionExternalPluginAdapterV1InstructionAccounts & + RemoveCollectionExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + collection: { + index: 0, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveCollectionExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getRemoveCollectionExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveCollectionExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/removeExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/removeExternalPluginAdapterV1.ts new file mode 100644 index 00000000..92349dfe --- /dev/null +++ b/clients/js/src/generated/instructions/removeExternalPluginAdapterV1.ts @@ -0,0 +1,174 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type RemoveExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + asset: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type RemoveExternalPluginAdapterV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; +}; + +export type RemoveExternalPluginAdapterV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; +}; + +export function getRemoveExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + RemoveExternalPluginAdapterV1InstructionDataArgs, + RemoveExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + RemoveExternalPluginAdapterV1InstructionDataArgs, + any, + RemoveExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ], + { description: 'RemoveExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 24 }) + ) as Serializer< + RemoveExternalPluginAdapterV1InstructionDataArgs, + RemoveExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type RemoveExternalPluginAdapterV1InstructionArgs = + RemoveExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function removeExternalPluginAdapterV1( + context: Pick, + input: RemoveExternalPluginAdapterV1InstructionAccounts & + RemoveExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + asset: { + index: 0, + isWritable: true as boolean, + value: input.asset ?? null, + }, + collection: { + index: 1, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 3, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: RemoveExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getRemoveExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as RemoveExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/updateCollectionExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/updateCollectionExternalPluginAdapterV1.ts new file mode 100644 index 00000000..c04c8e08 --- /dev/null +++ b/clients/js/src/generated/instructions/updateCollectionExternalPluginAdapterV1.ts @@ -0,0 +1,173 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + BaseExternalPluginAdapterUpdateInfo, + BaseExternalPluginAdapterUpdateInfoArgs, + getBaseExternalPluginAdapterKeySerializer, + getBaseExternalPluginAdapterUpdateInfoSerializer, +} from '../types'; + +// Accounts. +export type UpdateCollectionExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + collection: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type UpdateCollectionExternalPluginAdapterV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; + updateInfo: BaseExternalPluginAdapterUpdateInfo; +}; + +export type UpdateCollectionExternalPluginAdapterV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; + updateInfo: BaseExternalPluginAdapterUpdateInfoArgs; +}; + +export function getUpdateCollectionExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + UpdateCollectionExternalPluginAdapterV1InstructionDataArgs, + UpdateCollectionExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + UpdateCollectionExternalPluginAdapterV1InstructionDataArgs, + any, + UpdateCollectionExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ['updateInfo', getBaseExternalPluginAdapterUpdateInfoSerializer()], + ], + { description: 'UpdateCollectionExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 27 }) + ) as Serializer< + UpdateCollectionExternalPluginAdapterV1InstructionDataArgs, + UpdateCollectionExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type UpdateCollectionExternalPluginAdapterV1InstructionArgs = + UpdateCollectionExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function updateCollectionExternalPluginAdapterV1( + context: Pick, + input: UpdateCollectionExternalPluginAdapterV1InstructionAccounts & + UpdateCollectionExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + collection: { + index: 0, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: UpdateCollectionExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getUpdateCollectionExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as UpdateCollectionExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/updateCollectionV1.ts b/clients/js/src/generated/instructions/updateCollectionV1.ts index 6ac91f0d..e31cbee4 100644 --- a/clients/js/src/generated/instructions/updateCollectionV1.ts +++ b/clients/js/src/generated/instructions/updateCollectionV1.ts @@ -14,6 +14,7 @@ import { PublicKey, Signer, TransactionBuilder, + none, transactionBuilder, } from '@metaplex-foundation/umi'; import { @@ -54,8 +55,8 @@ export type UpdateCollectionV1InstructionData = { }; export type UpdateCollectionV1InstructionDataArgs = { - newName: OptionOrNullable; - newUri: OptionOrNullable; + newName?: OptionOrNullable; + newUri?: OptionOrNullable; }; export function getUpdateCollectionV1InstructionDataSerializer(): Serializer< @@ -75,7 +76,12 @@ export function getUpdateCollectionV1InstructionDataSerializer(): Serializer< ], { description: 'UpdateCollectionV1InstructionData' } ), - (value) => ({ ...value, discriminator: 16 }) + (value) => ({ + ...value, + discriminator: 16, + newName: value.newName ?? none(), + newUri: value.newUri ?? none(), + }) ) as Serializer< UpdateCollectionV1InstructionDataArgs, UpdateCollectionV1InstructionData diff --git a/clients/js/src/generated/instructions/updateExternalPluginAdapterV1.ts b/clients/js/src/generated/instructions/updateExternalPluginAdapterV1.ts new file mode 100644 index 00000000..53c61537 --- /dev/null +++ b/clients/js/src/generated/instructions/updateExternalPluginAdapterV1.ts @@ -0,0 +1,180 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + BaseExternalPluginAdapterUpdateInfo, + BaseExternalPluginAdapterUpdateInfoArgs, + getBaseExternalPluginAdapterKeySerializer, + getBaseExternalPluginAdapterUpdateInfoSerializer, +} from '../types'; + +// Accounts. +export type UpdateExternalPluginAdapterV1InstructionAccounts = { + /** The address of the asset */ + asset: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type UpdateExternalPluginAdapterV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; + updateInfo: BaseExternalPluginAdapterUpdateInfo; +}; + +export type UpdateExternalPluginAdapterV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; + updateInfo: BaseExternalPluginAdapterUpdateInfoArgs; +}; + +export function getUpdateExternalPluginAdapterV1InstructionDataSerializer(): Serializer< + UpdateExternalPluginAdapterV1InstructionDataArgs, + UpdateExternalPluginAdapterV1InstructionData +> { + return mapSerializer< + UpdateExternalPluginAdapterV1InstructionDataArgs, + any, + UpdateExternalPluginAdapterV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ['updateInfo', getBaseExternalPluginAdapterUpdateInfoSerializer()], + ], + { description: 'UpdateExternalPluginAdapterV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 26 }) + ) as Serializer< + UpdateExternalPluginAdapterV1InstructionDataArgs, + UpdateExternalPluginAdapterV1InstructionData + >; +} + +// Args. +export type UpdateExternalPluginAdapterV1InstructionArgs = + UpdateExternalPluginAdapterV1InstructionDataArgs; + +// Instruction. +export function updateExternalPluginAdapterV1( + context: Pick, + input: UpdateExternalPluginAdapterV1InstructionAccounts & + UpdateExternalPluginAdapterV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + asset: { + index: 0, + isWritable: true as boolean, + value: input.asset ?? null, + }, + collection: { + index: 1, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 3, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: UpdateExternalPluginAdapterV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getUpdateExternalPluginAdapterV1InstructionDataSerializer().serialize( + resolvedArgs as UpdateExternalPluginAdapterV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/updateV1.ts b/clients/js/src/generated/instructions/updateV1.ts index 18db5c77..1be587a6 100644 --- a/clients/js/src/generated/instructions/updateV1.ts +++ b/clients/js/src/generated/instructions/updateV1.ts @@ -31,9 +31,9 @@ import { getAccountMetasAndSigners, } from '../shared'; import { - UpdateAuthority, - UpdateAuthorityArgs, - getUpdateAuthoritySerializer, + BaseUpdateAuthority, + BaseUpdateAuthorityArgs, + getBaseUpdateAuthoritySerializer, } from '../types'; // Accounts. @@ -57,13 +57,13 @@ export type UpdateV1InstructionData = { discriminator: number; newName: Option; newUri: Option; - newUpdateAuthority: Option; + newUpdateAuthority: Option; }; export type UpdateV1InstructionDataArgs = { - newName: OptionOrNullable; - newUri: OptionOrNullable; - newUpdateAuthority?: OptionOrNullable; + newName?: OptionOrNullable; + newUri?: OptionOrNullable; + newUpdateAuthority?: OptionOrNullable; }; export function getUpdateV1InstructionDataSerializer(): Serializer< @@ -80,13 +80,15 @@ export function getUpdateV1InstructionDataSerializer(): Serializer< ['discriminator', u8()], ['newName', option(string())], ['newUri', option(string())], - ['newUpdateAuthority', option(getUpdateAuthoritySerializer())], + ['newUpdateAuthority', option(getBaseUpdateAuthoritySerializer())], ], { description: 'UpdateV1InstructionData' } ), (value) => ({ ...value, discriminator: 15, + newName: value.newName ?? none(), + newUri: value.newUri ?? none(), newUpdateAuthority: value.newUpdateAuthority ?? none(), }) ) as Serializer; diff --git a/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts b/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts new file mode 100644 index 00000000..1397b69a --- /dev/null +++ b/clients/js/src/generated/instructions/writeCollectionExternalPluginAdapterDataV1.ts @@ -0,0 +1,174 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + bytes, + mapSerializer, + struct, + u32, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type WriteCollectionExternalPluginAdapterDataV1InstructionAccounts = { + /** The address of the asset */ + collection: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The Data Authority of the External PluginExternalPluginAdapter */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type WriteCollectionExternalPluginAdapterDataV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; + data: Uint8Array; +}; + +export type WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; + data: Uint8Array; +}; + +export function getWriteCollectionExternalPluginAdapterDataV1InstructionDataSerializer(): Serializer< + WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs, + WriteCollectionExternalPluginAdapterDataV1InstructionData +> { + return mapSerializer< + WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs, + any, + WriteCollectionExternalPluginAdapterDataV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ['data', bytes({ size: u32() })], + ], + { + description: + 'WriteCollectionExternalPluginAdapterDataV1InstructionData', + } + ), + (value) => ({ ...value, discriminator: 29 }) + ) as Serializer< + WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs, + WriteCollectionExternalPluginAdapterDataV1InstructionData + >; +} + +// Args. +export type WriteCollectionExternalPluginAdapterDataV1InstructionArgs = + WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs; + +// Instruction. +export function writeCollectionExternalPluginAdapterDataV1( + context: Pick, + input: WriteCollectionExternalPluginAdapterDataV1InstructionAccounts & + WriteCollectionExternalPluginAdapterDataV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + collection: { + index: 0, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 1, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 2, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 3, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: WriteCollectionExternalPluginAdapterDataV1InstructionArgs = + { ...input }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getWriteCollectionExternalPluginAdapterDataV1InstructionDataSerializer().serialize( + resolvedArgs as WriteCollectionExternalPluginAdapterDataV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts b/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts new file mode 100644 index 00000000..78e639ba --- /dev/null +++ b/clients/js/src/generated/instructions/writeExternalPluginAdapterDataV1.ts @@ -0,0 +1,179 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + bytes, + mapSerializer, + struct, + u32, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterKeyArgs, + getBaseExternalPluginAdapterKeySerializer, +} from '../types'; + +// Accounts. +export type WriteExternalPluginAdapterDataV1InstructionAccounts = { + /** The address of the asset */ + asset: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The account paying for the storage fees */ + payer?: Signer; + /** The Data Authority of the External PluginExternalPluginAdapter */ + authority?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type WriteExternalPluginAdapterDataV1InstructionData = { + discriminator: number; + key: BaseExternalPluginAdapterKey; + data: Uint8Array; +}; + +export type WriteExternalPluginAdapterDataV1InstructionDataArgs = { + key: BaseExternalPluginAdapterKeyArgs; + data: Uint8Array; +}; + +export function getWriteExternalPluginAdapterDataV1InstructionDataSerializer(): Serializer< + WriteExternalPluginAdapterDataV1InstructionDataArgs, + WriteExternalPluginAdapterDataV1InstructionData +> { + return mapSerializer< + WriteExternalPluginAdapterDataV1InstructionDataArgs, + any, + WriteExternalPluginAdapterDataV1InstructionData + >( + struct( + [ + ['discriminator', u8()], + ['key', getBaseExternalPluginAdapterKeySerializer()], + ['data', bytes({ size: u32() })], + ], + { description: 'WriteExternalPluginAdapterDataV1InstructionData' } + ), + (value) => ({ ...value, discriminator: 28 }) + ) as Serializer< + WriteExternalPluginAdapterDataV1InstructionDataArgs, + WriteExternalPluginAdapterDataV1InstructionData + >; +} + +// Args. +export type WriteExternalPluginAdapterDataV1InstructionArgs = + WriteExternalPluginAdapterDataV1InstructionDataArgs; + +// Instruction. +export function writeExternalPluginAdapterDataV1( + context: Pick, + input: WriteExternalPluginAdapterDataV1InstructionAccounts & + WriteExternalPluginAdapterDataV1InstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplCore', + 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d' + ); + + // Accounts. + const resolvedAccounts = { + asset: { + index: 0, + isWritable: true as boolean, + value: input.asset ?? null, + }, + collection: { + index: 1, + isWritable: true as boolean, + value: input.collection ?? null, + }, + payer: { + index: 2, + isWritable: true as boolean, + value: input.payer ?? null, + }, + authority: { + index: 3, + isWritable: false as boolean, + value: input.authority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, + value: input.logWrapper ?? null, + }, + } satisfies ResolvedAccountsWithIndices; + + // Arguments. + const resolvedArgs: WriteExternalPluginAdapterDataV1InstructionArgs = { + ...input, + }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = + getWriteExternalPluginAdapterDataV1InstructionDataSerializer().serialize( + resolvedArgs as WriteExternalPluginAdapterDataV1InstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/types/assetV1AccountData.ts b/clients/js/src/generated/types/assetV1AccountData.ts index 9f4ae127..b1a2dd48 100644 --- a/clients/js/src/generated/types/assetV1AccountData.ts +++ b/clients/js/src/generated/types/assetV1AccountData.ts @@ -16,18 +16,18 @@ import { u64, } from '@metaplex-foundation/umi/serializers'; import { + BaseUpdateAuthority, + BaseUpdateAuthorityArgs, Key, KeyArgs, - UpdateAuthority, - UpdateAuthorityArgs, + getBaseUpdateAuthoritySerializer, getKeySerializer, - getUpdateAuthoritySerializer, } from '.'; export type AssetV1AccountData = { key: Key; owner: PublicKey; - updateAuthority: UpdateAuthority; + updateAuthority: BaseUpdateAuthority; name: string; uri: string; seq: Option; @@ -36,7 +36,7 @@ export type AssetV1AccountData = { export type AssetV1AccountDataArgs = { key: KeyArgs; owner: PublicKey; - updateAuthority: UpdateAuthorityArgs; + updateAuthority: BaseUpdateAuthorityArgs; name: string; uri: string; seq: OptionOrNullable; @@ -50,7 +50,7 @@ export function getAssetV1AccountDataSerializer(): Serializer< [ ['key', getKeySerializer()], ['owner', publicKeySerializer()], - ['updateAuthority', getUpdateAuthoritySerializer()], + ['updateAuthority', getBaseUpdateAuthoritySerializer()], ['name', string()], ['uri', string()], ['seq', option(u64())], diff --git a/clients/js/src/generated/types/baseDataStore.ts b/clients/js/src/generated/types/baseDataStore.ts new file mode 100644 index 00000000..e83eff4d --- /dev/null +++ b/clients/js/src/generated/types/baseDataStore.ts @@ -0,0 +1,40 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseDataStore = { + dataAuthority: BasePluginAuthority; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseDataStoreArgs = { + dataAuthority: BasePluginAuthorityArgs; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseDataStoreSerializer(): Serializer< + BaseDataStoreArgs, + BaseDataStore +> { + return struct( + [ + ['dataAuthority', getBasePluginAuthoritySerializer()], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseDataStore' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseDataStoreInitInfo.ts b/clients/js/src/generated/types/baseDataStoreInitInfo.ts new file mode 100644 index 00000000..b23c72f6 --- /dev/null +++ b/clients/js/src/generated/types/baseDataStoreInitInfo.ts @@ -0,0 +1,48 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseDataStoreInitInfo = { + dataAuthority: BasePluginAuthority; + initPluginAuthority: Option; + schema: Option; +}; + +export type BaseDataStoreInitInfoArgs = { + dataAuthority: BasePluginAuthorityArgs; + initPluginAuthority: OptionOrNullable; + schema: OptionOrNullable; +}; + +export function getBaseDataStoreInitInfoSerializer(): Serializer< + BaseDataStoreInitInfoArgs, + BaseDataStoreInitInfo +> { + return struct( + [ + ['dataAuthority', getBasePluginAuthoritySerializer()], + ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseDataStoreInitInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseDataStoreUpdateInfo.ts b/clients/js/src/generated/types/baseDataStoreUpdateInfo.ts new file mode 100644 index 00000000..53b65c4c --- /dev/null +++ b/clients/js/src/generated/types/baseDataStoreUpdateInfo.ts @@ -0,0 +1,37 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseDataStoreUpdateInfo = { + schema: Option; +}; + +export type BaseDataStoreUpdateInfoArgs = { + schema: OptionOrNullable; +}; + +export function getBaseDataStoreUpdateInfoSerializer(): Serializer< + BaseDataStoreUpdateInfoArgs, + BaseDataStoreUpdateInfo +> { + return struct( + [['schema', option(getExternalPluginAdapterSchemaSerializer())]], + { description: 'BaseDataStoreUpdateInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts b/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts new file mode 100644 index 00000000..2649b73a --- /dev/null +++ b/clients/js/src/generated/types/baseExternalPluginAdapterInitInfo.ts @@ -0,0 +1,113 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseDataStoreInitInfo, + BaseDataStoreInitInfoArgs, + BaseLifecycleHookInitInfo, + BaseLifecycleHookInitInfoArgs, + BaseOracleInitInfo, + BaseOracleInitInfoArgs, + getBaseDataStoreInitInfoSerializer, + getBaseLifecycleHookInitInfoSerializer, + getBaseOracleInitInfoSerializer, +} from '.'; + +export type BaseExternalPluginAdapterInitInfo = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookInitInfo] } + | { __kind: 'Oracle'; fields: [BaseOracleInitInfo] } + | { __kind: 'DataStore'; fields: [BaseDataStoreInitInfo] }; + +export type BaseExternalPluginAdapterInitInfoArgs = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookInitInfoArgs] } + | { __kind: 'Oracle'; fields: [BaseOracleInitInfoArgs] } + | { __kind: 'DataStore'; fields: [BaseDataStoreInitInfoArgs] }; + +export function getBaseExternalPluginAdapterInitInfoSerializer(): Serializer< + BaseExternalPluginAdapterInitInfoArgs, + BaseExternalPluginAdapterInitInfo +> { + return dataEnum( + [ + [ + 'LifecycleHook', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfo, + 'LifecycleHook' + > + >([['fields', tuple([getBaseLifecycleHookInitInfoSerializer()])]]), + ], + [ + 'Oracle', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseOracleInitInfoSerializer()])]]), + ], + [ + 'DataStore', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseDataStoreInitInfoSerializer()])]]), + ], + ], + { description: 'BaseExternalPluginAdapterInitInfo' } + ) as Serializer< + BaseExternalPluginAdapterInitInfoArgs, + BaseExternalPluginAdapterInitInfo + >; +} + +// Data Enum Helpers. +export function baseExternalPluginAdapterInitInfo( + kind: 'LifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'LifecycleHook' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterInitInfo( + kind: 'Oracle', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'Oracle' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterInitInfo( + kind: 'DataStore', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterInitInfoArgs, + 'DataStore' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterInitInfo< + K extends BaseExternalPluginAdapterInitInfoArgs['__kind'], +>( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseExternalPluginAdapterInitInfo< + K extends BaseExternalPluginAdapterInitInfo['__kind'], +>( + kind: K, + value: BaseExternalPluginAdapterInitInfo +): value is BaseExternalPluginAdapterInitInfo & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts b/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts new file mode 100644 index 00000000..1a57e653 --- /dev/null +++ b/clients/js/src/generated/types/baseExternalPluginAdapterKey.ts @@ -0,0 +1,106 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + getBasePluginAuthoritySerializer, +} from '.'; + +export type BaseExternalPluginAdapterKey = + | { __kind: 'LifecycleHook'; fields: [PublicKey] } + | { __kind: 'Oracle'; fields: [PublicKey] } + | { __kind: 'DataStore'; fields: [BasePluginAuthority] }; + +export type BaseExternalPluginAdapterKeyArgs = + | { __kind: 'LifecycleHook'; fields: [PublicKey] } + | { __kind: 'Oracle'; fields: [PublicKey] } + | { __kind: 'DataStore'; fields: [BasePluginAuthorityArgs] }; + +export function getBaseExternalPluginAdapterKeySerializer(): Serializer< + BaseExternalPluginAdapterKeyArgs, + BaseExternalPluginAdapterKey +> { + return dataEnum( + [ + [ + 'LifecycleHook', + struct< + GetDataEnumKindContent + >([['fields', tuple([publicKeySerializer()])]]), + ], + [ + 'Oracle', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + [ + 'DataStore', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBasePluginAuthoritySerializer()])]]), + ], + ], + { description: 'BaseExternalPluginAdapterKey' } + ) as Serializer< + BaseExternalPluginAdapterKeyArgs, + BaseExternalPluginAdapterKey + >; +} + +// Data Enum Helpers. +export function baseExternalPluginAdapterKey( + kind: 'LifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'LifecycleHook' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey( + kind: 'Oracle', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'Oracle' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey( + kind: 'DataStore', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterKeyArgs, + 'DataStore' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterKey< + K extends BaseExternalPluginAdapterKeyArgs['__kind'], +>( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseExternalPluginAdapterKey< + K extends BaseExternalPluginAdapterKey['__kind'], +>( + kind: K, + value: BaseExternalPluginAdapterKey +): value is BaseExternalPluginAdapterKey & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts b/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts new file mode 100644 index 00000000..4fc19360 --- /dev/null +++ b/clients/js/src/generated/types/baseExternalPluginAdapterUpdateInfo.ts @@ -0,0 +1,116 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseDataStoreUpdateInfo, + BaseDataStoreUpdateInfoArgs, + BaseLifecycleHookUpdateInfo, + BaseLifecycleHookUpdateInfoArgs, + BaseOracleUpdateInfo, + BaseOracleUpdateInfoArgs, + getBaseDataStoreUpdateInfoSerializer, + getBaseLifecycleHookUpdateInfoSerializer, + getBaseOracleUpdateInfoSerializer, +} from '.'; + +export type BaseExternalPluginAdapterUpdateInfo = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookUpdateInfo] } + | { __kind: 'Oracle'; fields: [BaseOracleUpdateInfo] } + | { __kind: 'DataStore'; fields: [BaseDataStoreUpdateInfo] }; + +export type BaseExternalPluginAdapterUpdateInfoArgs = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookUpdateInfoArgs] } + | { __kind: 'Oracle'; fields: [BaseOracleUpdateInfoArgs] } + | { __kind: 'DataStore'; fields: [BaseDataStoreUpdateInfoArgs] }; + +export function getBaseExternalPluginAdapterUpdateInfoSerializer(): Serializer< + BaseExternalPluginAdapterUpdateInfoArgs, + BaseExternalPluginAdapterUpdateInfo +> { + return dataEnum( + [ + [ + 'LifecycleHook', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfo, + 'LifecycleHook' + > + >([['fields', tuple([getBaseLifecycleHookUpdateInfoSerializer()])]]), + ], + [ + 'Oracle', + struct< + GetDataEnumKindContent + >([['fields', tuple([getBaseOracleUpdateInfoSerializer()])]]), + ], + [ + 'DataStore', + struct< + GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfo, + 'DataStore' + > + >([['fields', tuple([getBaseDataStoreUpdateInfoSerializer()])]]), + ], + ], + { description: 'BaseExternalPluginAdapterUpdateInfo' } + ) as Serializer< + BaseExternalPluginAdapterUpdateInfoArgs, + BaseExternalPluginAdapterUpdateInfo + >; +} + +// Data Enum Helpers. +export function baseExternalPluginAdapterUpdateInfo( + kind: 'LifecycleHook', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfoArgs, + 'LifecycleHook' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterUpdateInfo( + kind: 'Oracle', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfoArgs, + 'Oracle' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterUpdateInfo( + kind: 'DataStore', + data: GetDataEnumKindContent< + BaseExternalPluginAdapterUpdateInfoArgs, + 'DataStore' + >['fields'] +): GetDataEnumKind; +export function baseExternalPluginAdapterUpdateInfo< + K extends BaseExternalPluginAdapterUpdateInfoArgs['__kind'], +>( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseExternalPluginAdapterUpdateInfo< + K extends BaseExternalPluginAdapterUpdateInfo['__kind'], +>( + kind: K, + value: BaseExternalPluginAdapterUpdateInfo +): value is BaseExternalPluginAdapterUpdateInfo & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseExtraAccount.ts b/clients/js/src/generated/types/baseExtraAccount.ts new file mode 100644 index 00000000..68a3d576 --- /dev/null +++ b/clients/js/src/generated/types/baseExtraAccount.ts @@ -0,0 +1,182 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + array, + bool, + dataEnum, + option, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { BaseSeed, BaseSeedArgs, getBaseSeedSerializer } from '.'; + +export type BaseExtraAccount = + | { __kind: 'PreconfiguredProgram'; isSigner: boolean; isWritable: boolean } + | { + __kind: 'PreconfiguredCollection'; + isSigner: boolean; + isWritable: boolean; + } + | { __kind: 'PreconfiguredOwner'; isSigner: boolean; isWritable: boolean } + | { __kind: 'PreconfiguredRecipient'; isSigner: boolean; isWritable: boolean } + | { __kind: 'PreconfiguredAsset'; isSigner: boolean; isWritable: boolean } + | { + __kind: 'CustomPda'; + seeds: Array; + customProgramId: Option; + isSigner: boolean; + isWritable: boolean; + } + | { + __kind: 'Address'; + address: PublicKey; + isSigner: boolean; + isWritable: boolean; + }; + +export type BaseExtraAccountArgs = + | { __kind: 'PreconfiguredProgram'; isSigner: boolean; isWritable: boolean } + | { + __kind: 'PreconfiguredCollection'; + isSigner: boolean; + isWritable: boolean; + } + | { __kind: 'PreconfiguredOwner'; isSigner: boolean; isWritable: boolean } + | { __kind: 'PreconfiguredRecipient'; isSigner: boolean; isWritable: boolean } + | { __kind: 'PreconfiguredAsset'; isSigner: boolean; isWritable: boolean } + | { + __kind: 'CustomPda'; + seeds: Array; + customProgramId: OptionOrNullable; + isSigner: boolean; + isWritable: boolean; + } + | { + __kind: 'Address'; + address: PublicKey; + isSigner: boolean; + isWritable: boolean; + }; + +export function getBaseExtraAccountSerializer(): Serializer< + BaseExtraAccountArgs, + BaseExtraAccount +> { + return dataEnum( + [ + [ + 'PreconfiguredProgram', + struct< + GetDataEnumKindContent + >([ + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'PreconfiguredCollection', + struct< + GetDataEnumKindContent + >([ + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'PreconfiguredOwner', + struct>([ + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'PreconfiguredRecipient', + struct< + GetDataEnumKindContent + >([ + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'PreconfiguredAsset', + struct>([ + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'CustomPda', + struct>([ + ['seeds', array(getBaseSeedSerializer())], + ['customProgramId', option(publicKeySerializer())], + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + [ + 'Address', + struct>([ + ['address', publicKeySerializer()], + ['isSigner', bool()], + ['isWritable', bool()], + ]), + ], + ], + { description: 'BaseExtraAccount' } + ) as Serializer; +} + +// Data Enum Helpers. +export function baseExtraAccount( + kind: 'PreconfiguredProgram', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'PreconfiguredCollection', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'PreconfiguredOwner', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'PreconfiguredRecipient', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'PreconfiguredAsset', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'CustomPda', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: 'Address', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function baseExtraAccount( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseExtraAccount( + kind: K, + value: BaseExtraAccount +): value is BaseExtraAccount & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseLifecycleHook.ts b/clients/js/src/generated/types/baseLifecycleHook.ts new file mode 100644 index 00000000..51e1d737 --- /dev/null +++ b/clients/js/src/generated/types/baseLifecycleHook.ts @@ -0,0 +1,56 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + getBaseExtraAccountSerializer, + getBasePluginAuthoritySerializer, + getExternalPluginAdapterSchemaSerializer, +} from '.'; + +export type BaseLifecycleHook = { + hookedProgram: PublicKey; + extraAccounts: Option>; + dataAuthority: Option; + schema: ExternalPluginAdapterSchema; +}; + +export type BaseLifecycleHookArgs = { + hookedProgram: PublicKey; + extraAccounts: OptionOrNullable>; + dataAuthority: OptionOrNullable; + schema: ExternalPluginAdapterSchemaArgs; +}; + +export function getBaseLifecycleHookSerializer(): Serializer< + BaseLifecycleHookArgs, + BaseLifecycleHook +> { + return struct( + [ + ['hookedProgram', publicKeySerializer()], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['dataAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', getExternalPluginAdapterSchemaSerializer()], + ], + { description: 'BaseLifecycleHook' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLifecycleHookInitInfo.ts b/clients/js/src/generated/types/baseLifecycleHookInitInfo.ts new file mode 100644 index 00000000..d9cd9bca --- /dev/null +++ b/clients/js/src/generated/types/baseLifecycleHookInitInfo.ts @@ -0,0 +1,77 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getBasePluginAuthoritySerializer, + getExternalCheckResultSerializer, + getExternalPluginAdapterSchemaSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseLifecycleHookInitInfo = { + hookedProgram: PublicKey; + initPluginAuthority: Option; + lifecycleChecks: Array<[HookableLifecycleEvent, ExternalCheckResult]>; + extraAccounts: Option>; + dataAuthority: Option; + schema: Option; +}; + +export type BaseLifecycleHookInitInfoArgs = { + hookedProgram: PublicKey; + initPluginAuthority: OptionOrNullable; + lifecycleChecks: Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]>; + extraAccounts: OptionOrNullable>; + dataAuthority: OptionOrNullable; + schema: OptionOrNullable; +}; + +export function getBaseLifecycleHookInitInfoSerializer(): Serializer< + BaseLifecycleHookInitInfoArgs, + BaseLifecycleHookInitInfo +> { + return struct( + [ + ['hookedProgram', publicKeySerializer()], + ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], + [ + 'lifecycleChecks', + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ), + ], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['dataAuthority', option(getBasePluginAuthoritySerializer())], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseLifecycleHookInitInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseLifecycleHookUpdateInfo.ts b/clients/js/src/generated/types/baseLifecycleHookUpdateInfo.ts new file mode 100644 index 00000000..26450898 --- /dev/null +++ b/clients/js/src/generated/types/baseLifecycleHookUpdateInfo.ts @@ -0,0 +1,68 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + ExternalPluginAdapterSchema, + ExternalPluginAdapterSchemaArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getExternalCheckResultSerializer, + getExternalPluginAdapterSchemaSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseLifecycleHookUpdateInfo = { + lifecycleChecks: Option>; + extraAccounts: Option>; + schema: Option; +}; + +export type BaseLifecycleHookUpdateInfoArgs = { + lifecycleChecks: OptionOrNullable< + Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]> + >; + extraAccounts: OptionOrNullable>; + schema: OptionOrNullable; +}; + +export function getBaseLifecycleHookUpdateInfoSerializer(): Serializer< + BaseLifecycleHookUpdateInfoArgs, + BaseLifecycleHookUpdateInfo +> { + return struct( + [ + [ + 'lifecycleChecks', + option( + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ) + ), + ], + ['extraAccounts', option(array(getBaseExtraAccountSerializer()))], + ['schema', option(getExternalPluginAdapterSchemaSerializer())], + ], + { description: 'BaseLifecycleHookUpdateInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/masterEdition.ts b/clients/js/src/generated/types/baseMasterEdition.ts similarity index 69% rename from clients/js/src/generated/types/masterEdition.ts rename to clients/js/src/generated/types/baseMasterEdition.ts index f11ec2a2..87d53692 100644 --- a/clients/js/src/generated/types/masterEdition.ts +++ b/clients/js/src/generated/types/baseMasterEdition.ts @@ -15,28 +15,28 @@ import { u32, } from '@metaplex-foundation/umi/serializers'; -export type MasterEdition = { +export type BaseMasterEdition = { maxSupply: Option; name: Option; uri: Option; }; -export type MasterEditionArgs = { +export type BaseMasterEditionArgs = { maxSupply: OptionOrNullable; name: OptionOrNullable; uri: OptionOrNullable; }; -export function getMasterEditionSerializer(): Serializer< - MasterEditionArgs, - MasterEdition +export function getBaseMasterEditionSerializer(): Serializer< + BaseMasterEditionArgs, + BaseMasterEdition > { - return struct( + return struct( [ ['maxSupply', option(u32())], ['name', option(string())], ['uri', option(string())], ], - { description: 'MasterEdition' } - ) as Serializer; + { description: 'BaseMasterEdition' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/baseOracle.ts b/clients/js/src/generated/types/baseOracle.ts new file mode 100644 index 00000000..7b73d53f --- /dev/null +++ b/clients/js/src/generated/types/baseOracle.ts @@ -0,0 +1,49 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + option, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BaseValidationResultsOffset, + BaseValidationResultsOffsetArgs, + getBaseExtraAccountSerializer, + getBaseValidationResultsOffsetSerializer, +} from '.'; + +export type BaseOracle = { + baseAddress: PublicKey; + baseAddressConfig: Option; + resultsOffset: BaseValidationResultsOffset; +}; + +export type BaseOracleArgs = { + baseAddress: PublicKey; + baseAddressConfig: OptionOrNullable; + resultsOffset: BaseValidationResultsOffsetArgs; +}; + +export function getBaseOracleSerializer(): Serializer< + BaseOracleArgs, + BaseOracle +> { + return struct( + [ + ['baseAddress', publicKeySerializer()], + ['baseAddressConfig', option(getBaseExtraAccountSerializer())], + ['resultsOffset', getBaseValidationResultsOffsetSerializer()], + ], + { description: 'BaseOracle' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseOracleInitInfo.ts b/clients/js/src/generated/types/baseOracleInitInfo.ts new file mode 100644 index 00000000..4f046613 --- /dev/null +++ b/clients/js/src/generated/types/baseOracleInitInfo.ts @@ -0,0 +1,74 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, + BaseValidationResultsOffset, + BaseValidationResultsOffsetArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getBasePluginAuthoritySerializer, + getBaseValidationResultsOffsetSerializer, + getExternalCheckResultSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseOracleInitInfo = { + baseAddress: PublicKey; + initPluginAuthority: Option; + lifecycleChecks: Array<[HookableLifecycleEvent, ExternalCheckResult]>; + baseAddressConfig: Option; + resultsOffset: Option; +}; + +export type BaseOracleInitInfoArgs = { + baseAddress: PublicKey; + initPluginAuthority: OptionOrNullable; + lifecycleChecks: Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]>; + baseAddressConfig: OptionOrNullable; + resultsOffset: OptionOrNullable; +}; + +export function getBaseOracleInitInfoSerializer(): Serializer< + BaseOracleInitInfoArgs, + BaseOracleInitInfo +> { + return struct( + [ + ['baseAddress', publicKeySerializer()], + ['initPluginAuthority', option(getBasePluginAuthoritySerializer())], + [ + 'lifecycleChecks', + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ), + ], + ['baseAddressConfig', option(getBaseExtraAccountSerializer())], + ['resultsOffset', option(getBaseValidationResultsOffsetSerializer())], + ], + { description: 'BaseOracleInitInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/baseOracleUpdateInfo.ts b/clients/js/src/generated/types/baseOracleUpdateInfo.ts new file mode 100644 index 00000000..7483a374 --- /dev/null +++ b/clients/js/src/generated/types/baseOracleUpdateInfo.ts @@ -0,0 +1,68 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseExtraAccount, + BaseExtraAccountArgs, + BaseValidationResultsOffset, + BaseValidationResultsOffsetArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBaseExtraAccountSerializer, + getBaseValidationResultsOffsetSerializer, + getExternalCheckResultSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type BaseOracleUpdateInfo = { + lifecycleChecks: Option>; + baseAddressConfig: Option; + resultsOffset: Option; +}; + +export type BaseOracleUpdateInfoArgs = { + lifecycleChecks: OptionOrNullable< + Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]> + >; + baseAddressConfig: OptionOrNullable; + resultsOffset: OptionOrNullable; +}; + +export function getBaseOracleUpdateInfoSerializer(): Serializer< + BaseOracleUpdateInfoArgs, + BaseOracleUpdateInfo +> { + return struct( + [ + [ + 'lifecycleChecks', + option( + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ) + ), + ], + ['baseAddressConfig', option(getBaseExtraAccountSerializer())], + ['resultsOffset', option(getBaseValidationResultsOffsetSerializer())], + ], + { description: 'BaseOracleUpdateInfo' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/basePluginAuthority.ts b/clients/js/src/generated/types/basePluginAuthority.ts new file mode 100644 index 00000000..8a6a7002 --- /dev/null +++ b/clients/js/src/generated/types/basePluginAuthority.ts @@ -0,0 +1,74 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + publicKey as publicKeySerializer, + struct, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type BasePluginAuthority = + | { __kind: 'None' } + | { __kind: 'Owner' } + | { __kind: 'UpdateAuthority' } + | { __kind: 'Address'; address: PublicKey }; + +export type BasePluginAuthorityArgs = BasePluginAuthority; + +export function getBasePluginAuthoritySerializer(): Serializer< + BasePluginAuthorityArgs, + BasePluginAuthority +> { + return dataEnum( + [ + ['None', unit()], + ['Owner', unit()], + ['UpdateAuthority', unit()], + [ + 'Address', + struct>([ + ['address', publicKeySerializer()], + ]), + ], + ], + { description: 'BasePluginAuthority' } + ) as Serializer; +} + +// Data Enum Helpers. +export function basePluginAuthority( + kind: 'None' +): GetDataEnumKind; +export function basePluginAuthority( + kind: 'Owner' +): GetDataEnumKind; +export function basePluginAuthority( + kind: 'UpdateAuthority' +): GetDataEnumKind; +export function basePluginAuthority( + kind: 'Address', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function basePluginAuthority< + K extends BasePluginAuthorityArgs['__kind'], +>(kind: K, data?: any): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBasePluginAuthority( + kind: K, + value: BasePluginAuthority +): value is BasePluginAuthority & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/royalties.ts b/clients/js/src/generated/types/baseRoyalties.ts similarity index 57% rename from clients/js/src/generated/types/royalties.ts rename to clients/js/src/generated/types/baseRoyalties.ts index 43fe449e..7cf71d9f 100644 --- a/clients/js/src/generated/types/royalties.ts +++ b/clients/js/src/generated/types/baseRoyalties.ts @@ -13,33 +13,36 @@ import { u16, } from '@metaplex-foundation/umi/serializers'; import { + BaseRuleSet, + BaseRuleSetArgs, Creator, CreatorArgs, - RuleSet, - RuleSetArgs, + getBaseRuleSetSerializer, getCreatorSerializer, - getRuleSetSerializer, } from '.'; -export type Royalties = { +export type BaseRoyalties = { basisPoints: number; creators: Array; - ruleSet: RuleSet; + ruleSet: BaseRuleSet; }; -export type RoyaltiesArgs = { +export type BaseRoyaltiesArgs = { basisPoints: number; creators: Array; - ruleSet: RuleSetArgs; + ruleSet: BaseRuleSetArgs; }; -export function getRoyaltiesSerializer(): Serializer { - return struct( +export function getBaseRoyaltiesSerializer(): Serializer< + BaseRoyaltiesArgs, + BaseRoyalties +> { + return struct( [ ['basisPoints', u16()], ['creators', array(getCreatorSerializer())], - ['ruleSet', getRuleSetSerializer()], + ['ruleSet', getBaseRuleSetSerializer()], ], - { description: 'Royalties' } - ) as Serializer; + { description: 'BaseRoyalties' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/ruleSet.ts b/clients/js/src/generated/types/baseRuleSet.ts similarity index 51% rename from clients/js/src/generated/types/ruleSet.ts rename to clients/js/src/generated/types/baseRuleSet.ts index 3850fb1a..eded7d57 100644 --- a/clients/js/src/generated/types/ruleSet.ts +++ b/clients/js/src/generated/types/baseRuleSet.ts @@ -19,55 +19,60 @@ import { unit, } from '@metaplex-foundation/umi/serializers'; -export type RuleSet = +export type BaseRuleSet = | { __kind: 'None' } | { __kind: 'ProgramAllowList'; fields: [Array] } | { __kind: 'ProgramDenyList'; fields: [Array] }; -export type RuleSetArgs = RuleSet; +export type BaseRuleSetArgs = BaseRuleSet; -export function getRuleSetSerializer(): Serializer { - return dataEnum( +export function getBaseRuleSetSerializer(): Serializer< + BaseRuleSetArgs, + BaseRuleSet +> { + return dataEnum( [ ['None', unit()], [ 'ProgramAllowList', - struct>([ + struct>([ ['fields', tuple([array(publicKeySerializer())])], ]), ], [ 'ProgramDenyList', - struct>([ + struct>([ ['fields', tuple([array(publicKeySerializer())])], ]), ], ], - { description: 'RuleSet' } - ) as Serializer; + { description: 'BaseRuleSet' } + ) as Serializer; } // Data Enum Helpers. -export function ruleSet(kind: 'None'): GetDataEnumKind; -export function ruleSet( +export function baseRuleSet( + kind: 'None' +): GetDataEnumKind; +export function baseRuleSet( kind: 'ProgramAllowList', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; -export function ruleSet( + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseRuleSet( kind: 'ProgramDenyList', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; -export function ruleSet( + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseRuleSet( kind: K, data?: any -): Extract { +): Extract { return Array.isArray(data) ? { __kind: kind, fields: data } : { __kind: kind, ...(data ?? {}) }; } -export function isRuleSet( +export function isBaseRuleSet( kind: K, - value: RuleSet -): value is RuleSet & { __kind: K } { + value: BaseRuleSet +): value is BaseRuleSet & { __kind: K } { return value.__kind === kind; } diff --git a/clients/js/src/generated/types/baseSeed.ts b/clients/js/src/generated/types/baseSeed.ts new file mode 100644 index 00000000..24bdc420 --- /dev/null +++ b/clients/js/src/generated/types/baseSeed.ts @@ -0,0 +1,87 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + bytes, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, + u32, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type BaseSeed = + | { __kind: 'Collection' } + | { __kind: 'Owner' } + | { __kind: 'Recipient' } + | { __kind: 'Asset' } + | { __kind: 'Address'; fields: [PublicKey] } + | { __kind: 'Bytes'; fields: [Uint8Array] }; + +export type BaseSeedArgs = BaseSeed; + +export function getBaseSeedSerializer(): Serializer { + return dataEnum( + [ + ['Collection', unit()], + ['Owner', unit()], + ['Recipient', unit()], + ['Asset', unit()], + [ + 'Address', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + [ + 'Bytes', + struct>([ + ['fields', tuple([bytes({ size: u32() })])], + ]), + ], + ], + { description: 'BaseSeed' } + ) as Serializer; +} + +// Data Enum Helpers. +export function baseSeed( + kind: 'Collection' +): GetDataEnumKind; +export function baseSeed(kind: 'Owner'): GetDataEnumKind; +export function baseSeed( + kind: 'Recipient' +): GetDataEnumKind; +export function baseSeed(kind: 'Asset'): GetDataEnumKind; +export function baseSeed( + kind: 'Address', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseSeed( + kind: 'Bytes', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseSeed( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseSeed( + kind: K, + value: BaseSeed +): value is BaseSeed & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseUpdateAuthority.ts b/clients/js/src/generated/types/baseUpdateAuthority.ts new file mode 100644 index 00000000..de8818f2 --- /dev/null +++ b/clients/js/src/generated/types/baseUpdateAuthority.ts @@ -0,0 +1,76 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type BaseUpdateAuthority = + | { __kind: 'None' } + | { __kind: 'Address'; fields: [PublicKey] } + | { __kind: 'Collection'; fields: [PublicKey] }; + +export type BaseUpdateAuthorityArgs = BaseUpdateAuthority; + +export function getBaseUpdateAuthoritySerializer(): Serializer< + BaseUpdateAuthorityArgs, + BaseUpdateAuthority +> { + return dataEnum( + [ + ['None', unit()], + [ + 'Address', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + [ + 'Collection', + struct>([ + ['fields', tuple([publicKeySerializer()])], + ]), + ], + ], + { description: 'BaseUpdateAuthority' } + ) as Serializer; +} + +// Data Enum Helpers. +export function baseUpdateAuthority( + kind: 'None' +): GetDataEnumKind; +export function baseUpdateAuthority( + kind: 'Address', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseUpdateAuthority( + kind: 'Collection', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function baseUpdateAuthority< + K extends BaseUpdateAuthorityArgs['__kind'], +>(kind: K, data?: any): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseUpdateAuthority( + kind: K, + value: BaseUpdateAuthority +): value is BaseUpdateAuthority & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/baseValidationResultsOffset.ts b/clients/js/src/generated/types/baseValidationResultsOffset.ts new file mode 100644 index 00000000..0d960574 --- /dev/null +++ b/clients/js/src/generated/types/baseValidationResultsOffset.ts @@ -0,0 +1,80 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + tuple, + u64, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type BaseValidationResultsOffset = + | { __kind: 'NoOffset' } + | { __kind: 'Anchor' } + | { __kind: 'Custom'; fields: [bigint] }; + +export type BaseValidationResultsOffsetArgs = + | { __kind: 'NoOffset' } + | { __kind: 'Anchor' } + | { __kind: 'Custom'; fields: [number | bigint] }; + +export function getBaseValidationResultsOffsetSerializer(): Serializer< + BaseValidationResultsOffsetArgs, + BaseValidationResultsOffset +> { + return dataEnum( + [ + ['NoOffset', unit()], + ['Anchor', unit()], + [ + 'Custom', + struct>([ + ['fields', tuple([u64()])], + ]), + ], + ], + { description: 'BaseValidationResultsOffset' } + ) as Serializer; +} + +// Data Enum Helpers. +export function baseValidationResultsOffset( + kind: 'NoOffset' +): GetDataEnumKind; +export function baseValidationResultsOffset( + kind: 'Anchor' +): GetDataEnumKind; +export function baseValidationResultsOffset( + kind: 'Custom', + data: GetDataEnumKindContent< + BaseValidationResultsOffsetArgs, + 'Custom' + >['fields'] +): GetDataEnumKind; +export function baseValidationResultsOffset< + K extends BaseValidationResultsOffsetArgs['__kind'], +>( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isBaseValidationResultsOffset< + K extends BaseValidationResultsOffset['__kind'], +>( + kind: K, + value: BaseValidationResultsOffset +): value is BaseValidationResultsOffset & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/compressionProof.ts b/clients/js/src/generated/types/compressionProof.ts index 13d12744..da22b58d 100644 --- a/clients/js/src/generated/types/compressionProof.ts +++ b/clients/js/src/generated/types/compressionProof.ts @@ -16,17 +16,17 @@ import { u64, } from '@metaplex-foundation/umi/serializers'; import { + BaseUpdateAuthority, + BaseUpdateAuthorityArgs, HashablePluginSchema, HashablePluginSchemaArgs, - UpdateAuthority, - UpdateAuthorityArgs, + getBaseUpdateAuthoritySerializer, getHashablePluginSchemaSerializer, - getUpdateAuthoritySerializer, } from '.'; export type CompressionProof = { owner: PublicKey; - updateAuthority: UpdateAuthority; + updateAuthority: BaseUpdateAuthority; name: string; uri: string; seq: bigint; @@ -35,7 +35,7 @@ export type CompressionProof = { export type CompressionProofArgs = { owner: PublicKey; - updateAuthority: UpdateAuthorityArgs; + updateAuthority: BaseUpdateAuthorityArgs; name: string; uri: string; seq: number | bigint; @@ -49,7 +49,7 @@ export function getCompressionProofSerializer(): Serializer< return struct( [ ['owner', publicKeySerializer()], - ['updateAuthority', getUpdateAuthoritySerializer()], + ['updateAuthority', getBaseUpdateAuthoritySerializer()], ['name', string()], ['uri', string()], ['seq', u64()], diff --git a/clients/js/src/generated/types/externalCheckResult.ts b/clients/js/src/generated/types/externalCheckResult.ts new file mode 100644 index 00000000..eb54e409 --- /dev/null +++ b/clients/js/src/generated/types/externalCheckResult.ts @@ -0,0 +1,22 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct, u32 } from '@metaplex-foundation/umi/serializers'; + +export type ExternalCheckResult = { flags: number }; + +export type ExternalCheckResultArgs = ExternalCheckResult; + +export function getExternalCheckResultSerializer(): Serializer< + ExternalCheckResultArgs, + ExternalCheckResult +> { + return struct([['flags', u32()]], { + description: 'ExternalCheckResult', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/externalPluginAdapter.ts b/clients/js/src/generated/types/externalPluginAdapter.ts new file mode 100644 index 00000000..b50617df --- /dev/null +++ b/clients/js/src/generated/types/externalPluginAdapter.ts @@ -0,0 +1,98 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + BaseDataStore, + BaseDataStoreArgs, + BaseLifecycleHook, + BaseLifecycleHookArgs, + BaseOracle, + BaseOracleArgs, + getBaseDataStoreSerializer, + getBaseLifecycleHookSerializer, + getBaseOracleSerializer, +} from '.'; + +export type ExternalPluginAdapter = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHook] } + | { __kind: 'Oracle'; fields: [BaseOracle] } + | { __kind: 'DataStore'; fields: [BaseDataStore] }; + +export type ExternalPluginAdapterArgs = + | { __kind: 'LifecycleHook'; fields: [BaseLifecycleHookArgs] } + | { __kind: 'Oracle'; fields: [BaseOracleArgs] } + | { __kind: 'DataStore'; fields: [BaseDataStoreArgs] }; + +export function getExternalPluginAdapterSerializer(): Serializer< + ExternalPluginAdapterArgs, + ExternalPluginAdapter +> { + return dataEnum( + [ + [ + 'LifecycleHook', + struct>([ + ['fields', tuple([getBaseLifecycleHookSerializer()])], + ]), + ], + [ + 'Oracle', + struct>([ + ['fields', tuple([getBaseOracleSerializer()])], + ]), + ], + [ + 'DataStore', + struct>([ + ['fields', tuple([getBaseDataStoreSerializer()])], + ]), + ], + ], + { description: 'ExternalPluginAdapter' } + ) as Serializer; +} + +// Data Enum Helpers. +export function externalPluginAdapter( + kind: 'LifecycleHook', + data: GetDataEnumKindContent< + ExternalPluginAdapterArgs, + 'LifecycleHook' + >['fields'] +): GetDataEnumKind; +export function externalPluginAdapter( + kind: 'Oracle', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function externalPluginAdapter( + kind: 'DataStore', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function externalPluginAdapter< + K extends ExternalPluginAdapterArgs['__kind'], +>(kind: K, data?: any): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isExternalPluginAdapter< + K extends ExternalPluginAdapter['__kind'], +>( + kind: K, + value: ExternalPluginAdapter +): value is ExternalPluginAdapter & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/externalPluginAdapterSchema.ts b/clients/js/src/generated/types/externalPluginAdapterSchema.ts new file mode 100644 index 00000000..cbaff13a --- /dev/null +++ b/clients/js/src/generated/types/externalPluginAdapterSchema.ts @@ -0,0 +1,29 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum ExternalPluginAdapterSchema { + Binary, + Json, + MsgPack, +} + +export type ExternalPluginAdapterSchemaArgs = ExternalPluginAdapterSchema; + +export function getExternalPluginAdapterSchemaSerializer(): Serializer< + ExternalPluginAdapterSchemaArgs, + ExternalPluginAdapterSchema +> { + return scalarEnum(ExternalPluginAdapterSchema, { + description: 'ExternalPluginAdapterSchema', + }) as Serializer< + ExternalPluginAdapterSchemaArgs, + ExternalPluginAdapterSchema + >; +} diff --git a/clients/js/src/generated/types/externalPluginAdapterType.ts b/clients/js/src/generated/types/externalPluginAdapterType.ts new file mode 100644 index 00000000..a0141f37 --- /dev/null +++ b/clients/js/src/generated/types/externalPluginAdapterType.ts @@ -0,0 +1,26 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum ExternalPluginAdapterType { + LifecycleHook, + Oracle, + DataStore, +} + +export type ExternalPluginAdapterTypeArgs = ExternalPluginAdapterType; + +export function getExternalPluginAdapterTypeSerializer(): Serializer< + ExternalPluginAdapterTypeArgs, + ExternalPluginAdapterType +> { + return scalarEnum(ExternalPluginAdapterType, { + description: 'ExternalPluginAdapterType', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/externalPluginRecord.ts b/clients/js/src/generated/types/externalPluginRecord.ts deleted file mode 100644 index 83325d1c..00000000 --- a/clients/js/src/generated/types/externalPluginRecord.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; -import { - PluginAuthority, - PluginAuthorityArgs, - getPluginAuthoritySerializer, -} from '.'; - -export type ExternalPluginRecord = { - authority: PluginAuthority; - offset: bigint; -}; - -export type ExternalPluginRecordArgs = { - authority: PluginAuthorityArgs; - offset: number | bigint; -}; - -export function getExternalPluginRecordSerializer(): Serializer< - ExternalPluginRecordArgs, - ExternalPluginRecord -> { - return struct( - [ - ['authority', getPluginAuthoritySerializer()], - ['offset', u64()], - ], - { description: 'ExternalPluginRecord' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/externalRegistryRecord.ts b/clients/js/src/generated/types/externalRegistryRecord.ts new file mode 100644 index 00000000..6d9f8134 --- /dev/null +++ b/clients/js/src/generated/types/externalRegistryRecord.ts @@ -0,0 +1,78 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable } from '@metaplex-foundation/umi'; +import { + Serializer, + array, + option, + struct, + tuple, + u64, +} from '@metaplex-foundation/umi/serializers'; +import { + BasePluginAuthority, + BasePluginAuthorityArgs, + ExternalCheckResult, + ExternalCheckResultArgs, + ExternalPluginAdapterType, + ExternalPluginAdapterTypeArgs, + HookableLifecycleEvent, + HookableLifecycleEventArgs, + getBasePluginAuthoritySerializer, + getExternalCheckResultSerializer, + getExternalPluginAdapterTypeSerializer, + getHookableLifecycleEventSerializer, +} from '.'; + +export type ExternalRegistryRecord = { + pluginType: ExternalPluginAdapterType; + authority: BasePluginAuthority; + lifecycleChecks: Option>; + offset: bigint; + dataOffset: Option; + dataLen: Option; +}; + +export type ExternalRegistryRecordArgs = { + pluginType: ExternalPluginAdapterTypeArgs; + authority: BasePluginAuthorityArgs; + lifecycleChecks: OptionOrNullable< + Array<[HookableLifecycleEventArgs, ExternalCheckResultArgs]> + >; + offset: number | bigint; + dataOffset: OptionOrNullable; + dataLen: OptionOrNullable; +}; + +export function getExternalRegistryRecordSerializer(): Serializer< + ExternalRegistryRecordArgs, + ExternalRegistryRecord +> { + return struct( + [ + ['pluginType', getExternalPluginAdapterTypeSerializer()], + ['authority', getBasePluginAuthoritySerializer()], + [ + 'lifecycleChecks', + option( + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ) + ), + ], + ['offset', u64()], + ['dataOffset', option(u64())], + ['dataLen', option(u64())], + ], + { description: 'ExternalRegistryRecord' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/externalValidationResult.ts b/clients/js/src/generated/types/externalValidationResult.ts new file mode 100644 index 00000000..f60f5189 --- /dev/null +++ b/clients/js/src/generated/types/externalValidationResult.ts @@ -0,0 +1,26 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum ExternalValidationResult { + Approved, + Rejected, + Pass, +} + +export type ExternalValidationResultArgs = ExternalValidationResult; + +export function getExternalValidationResultSerializer(): Serializer< + ExternalValidationResultArgs, + ExternalValidationResult +> { + return scalarEnum(ExternalValidationResult, { + description: 'ExternalValidationResult', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/extraAccounts.ts b/clients/js/src/generated/types/extraAccounts.ts deleted file mode 100644 index 4e2958ae..00000000 --- a/clients/js/src/generated/types/extraAccounts.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; -import { - GetDataEnumKind, - GetDataEnumKindContent, - Serializer, - dataEnum, - option, - publicKey as publicKeySerializer, - struct, - unit, -} from '@metaplex-foundation/umi/serializers'; - -export type ExtraAccounts = - | { __kind: 'None' } - | { __kind: 'SplHook'; extraAccountMetas: PublicKey } - | { - __kind: 'MplHook'; - mintPda: Option; - collectionPda: Option; - ownerPda: Option; - }; - -export type ExtraAccountsArgs = - | { __kind: 'None' } - | { __kind: 'SplHook'; extraAccountMetas: PublicKey } - | { - __kind: 'MplHook'; - mintPda: OptionOrNullable; - collectionPda: OptionOrNullable; - ownerPda: OptionOrNullable; - }; - -export function getExtraAccountsSerializer(): Serializer< - ExtraAccountsArgs, - ExtraAccounts -> { - return dataEnum( - [ - ['None', unit()], - [ - 'SplHook', - struct>([ - ['extraAccountMetas', publicKeySerializer()], - ]), - ], - [ - 'MplHook', - struct>([ - ['mintPda', option(publicKeySerializer())], - ['collectionPda', option(publicKeySerializer())], - ['ownerPda', option(publicKeySerializer())], - ]), - ], - ], - { description: 'ExtraAccounts' } - ) as Serializer; -} - -// Data Enum Helpers. -export function extraAccounts( - kind: 'None' -): GetDataEnumKind; -export function extraAccounts( - kind: 'SplHook', - data: GetDataEnumKindContent -): GetDataEnumKind; -export function extraAccounts( - kind: 'MplHook', - data: GetDataEnumKindContent -): GetDataEnumKind; -export function extraAccounts( - kind: K, - data?: any -): Extract { - return Array.isArray(data) - ? { __kind: kind, fields: data } - : { __kind: kind, ...(data ?? {}) }; -} -export function isExtraAccounts( - kind: K, - value: ExtraAccounts -): value is ExtraAccounts & { __kind: K } { - return value.__kind === kind; -} diff --git a/clients/js/src/generated/types/hashablePluginSchema.ts b/clients/js/src/generated/types/hashablePluginSchema.ts index 858226e7..6aeb83d6 100644 --- a/clients/js/src/generated/types/hashablePluginSchema.ts +++ b/clients/js/src/generated/types/hashablePluginSchema.ts @@ -8,23 +8,23 @@ import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; import { + BasePluginAuthority, + BasePluginAuthorityArgs, Plugin, PluginArgs, - PluginAuthority, - PluginAuthorityArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginSerializer, } from '.'; export type HashablePluginSchema = { index: bigint; - authority: PluginAuthority; + authority: BasePluginAuthority; plugin: Plugin; }; export type HashablePluginSchemaArgs = { index: number | bigint; - authority: PluginAuthorityArgs; + authority: BasePluginAuthorityArgs; plugin: PluginArgs; }; @@ -35,7 +35,7 @@ export function getHashablePluginSchemaSerializer(): Serializer< return struct( [ ['index', u64()], - ['authority', getPluginAuthoritySerializer()], + ['authority', getBasePluginAuthoritySerializer()], ['plugin', getPluginSerializer()], ], { description: 'HashablePluginSchema' } diff --git a/clients/js/src/generated/types/hookableLifecycleEvent.ts b/clients/js/src/generated/types/hookableLifecycleEvent.ts new file mode 100644 index 00000000..fcf73d79 --- /dev/null +++ b/clients/js/src/generated/types/hookableLifecycleEvent.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum HookableLifecycleEvent { + Create, + Transfer, + Burn, + Update, +} + +export type HookableLifecycleEventArgs = HookableLifecycleEvent; + +export function getHookableLifecycleEventSerializer(): Serializer< + HookableLifecycleEventArgs, + HookableLifecycleEvent +> { + return scalarEnum(HookableLifecycleEvent, { + description: 'HookableLifecycleEvent', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index d21be145..ca171c8e 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -9,29 +9,51 @@ export * from './addBlocker'; export * from './attribute'; export * from './attributes'; +export * from './baseDataStore'; +export * from './baseDataStoreInitInfo'; +export * from './baseDataStoreUpdateInfo'; +export * from './baseExternalPluginAdapterInitInfo'; +export * from './baseExternalPluginAdapterKey'; +export * from './baseExternalPluginAdapterUpdateInfo'; +export * from './baseExtraAccount'; +export * from './baseLifecycleHook'; +export * from './baseLifecycleHookInitInfo'; +export * from './baseLifecycleHookUpdateInfo'; +export * from './baseMasterEdition'; +export * from './baseOracle'; +export * from './baseOracleInitInfo'; +export * from './baseOracleUpdateInfo'; +export * from './basePluginAuthority'; +export * from './baseRoyalties'; +export * from './baseRuleSet'; +export * from './baseSeed'; +export * from './baseUpdateAuthority'; +export * from './baseValidationResultsOffset'; export * from './burnDelegate'; export * from './compressionProof'; export * from './creator'; export * from './dataState'; export * from './edition'; -export * from './externalPluginRecord'; -export * from './extraAccounts'; +export * from './externalCheckResult'; +export * from './externalPluginAdapter'; +export * from './externalPluginAdapterSchema'; +export * from './externalPluginAdapterType'; +export * from './externalRegistryRecord'; +export * from './externalValidationResult'; export * from './freezeDelegate'; export * from './hashablePluginSchema'; export * from './hashedAssetSchema'; +export * from './hookableLifecycleEvent'; export * from './immutableMetadata'; export * from './key'; -export * from './masterEdition'; +export * from './oracleValidation'; export * from './permanentBurnDelegate'; export * from './permanentFreezeDelegate'; export * from './permanentTransferDelegate'; export * from './plugin'; -export * from './pluginAuthority'; export * from './pluginAuthorityPair'; export * from './pluginType'; export * from './registryRecord'; -export * from './royalties'; -export * from './ruleSet'; export * from './transferDelegate'; -export * from './updateAuthority'; export * from './updateDelegate'; +export * from './validationResult'; diff --git a/clients/js/src/generated/types/oracleValidation.ts b/clients/js/src/generated/types/oracleValidation.ts new file mode 100644 index 00000000..e92efa8c --- /dev/null +++ b/clients/js/src/generated/types/oracleValidation.ts @@ -0,0 +1,85 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + unit, +} from '@metaplex-foundation/umi/serializers'; +import { + ExternalValidationResult, + ExternalValidationResultArgs, + getExternalValidationResultSerializer, +} from '.'; + +export type OracleValidation = + | { __kind: 'Uninitialized' } + | { + __kind: 'V1'; + create: ExternalValidationResult; + transfer: ExternalValidationResult; + burn: ExternalValidationResult; + update: ExternalValidationResult; + }; + +export type OracleValidationArgs = + | { __kind: 'Uninitialized' } + | { + __kind: 'V1'; + create: ExternalValidationResultArgs; + transfer: ExternalValidationResultArgs; + burn: ExternalValidationResultArgs; + update: ExternalValidationResultArgs; + }; + +export function getOracleValidationSerializer(): Serializer< + OracleValidationArgs, + OracleValidation +> { + return dataEnum( + [ + ['Uninitialized', unit()], + [ + 'V1', + struct>([ + ['create', getExternalValidationResultSerializer()], + ['transfer', getExternalValidationResultSerializer()], + ['burn', getExternalValidationResultSerializer()], + ['update', getExternalValidationResultSerializer()], + ]), + ], + ], + { description: 'OracleValidation' } + ) as Serializer; +} + +// Data Enum Helpers. +export function oracleValidation( + kind: 'Uninitialized' +): GetDataEnumKind; +export function oracleValidation( + kind: 'V1', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function oracleValidation( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isOracleValidation( + kind: K, + value: OracleValidation +): value is OracleValidation & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 1fe904b4..64c4c394 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -19,6 +19,10 @@ import { AddBlockerArgs, Attributes, AttributesArgs, + BaseMasterEdition, + BaseMasterEditionArgs, + BaseRoyalties, + BaseRoyaltiesArgs, BurnDelegate, BurnDelegateArgs, Edition, @@ -27,37 +31,33 @@ import { FreezeDelegateArgs, ImmutableMetadata, ImmutableMetadataArgs, - MasterEdition, - MasterEditionArgs, PermanentBurnDelegate, PermanentBurnDelegateArgs, PermanentFreezeDelegate, PermanentFreezeDelegateArgs, PermanentTransferDelegate, PermanentTransferDelegateArgs, - Royalties, - RoyaltiesArgs, TransferDelegate, TransferDelegateArgs, UpdateDelegate, UpdateDelegateArgs, getAddBlockerSerializer, getAttributesSerializer, + getBaseMasterEditionSerializer, + getBaseRoyaltiesSerializer, getBurnDelegateSerializer, getEditionSerializer, getFreezeDelegateSerializer, getImmutableMetadataSerializer, - getMasterEditionSerializer, getPermanentBurnDelegateSerializer, getPermanentFreezeDelegateSerializer, getPermanentTransferDelegateSerializer, - getRoyaltiesSerializer, getTransferDelegateSerializer, getUpdateDelegateSerializer, } from '.'; export type Plugin = - | { __kind: 'Royalties'; fields: [Royalties] } + | { __kind: 'Royalties'; fields: [BaseRoyalties] } | { __kind: 'FreezeDelegate'; fields: [FreezeDelegate] } | { __kind: 'BurnDelegate'; fields: [BurnDelegate] } | { __kind: 'TransferDelegate'; fields: [TransferDelegate] } @@ -67,12 +67,12 @@ export type Plugin = | { __kind: 'PermanentTransferDelegate'; fields: [PermanentTransferDelegate] } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegate] } | { __kind: 'Edition'; fields: [Edition] } - | { __kind: 'MasterEdition'; fields: [MasterEdition] } + | { __kind: 'MasterEdition'; fields: [BaseMasterEdition] } | { __kind: 'AddBlocker'; fields: [AddBlocker] } | { __kind: 'ImmutableMetadata'; fields: [ImmutableMetadata] }; export type PluginArgs = - | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } + | { __kind: 'Royalties'; fields: [BaseRoyaltiesArgs] } | { __kind: 'FreezeDelegate'; fields: [FreezeDelegateArgs] } | { __kind: 'BurnDelegate'; fields: [BurnDelegateArgs] } | { __kind: 'TransferDelegate'; fields: [TransferDelegateArgs] } @@ -85,7 +85,7 @@ export type PluginArgs = } | { __kind: 'PermanentBurnDelegate'; fields: [PermanentBurnDelegateArgs] } | { __kind: 'Edition'; fields: [EditionArgs] } - | { __kind: 'MasterEdition'; fields: [MasterEditionArgs] } + | { __kind: 'MasterEdition'; fields: [BaseMasterEditionArgs] } | { __kind: 'AddBlocker'; fields: [AddBlockerArgs] } | { __kind: 'ImmutableMetadata'; fields: [ImmutableMetadataArgs] }; @@ -95,7 +95,7 @@ export function getPluginSerializer(): Serializer { [ 'Royalties', struct>([ - ['fields', tuple([getRoyaltiesSerializer()])], + ['fields', tuple([getBaseRoyaltiesSerializer()])], ]), ], [ @@ -155,7 +155,7 @@ export function getPluginSerializer(): Serializer { [ 'MasterEdition', struct>([ - ['fields', tuple([getMasterEditionSerializer()])], + ['fields', tuple([getBaseMasterEditionSerializer()])], ]), ], [ diff --git a/clients/js/src/generated/types/pluginAuthority.ts b/clients/js/src/generated/types/pluginAuthority.ts deleted file mode 100644 index 5f636870..00000000 --- a/clients/js/src/generated/types/pluginAuthority.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { PublicKey } from '@metaplex-foundation/umi'; -import { - GetDataEnumKind, - GetDataEnumKindContent, - Serializer, - dataEnum, - publicKey as publicKeySerializer, - struct, - unit, -} from '@metaplex-foundation/umi/serializers'; - -export type PluginAuthority = - | { __kind: 'None' } - | { __kind: 'Owner' } - | { __kind: 'UpdateAuthority' } - | { __kind: 'Address'; address: PublicKey }; - -export type PluginAuthorityArgs = PluginAuthority; - -export function getPluginAuthoritySerializer(): Serializer< - PluginAuthorityArgs, - PluginAuthority -> { - return dataEnum( - [ - ['None', unit()], - ['Owner', unit()], - ['UpdateAuthority', unit()], - [ - 'Address', - struct>([ - ['address', publicKeySerializer()], - ]), - ], - ], - { description: 'PluginAuthority' } - ) as Serializer; -} - -// Data Enum Helpers. -export function pluginAuthority( - kind: 'None' -): GetDataEnumKind; -export function pluginAuthority( - kind: 'Owner' -): GetDataEnumKind; -export function pluginAuthority( - kind: 'UpdateAuthority' -): GetDataEnumKind; -export function pluginAuthority( - kind: 'Address', - data: GetDataEnumKindContent -): GetDataEnumKind; -export function pluginAuthority( - kind: K, - data?: any -): Extract { - return Array.isArray(data) - ? { __kind: kind, fields: data } - : { __kind: kind, ...(data ?? {}) }; -} -export function isPluginAuthority( - kind: K, - value: PluginAuthority -): value is PluginAuthority & { __kind: K } { - return value.__kind === kind; -} diff --git a/clients/js/src/generated/types/pluginAuthorityPair.ts b/clients/js/src/generated/types/pluginAuthorityPair.ts index 27558f36..b7deb46e 100644 --- a/clients/js/src/generated/types/pluginAuthorityPair.ts +++ b/clients/js/src/generated/types/pluginAuthorityPair.ts @@ -13,22 +13,22 @@ import { struct, } from '@metaplex-foundation/umi/serializers'; import { + BasePluginAuthority, + BasePluginAuthorityArgs, Plugin, PluginArgs, - PluginAuthority, - PluginAuthorityArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginSerializer, } from '.'; export type PluginAuthorityPair = { plugin: Plugin; - authority: Option; + authority: Option; }; export type PluginAuthorityPairArgs = { plugin: PluginArgs; - authority: OptionOrNullable; + authority: OptionOrNullable; }; export function getPluginAuthorityPairSerializer(): Serializer< @@ -38,7 +38,7 @@ export function getPluginAuthorityPairSerializer(): Serializer< return struct( [ ['plugin', getPluginSerializer()], - ['authority', option(getPluginAuthoritySerializer())], + ['authority', option(getBasePluginAuthoritySerializer())], ], { description: 'PluginAuthorityPair' } ) as Serializer; diff --git a/clients/js/src/generated/types/pluginRegistryV1AccountData.ts b/clients/js/src/generated/types/pluginRegistryV1AccountData.ts index 6bfd5a0b..eb7a59cf 100644 --- a/clients/js/src/generated/types/pluginRegistryV1AccountData.ts +++ b/clients/js/src/generated/types/pluginRegistryV1AccountData.ts @@ -12,13 +12,13 @@ import { struct, } from '@metaplex-foundation/umi/serializers'; import { - ExternalPluginRecord, - ExternalPluginRecordArgs, + ExternalRegistryRecord, + ExternalRegistryRecordArgs, Key, KeyArgs, RegistryRecord, RegistryRecordArgs, - getExternalPluginRecordSerializer, + getExternalRegistryRecordSerializer, getKeySerializer, getRegistryRecordSerializer, } from '.'; @@ -26,13 +26,13 @@ import { export type PluginRegistryV1AccountData = { key: Key; registry: Array; - externalPlugins: Array; + externalRegistry: Array; }; export type PluginRegistryV1AccountDataArgs = { key: KeyArgs; registry: Array; - externalPlugins: Array; + externalRegistry: Array; }; export function getPluginRegistryV1AccountDataSerializer(): Serializer< @@ -43,7 +43,7 @@ export function getPluginRegistryV1AccountDataSerializer(): Serializer< [ ['key', getKeySerializer()], ['registry', array(getRegistryRecordSerializer())], - ['externalPlugins', array(getExternalPluginRecordSerializer())], + ['externalRegistry', array(getExternalRegistryRecordSerializer())], ], { description: 'PluginRegistryV1AccountData' } ) as Serializer; diff --git a/clients/js/src/generated/types/registryRecord.ts b/clients/js/src/generated/types/registryRecord.ts index 77247d84..56581ec9 100644 --- a/clients/js/src/generated/types/registryRecord.ts +++ b/clients/js/src/generated/types/registryRecord.ts @@ -8,23 +8,23 @@ import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; import { - PluginAuthority, - PluginAuthorityArgs, + BasePluginAuthority, + BasePluginAuthorityArgs, PluginType, PluginTypeArgs, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginTypeSerializer, } from '.'; export type RegistryRecord = { pluginType: PluginType; - authority: PluginAuthority; + authority: BasePluginAuthority; offset: bigint; }; export type RegistryRecordArgs = { pluginType: PluginTypeArgs; - authority: PluginAuthorityArgs; + authority: BasePluginAuthorityArgs; offset: number | bigint; }; @@ -35,7 +35,7 @@ export function getRegistryRecordSerializer(): Serializer< return struct( [ ['pluginType', getPluginTypeSerializer()], - ['authority', getPluginAuthoritySerializer()], + ['authority', getBasePluginAuthoritySerializer()], ['offset', u64()], ], { description: 'RegistryRecord' } diff --git a/clients/js/src/generated/types/updateAuthority.ts b/clients/js/src/generated/types/updateAuthority.ts deleted file mode 100644 index 38d010cd..00000000 --- a/clients/js/src/generated/types/updateAuthority.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { PublicKey } from '@metaplex-foundation/umi'; -import { - GetDataEnumKind, - GetDataEnumKindContent, - Serializer, - dataEnum, - publicKey as publicKeySerializer, - struct, - tuple, - unit, -} from '@metaplex-foundation/umi/serializers'; - -export type UpdateAuthority = - | { __kind: 'None' } - | { __kind: 'Address'; fields: [PublicKey] } - | { __kind: 'Collection'; fields: [PublicKey] }; - -export type UpdateAuthorityArgs = UpdateAuthority; - -export function getUpdateAuthoritySerializer(): Serializer< - UpdateAuthorityArgs, - UpdateAuthority -> { - return dataEnum( - [ - ['None', unit()], - [ - 'Address', - struct>([ - ['fields', tuple([publicKeySerializer()])], - ]), - ], - [ - 'Collection', - struct>([ - ['fields', tuple([publicKeySerializer()])], - ]), - ], - ], - { description: 'UpdateAuthority' } - ) as Serializer; -} - -// Data Enum Helpers. -export function updateAuthority( - kind: 'None' -): GetDataEnumKind; -export function updateAuthority( - kind: 'Address', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; -export function updateAuthority( - kind: 'Collection', - data: GetDataEnumKindContent['fields'] -): GetDataEnumKind; -export function updateAuthority( - kind: K, - data?: any -): Extract { - return Array.isArray(data) - ? { __kind: kind, fields: data } - : { __kind: kind, ...(data ?? {}) }; -} -export function isUpdateAuthority( - kind: K, - value: UpdateAuthority -): value is UpdateAuthority & { __kind: K } { - return value.__kind === kind; -} diff --git a/clients/js/src/generated/types/validationResult.ts b/clients/js/src/generated/types/validationResult.ts new file mode 100644 index 00000000..b39ef234 --- /dev/null +++ b/clients/js/src/generated/types/validationResult.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum ValidationResult { + Approved, + Rejected, + Pass, + ForceApproved, +} + +export type ValidationResultArgs = ValidationResult; + +export function getValidationResultSerializer(): Serializer< + ValidationResultArgs, + ValidationResult +> { + return scalarEnum(ValidationResult, { + description: 'ValidationResult', + }) as Serializer; +} diff --git a/clients/js/src/helpers/authority.ts b/clients/js/src/helpers/authority.ts index 60f6c37f..2cd10c22 100644 --- a/clients/js/src/helpers/authority.ts +++ b/clients/js/src/helpers/authority.ts @@ -1,17 +1,17 @@ import { PublicKey, publicKey } from '@metaplex-foundation/umi'; import { AssetV1, CollectionV1 } from '../generated'; -import { BasePluginAuthority } from '../types'; import { deriveAssetPlugins, isAssetOwner } from './state'; +import { PluginAuthority } from '../plugins'; /** * Check if the given pubkey has the Address authority for the plugin. * @param {PublicKey | string} pubkey Pubkey - * @param {BasePluginAuthority} authority Plugin authority + * @param {PluginAuthority} authority Plugin authority * @returns {boolean} True if the pubkey has the authority */ export function hasPluginAddressAuthority( pubkey: PublicKey | string, - authority: BasePluginAuthority + authority: PluginAuthority ): boolean { return ( authority.type === 'Address' && authority.address === publicKey(pubkey) @@ -20,13 +20,13 @@ export function hasPluginAddressAuthority( /** * Check if the given pubkey has the Owner authority for the plugin. * @param {PublicKey | string} pubkey Pubkey - * @param {BasePluginAuthority} authority Plugin authority + * @param {PluginAuthority} authority Plugin authority * @param {AssetV1} asset Asset * @returns {boolean} True if the pubkey has the authority */ export function hasPluginOwnerAuthority( pubkey: PublicKey | string, - authority: BasePluginAuthority, + authority: PluginAuthority, asset: AssetV1 ): boolean { return authority.type === 'Owner' && isAssetOwner(pubkey, asset); @@ -34,14 +34,14 @@ export function hasPluginOwnerAuthority( /** * Check if the given pubkey has the UpdateAuthority authority for the plugin. * @param {PublicKey | string} pubkey Pubkey - * @param {BasePluginAuthority} authority Plugin authority + * @param {PluginAuthority} authority Plugin authority * @param {AssetV1} asset Asset * @param {CollectionV1 | undefined} collection Collection * @returns {boolean} True if the pubkey has the authority */ export function hasPluginUpdateAuthority( pubkey: PublicKey | string, - authority: BasePluginAuthority, + authority: PluginAuthority, asset: AssetV1, collection?: CollectionV1 ): boolean { diff --git a/clients/js/src/helpers/fetch.ts b/clients/js/src/helpers/fetch.ts new file mode 100644 index 00000000..d9f45c7e --- /dev/null +++ b/clients/js/src/helpers/fetch.ts @@ -0,0 +1,162 @@ +import { + Context, + publicKey, + PublicKey, + RpcGetAccountOptions, + Signer, +} from '@metaplex-foundation/umi'; +import { + AssetV1, + CollectionV1, + fetchAllCollectionV1, + fetchAssetV1, + fetchCollectionV1, + getAssetV1GpaBuilder, + getCollectionV1GpaBuilder, + Key, +} from '../generated'; +import { updateAuthority } from '../plugins'; +import { collectionAddress, deriveAssetPlugins } from './state'; + +/** + * Helper function to derive plugins for a list of assets while fetching relevant collections. + * + * @param umi Context + * @param assets Assets to derive plugins for + * @returns Promise of a list of `AssetV1` + */ +export const deriveAssetPluginsWithFetch = async ( + umi: Context, + assets: AssetV1[] +): Promise => { + const collectionKeys = Array.from( + new Set(assets.map((asset) => collectionAddress(asset))) + ).filter((collection): collection is PublicKey => !!collection); + const collections = await fetchAllCollectionV1(umi, collectionKeys); + + const collectionMap = collections.reduce( + (map, collection) => { + map[collection.publicKey] = collection; + return map; + }, + {} as { [key: string]: CollectionV1 } + ); + + return assets.map((asset) => { + const collection = collectionAddress(asset); + if (!collection) { + return asset; + } + return deriveAssetPlugins(asset, collectionMap[collection]); + }); +}; + +/** + * Helper function to fetch assets by owner using GPA. For more filters, use the `getAssetV1GpaBuilder` directly. + * For faster performance or more flexible queries, use DAS with `mpl-core-das` package. + * + * @param umi Context + * @param owner Owner of the assets + * @param options Options, `skipDerivePlugins` plugins from collection is false by default + * @returns Promise of a list of `AssetV1` + */ +export const fetchAssetsByOwner = async ( + umi: Context, + owner: PublicKey | Signer | string, + options: { skipDerivePlugins?: boolean } = {} +): Promise => { + const assets = await getAssetV1GpaBuilder(umi) + .whereField('key', Key.AssetV1) + .whereField('owner', publicKey(owner)) + .getDeserialized(); + + if (options.skipDerivePlugins) { + return assets; + } + + return deriveAssetPluginsWithFetch(umi, assets); +}; + +/** + * Helper function to fetch assets by collection using GPA. For more filters, use the `getAssetV1GpaBuilder` directly. + * For faster performance or more flexible queries, use DAS with `mpl-core-das` package. + * + * @param umi Context + * @param collection Assets that belong to this collection + * @param options Options, `skipDerivePlugins` plugins from collection is false by default + * @returns Promise of a list of `AssetV1` + */ +export const fetchAssetsByCollection = async ( + umi: Context, + collection: PublicKey | string, + options: { skipDerivePlugins?: boolean } = {} +): Promise => { + const assets = await getAssetV1GpaBuilder(umi) + .whereField('key', Key.AssetV1) + .whereField( + 'updateAuthority', + updateAuthority('Collection', [publicKey(collection)]) + ) + .getDeserialized(); + + if (options.skipDerivePlugins) { + return assets; + } + return deriveAssetPluginsWithFetch(umi, assets); +}; + +/** + * Helper function to fetch collections by update authority using GPA. For more filters, use the `getCollectionV1GpaBuilder` directly. + * For faster performance or more flexible queries, use DAS with `mpl-core-das` package. + * + * @param umi Context + * @param authority Update authority of the collections + * @returns + */ +export const fetchCollectionsByUpdateAuthority = async ( + umi: Context, + authority: PublicKey | string +) => + getCollectionV1GpaBuilder(umi) + .whereField('key', Key.CollectionV1) + .whereField('updateAuthority', publicKey(authority)) + .getDeserialized(); + +/** + * Helper function to fetch an asset and derive plugins from the collection if applicable. + * + * @param umi + * @param asset + * @param options + */ +export const fetchAsset = async ( + umi: Context, + asset: PublicKey | string, + options: { skipDerivePlugins?: boolean } & RpcGetAccountOptions = {} +): Promise => { + const assetV1 = await fetchAssetV1(umi, publicKey(asset)); + + if (options.skipDerivePlugins) { + return assetV1; + } + + const collection = collectionAddress(assetV1); + if (!collection) { + return assetV1; + } + return deriveAssetPlugins(assetV1, await fetchCollectionV1(umi, collection)); +}; + +/** + * Helper function to fetch a collection. + * + * @param umi + * @param collection + */ + +export const fetchCollection = async ( + umi: Context, + collection: PublicKey | string, + options?: RpcGetAccountOptions +): Promise => + fetchCollectionV1(umi, publicKey(collection), options); diff --git a/clients/js/src/helpers/index.ts b/clients/js/src/helpers/index.ts index 19d5445d..9c4bfa53 100644 --- a/clients/js/src/helpers/index.ts +++ b/clients/js/src/helpers/index.ts @@ -2,3 +2,4 @@ export * from './state'; export * from './lifecycle'; export * from './plugin'; export * from './authority'; +export * from './fetch'; diff --git a/clients/js/src/helpers/lifecycle.ts b/clients/js/src/helpers/lifecycle.ts index 3c08224d..c77eca8f 100644 --- a/clients/js/src/helpers/lifecycle.ts +++ b/clients/js/src/helpers/lifecycle.ts @@ -1,12 +1,30 @@ -import { PublicKey } from '@metaplex-foundation/umi'; -import { AssetV1, CollectionV1, PluginType } from '../generated'; +import { Context, PublicKey } from '@metaplex-foundation/umi'; +import { + AssetV1, + CollectionV1, + ExternalValidationResult, + PluginType, +} from '../generated'; import { deriveAssetPlugins, isFrozen } from './state'; import { checkPluginAuthorities } from './plugin'; import { hasAssetUpdateAuthority } from './authority'; +import { + CheckResult, + deserializeOracleValidation, + findOracleAccount, + getExtraAccountRequiredInputs, +} from '../plugins'; + +export enum LifecycleValidationError { + OracleValidationFailed = 'Oracle validation failed.', + NoAuthority = 'No authority to perform this action.', + AssetFrozen = 'Asset is frozen.', +} /** * Check if the given authority is eligible to transfer the asset. - * This does NOT check if the asset's roylaty rule sets. + * This does NOT check the asset's royalty rule sets or external plugin adapters. Use `validateTransfer` for more comprehensive checks. + * @deprecated since v1.0.0. Use `validateTransfer` instead. * @param {PublicKey | string} authority Pubkey * @param {AssetV1} asset Asset * @param {CollectionV1 | undefined} collection Collection @@ -18,6 +36,8 @@ export function canTransfer( collection?: CollectionV1 ): boolean { const dAsset = deriveAssetPlugins(asset, collection); + + // Permanent plugins have force approve powers const permaTransferDelegate = checkPluginAuthorities({ authority, pluginTypes: [PluginType.PermanentTransferDelegate], @@ -28,23 +48,142 @@ export function canTransfer( return true; } - if (!isFrozen(asset, collection)) { - if (dAsset.owner === authority) { - return true; + if (isFrozen(asset, collection)) { + return false; + } + + if (dAsset.owner === authority) { + return true; + } + const transferDelegates = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.TransferDelegate], + asset: dAsset, + collection, + }); + return transferDelegates.some((d) => d); +} + +export type ValidateTransferInput = { + authority: PublicKey | string; + asset: AssetV1; + collection?: CollectionV1; + recipient?: PublicKey; +}; + +/** + * Check if the given authority is eligible to transfer the asset and receive an error message if not. + * + * @param {Context} context Umi context + * @param {ValidateTransferInput} inputs Inputs to validate transfer + * @returns {null | LifecycleValidationError} null if success or error message + */ +export async function validateTransfer( + context: Pick, + { authority, asset, collection, recipient }: ValidateTransferInput +): Promise { + const dAsset = deriveAssetPlugins(asset, collection); + + // Permanent plugins have force approve powers + const permaTransferDelegate = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.PermanentTransferDelegate], + asset: dAsset, + collection, + }); + if (permaTransferDelegate.some((d) => d)) { + return null; + } + + if (isFrozen(asset, collection)) { + return LifecycleValidationError.AssetFrozen; + } + + if (dAsset.oracles?.length) { + const eligibleOracles = dAsset.oracles + .filter((o) => + o.lifecycleChecks?.transfer?.includes(CheckResult.CAN_REJECT) + ) + .filter((o) => { + // there's no PDA to derive, we can check the oracle account + if (!o.baseAddressConfig) { + return true; + } + // If there's a recipient in the inputs, we can try to check the oracle account + if (recipient) { + return true; + } + + if ( + !getExtraAccountRequiredInputs(o.baseAddressConfig).includes( + 'recipient' + ) + ) { + return true; + } + // we skip the check if there's a recipient required but no recipient provided + // this is due how UIs generally show the availability of the transfer button before requiring the recipient address + return false; + }); + if (eligibleOracles.length) { + const accountsWithOffset = eligibleOracles.map((o) => { + const account = findOracleAccount(context, o, { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + recipient, + }); + + return { + pubkey: account, + offset: o.resultsOffset, + }; + }); + + const oracleValidations = ( + await context.rpc.getAccounts(accountsWithOffset.map((a) => a.pubkey)) + ).map((a, index) => { + if (a.exists) { + return deserializeOracleValidation( + a.data, + accountsWithOffset[index].offset + ); + } + return null; + }); + + const oraclePass = oracleValidations.every((v) => { + if (v?.__kind === 'Uninitialized') { + return false; + } + return v?.transfer === ExternalValidationResult.Pass; + }); + if (!oraclePass) { + return LifecycleValidationError.OracleValidationFailed; + } } - const transferDelegates = checkPluginAuthorities({ - authority, - pluginTypes: [PluginType.TransferDelegate], - asset: dAsset, - collection, - }); - return transferDelegates.some((d) => d); - } - return false; + } + + if (dAsset.owner === authority) { + return null; + } + const transferDelegates = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.TransferDelegate], + asset: dAsset, + collection, + }); + if (transferDelegates.some((d) => d)) { + return null; + } + + return LifecycleValidationError.NoAuthority; } /** * Check if the given pubkey is eligible to burn the asset. + * This does NOT check external plugin adapters, use `validateBurn` for more comprehensive checks. + * @deprecated since v1.0.0. Use `validateBurn` instead. * @param {PublicKey | string} authority Pubkey * @param {AssetV1} asset Asset * @param {CollectionV1 | undefined} collection Collection @@ -66,23 +205,124 @@ export function canBurn( return true; } - if (!isFrozen(asset, collection)) { - if (dAsset.owner === authority) { - return true; + if (isFrozen(asset, collection)) { + return false; + } + + if (dAsset.owner === authority) { + return true; + } + const burnDelegates = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.BurnDelegate], + asset, + collection, + }); + return burnDelegates.some((d) => d); +} + +export type ValidateBurnInput = { + authority: PublicKey | string; + asset: AssetV1; + collection?: CollectionV1; +}; + +/** + * Check if the given authority is eligible to burn the asset and receive an error message if not. + * + * @param {Context} context Umi context + * @param {ValidateBurnInput} inputs Inputs to validate burn + * @returns {null | LifecycleValidationError} null if success or error message + */ +export async function validateBurn( + context: Pick, + { + authority, + asset, + collection, + }: { + authority: PublicKey | string; + asset: AssetV1; + collection?: CollectionV1; + } +): Promise { + const dAsset = deriveAssetPlugins(asset, collection); + const permaBurnDelegate = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.PermanentBurnDelegate], + asset: dAsset, + collection, + }); + if (permaBurnDelegate.some((d) => d)) { + return null; + } + + if (isFrozen(asset, collection)) { + return LifecycleValidationError.AssetFrozen; + } + + if (dAsset.oracles?.length) { + const eligibleOracles = dAsset.oracles.filter((o) => + o.lifecycleChecks?.burn?.includes(CheckResult.CAN_REJECT) + ); + if (eligibleOracles.length) { + const accountsWithOffset = eligibleOracles.map((o) => { + const account = findOracleAccount(context, o, { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + }); + + return { + pubkey: account, + offset: o.resultsOffset, + }; + }); + + const oracleValidations = ( + await context.rpc.getAccounts(accountsWithOffset.map((a) => a.pubkey)) + ).map((a, index) => { + if (a.exists) { + return deserializeOracleValidation( + a.data, + accountsWithOffset[index].offset + ); + } + return null; + }); + + const oraclePass = oracleValidations.every((v) => { + if (v?.__kind === 'Uninitialized') { + return false; + } + return v?.burn === ExternalValidationResult.Pass; + }); + if (!oraclePass) { + return LifecycleValidationError.OracleValidationFailed; + } } - const burnDelegates = checkPluginAuthorities({ - authority, - pluginTypes: [PluginType.BurnDelegate], - asset, - collection, - }); - return burnDelegates.some((d) => d); - } - return false; + } + + if (dAsset.owner === authority) { + return null; + } + const burnDelegates = checkPluginAuthorities({ + authority, + pluginTypes: [PluginType.BurnDelegate], + asset, + collection, + }); + if (burnDelegates.some((d) => d)) { + return null; + } + + return LifecycleValidationError.NoAuthority; } /** * Check if the given pubkey is eligible to update the asset. + * This does NOT check external plugin adapters. Use `validateUpdate` for more comprehensive checks. + * @deprecated since v1.0.0. Use `validateTransfer` instead. * @param {PublicKey | string} authority Pubkey * @param {AssetV1} asset Asset * @param {CollectionV1 | undefined} collection Collection @@ -95,3 +335,70 @@ export function canUpdate( ): boolean { return hasAssetUpdateAuthority(authority, asset, collection); } + +export type ValidateUpdateInput = { + authority: PublicKey | string; + asset: AssetV1; + collection?: CollectionV1; +}; + +/** + * Check if the given authority is eligible to update the asset and receive an error message if not. + * + * @param {Context} context Umi context + * @param {ValidateUpdateInput} inputs Inputs to validate update + * @returns {null | LifecycleValidationError} null if success or error message + */ +export async function validateUpdate( + context: Pick, + { authority, asset, collection }: ValidateUpdateInput +): Promise { + if (asset.oracles?.length) { + const eligibleOracles = asset.oracles.filter((o) => + o.lifecycleChecks?.update?.includes(CheckResult.CAN_REJECT) + ); + if (eligibleOracles.length) { + const accountsWithOffset = eligibleOracles.map((o) => { + const account = findOracleAccount(context, o, { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + }); + + return { + pubkey: account, + offset: o.resultsOffset, + }; + }); + + const oracleValidations = ( + await context.rpc.getAccounts(accountsWithOffset.map((a) => a.pubkey)) + ).map((a, index) => { + if (a.exists) { + return deserializeOracleValidation( + a.data, + accountsWithOffset[index].offset + ); + } + return null; + }); + + const oraclePass = oracleValidations.every((v) => { + if (v?.__kind === 'Uninitialized') { + return false; + } + return v?.update === ExternalValidationResult.Pass; + }); + + if (!oraclePass) { + return LifecycleValidationError.OracleValidationFailed; + } + } + } + + if (!hasAssetUpdateAuthority(authority, asset, collection)) { + return LifecycleValidationError.NoAuthority; + } + + return null; +} diff --git a/clients/js/src/helpers/plugin.ts b/clients/js/src/helpers/plugin.ts index 979aacc4..91356011 100644 --- a/clients/js/src/helpers/plugin.ts +++ b/clients/js/src/helpers/plugin.ts @@ -1,5 +1,4 @@ import { PublicKey, publicKey } from '@metaplex-foundation/umi'; -import { PluginsList } from '../types'; import { capitalizeFirstLetter, lowercaseFirstLetter } from '../utils'; import { AssetV1, CollectionV1, PluginType } from '../generated'; import { collectionAddress, deriveAssetPlugins, isAssetOwner } from './state'; @@ -7,8 +6,9 @@ import { hasAssetUpdateAuthority, hasPluginAddressAuthority, } from './authority'; +import { AssetPluginsList } from '../plugins'; -export type AssetPluginKey = keyof PluginsList; +export type AssetPluginKey = keyof AssetPluginsList; /** * Convert a plugin type to a key for the asset plugins. diff --git a/clients/js/src/helpers/state.ts b/clients/js/src/helpers/state.ts index 27b628b7..f522ac9c 100644 --- a/clients/js/src/helpers/state.ts +++ b/clients/js/src/helpers/state.ts @@ -1,5 +1,12 @@ import { PublicKey, publicKey } from '@metaplex-foundation/umi'; import { AssetV1, CollectionV1 } from '../generated'; +import { ExternalPluginAdaptersList } from '../plugins'; +import { OracleInitInfoArgs, OraclePlugin } from '../plugins/oracle'; +import { DataStoreInitInfoArgs, DataStorePlugin } from '../plugins/dataStore'; +import { + LifecycleHookInitInfoArgs, + LifecycleHookPlugin, +} from '../plugins/lifecycleHook'; /** * Find the collection address for the given asset if it is part of a collection. @@ -14,6 +21,64 @@ export function collectionAddress(asset: AssetV1): PublicKey | undefined { return undefined; } +const externalPluginAdapterKeys: (keyof ExternalPluginAdaptersList)[] = [ + 'oracles', + 'dataStores', + 'lifecycleHooks', +]; +export const getExternalPluginAdapterKeyAsString = ( + plugin: + | OraclePlugin + | DataStorePlugin + | LifecycleHookPlugin + | OracleInitInfoArgs + | LifecycleHookInitInfoArgs + | DataStoreInitInfoArgs +) => { + switch (plugin.type) { + case 'Oracle': + return `${plugin.type}-${plugin.baseAddress}`; + case 'DataStore': + return `${plugin.type}-${plugin.dataAuthority.type}${ + plugin.dataAuthority.address ? `-${plugin.dataAuthority.address}` : '' + }`; + case 'LifecycleHook': + default: + return `${plugin.type}-${plugin.hookedProgram}`; + } +}; + +export const deriveExternalPluginAdapters = ( + asset: ExternalPluginAdaptersList, + collection?: ExternalPluginAdaptersList +) => { + if (!collection) { + return asset; + } + const externalPluginAdapters: ExternalPluginAdaptersList = {}; + externalPluginAdapterKeys.forEach((key) => { + const set = new Set(); + if (asset[key] || collection[key]) { + externalPluginAdapters[key] = []; + } + asset[key]?.forEach( + (plugin: OraclePlugin | DataStorePlugin | LifecycleHookPlugin) => { + set.add(getExternalPluginAdapterKeyAsString(plugin)); + externalPluginAdapters[key]?.push(plugin as any); + } + ); + + collection[key]?.forEach( + (plugin: OraclePlugin | DataStorePlugin | LifecycleHookPlugin) => { + if (!set.has(getExternalPluginAdapterKeyAsString(plugin))) { + externalPluginAdapters[key]?.push(plugin as any); + } + } + ); + }); + + return externalPluginAdapters; +}; /** * Derive the asset plugins from the asset and collection. Plugins on the asset take precedence over plugins on the collection. * @param {AssetV1} asset Asset @@ -27,10 +92,18 @@ export function deriveAssetPlugins( if (!collection) { return asset; } + const externalPluginAdapters = deriveExternalPluginAdapters( + asset, + collection + ); return { - ...collection, + ...{ + ...collection, + masterEdition: undefined, // master edition can only be on the collection + }, ...asset, + ...externalPluginAdapters, }; } diff --git a/clients/js/src/hooked/assetAccountData.ts b/clients/js/src/hooked/assetAccountData.ts index 24631e57..6892318e 100644 --- a/clients/js/src/hooked/assetAccountData.ts +++ b/clients/js/src/hooked/assetAccountData.ts @@ -11,29 +11,38 @@ import { AssetV1AccountDataArgs as GenAssetV1AccountDataArgs, getAssetV1AccountDataSerializer as genGetAssetV1AccountDataSerializer, } from '../generated/types/assetV1AccountData'; -import { BaseUpdateAuthority, PluginsList } from '../types'; -import { registryRecordsToPluginsList } from '../plugins'; + +import { + AssetPluginsList, + registryRecordsToPluginsList, + UpdateAuthority, +} from '../plugins'; import { PluginRegistryV1AccountData, getPluginRegistryV1AccountDataSerializer, } from './pluginRegistryV1Data'; +import { + ExternalPluginAdaptersList, + externalRegistryRecordsToExternalPluginAdapterList, +} from '../plugins/externalPluginAdapters'; export type AssetV1AccountData = Omit< GenAssetV1AccountData, 'updateAuthority' > & - PluginsList & { + AssetPluginsList & + ExternalPluginAdaptersList & { pluginHeader?: Omit; - updateAuthority: BaseUpdateAuthority; + updateAuthority: UpdateAuthority; }; export type AssetV1AccountDataArgs = Omit< GenAssetV1AccountDataArgs, 'updateAuthority' > & - PluginsList & { + AssetPluginsList & { pluginHeader?: Omit; - updateAuthority: BaseUpdateAuthority; + updateAuthority: UpdateAuthority; }; export const getAssetV1AccountDataSerializer = (): Serializer< @@ -59,7 +68,8 @@ export const getAssetV1AccountDataSerializer = (): Serializer< let pluginHeader: PluginHeaderV1AccountData | undefined; let pluginRegistry: PluginRegistryV1AccountData | undefined; - let pluginsList: PluginsList | undefined; + let pluginsList: AssetPluginsList | undefined; + let externalPluginAdaptersList: ExternalPluginAdaptersList | undefined; let finalOffset = assetOffset; if (buffer.length !== assetOffset) { @@ -78,6 +88,12 @@ export const getAssetV1AccountDataSerializer = (): Serializer< pluginRegistry.registry, buffer ); + + externalPluginAdaptersList = + externalRegistryRecordsToExternalPluginAdapterList( + pluginRegistry.externalRegistry, + buffer + ); } const updateAuth = { type: asset.updateAuthority.__kind, @@ -91,6 +107,7 @@ export const getAssetV1AccountDataSerializer = (): Serializer< { pluginHeader, ...pluginsList, + ...externalPluginAdaptersList, ...asset, updateAuthority: updateAuth, }, diff --git a/clients/js/src/hooked/collectionAccountData.ts b/clients/js/src/hooked/collectionAccountData.ts index bb3bfb4d..eb69d9ab 100644 --- a/clients/js/src/hooked/collectionAccountData.ts +++ b/clients/js/src/hooked/collectionAccountData.ts @@ -11,15 +11,20 @@ import { CollectionV1AccountDataArgs as GenCollectionV1AccountDataArgs, getCollectionV1AccountDataSerializer as genGetCollectionV1AccountDataSerializer, } from '../generated/types/collectionV1AccountData'; -import { PluginsList } from '../types'; -import { registryRecordsToPluginsList } from '../plugins'; +import { + CollectionPluginsList, + ExternalPluginAdaptersList, + externalRegistryRecordsToExternalPluginAdapterList, + registryRecordsToPluginsList, +} from '../plugins'; import { PluginRegistryV1AccountData, getPluginRegistryV1AccountDataSerializer, } from './pluginRegistryV1Data'; export type CollectionV1AccountData = GenCollectionV1AccountData & - PluginsList & { + CollectionPluginsList & + ExternalPluginAdaptersList & { pluginHeader?: Omit; }; @@ -27,7 +32,7 @@ export type CollectionV1AccountDataArgs = Omit< GenCollectionV1AccountDataArgs, 'updateAuthority' > & - PluginsList & { + CollectionPluginsList & { pluginHeader?: Omit; }; @@ -56,7 +61,8 @@ export const getCollectionV1AccountDataSerializer = (): Serializer< let pluginHeader: PluginHeaderV1AccountData | undefined; let pluginRegistry: PluginRegistryV1AccountData | undefined; - let pluginsList: PluginsList | undefined; + let pluginsList: CollectionPluginsList | undefined; + let externalPluginAdaptersList: ExternalPluginAdaptersList | undefined; let finalOffset = collectionOffset; if (buffer.length !== collectionOffset) { @@ -75,12 +81,19 @@ export const getCollectionV1AccountDataSerializer = (): Serializer< pluginRegistry.registry, buffer ); + + externalPluginAdaptersList = + externalRegistryRecordsToExternalPluginAdapterList( + pluginRegistry.externalRegistry, + buffer + ); } return [ { pluginHeader, ...pluginsList, + ...externalPluginAdaptersList, ...collection, }, finalOffset, diff --git a/clients/js/src/hooked/pluginRegistryV1Data.ts b/clients/js/src/hooked/pluginRegistryV1Data.ts index ae5a11fc..00ee11e1 100644 --- a/clients/js/src/hooked/pluginRegistryV1Data.ts +++ b/clients/js/src/hooked/pluginRegistryV1Data.ts @@ -1,12 +1,24 @@ -import { Serializer, array, u64 } from '@metaplex-foundation/umi/serializers'; +import { + Serializer, + array, + option, + tuple, + u64, +} from '@metaplex-foundation/umi/serializers'; import { Key, PluginType, RegistryRecord, RegistryRecordArgs, getKeySerializer, - getPluginAuthoritySerializer, + getBasePluginAuthoritySerializer, getPluginTypeSerializer, + ExternalRegistryRecordArgs, + ExternalRegistryRecord, + getExternalPluginAdapterTypeSerializer, + ExternalPluginAdapterType, + getHookableLifecycleEventSerializer, + getExternalCheckResultSerializer, } from '../generated'; import { PluginRegistryV1AccountData, @@ -19,6 +31,10 @@ export type RegistryRecordWithUnknown = RegistryRecord & { isUnknown?: boolean; }; +export type ExternalRegistryRecordWithUnknown = ExternalRegistryRecord & { + isUnknown?: boolean; +}; + export function getRegistryRecordSerializer(): Serializer< RegistryRecordArgs, RegistryRecordWithUnknown @@ -49,7 +65,10 @@ export function getRegistryRecordSerializer(): Serializer< // Do nothing, unknown plugin type } const [authority, authorityOffset] = - getPluginAuthoritySerializer().deserialize(buffer, pluginTypeOffset); + getBasePluginAuthoritySerializer().deserialize( + buffer, + pluginTypeOffset + ); const [pluginOffset, pluginOffsetOffset] = u64().deserialize( buffer, authorityOffset @@ -68,6 +87,77 @@ export function getRegistryRecordSerializer(): Serializer< }; } +export function getAdapterRegistryRecordSerializer(): Serializer< + ExternalRegistryRecordArgs, + ExternalRegistryRecordWithUnknown +> { + return { + description: 'AdapterRegistryRecordWithUnknown', + fixedSize: null, + maxSize: null, + serialize: () => { + throw new Error('Operation not supported.'); + }, + deserialize: ( + buffer: Uint8Array, + offset = 0 + ): [ExternalRegistryRecordWithUnknown, number] => { + let [pluginType, pluginTypeOffset, isUnknown] = [ + ExternalPluginAdapterType.DataStore, + offset + 1, + true, + ]; + try { + [pluginType, pluginTypeOffset] = + getExternalPluginAdapterTypeSerializer().deserialize(buffer, offset); + isUnknown = false; + } catch (e) { + // do nothing + } + + const [authority, authorityOffset] = + getBasePluginAuthoritySerializer().deserialize( + buffer, + pluginTypeOffset + ); + const [lifecycleChecks, lifecycleChecksOffset] = option( + array( + tuple([ + getHookableLifecycleEventSerializer(), + getExternalCheckResultSerializer(), + ]) + ) + ).deserialize(buffer, authorityOffset); + + const [pluginOffset, pluginOffsetOffset] = u64().deserialize( + buffer, + lifecycleChecksOffset + ); + + const [dataOffset, dataOffsetOffset] = option(u64()).deserialize( + buffer, + pluginOffsetOffset + ); + const [dataLen, dataLenOffset] = option(u64()).deserialize( + buffer, + dataOffsetOffset + ); + return [ + { + pluginType, + authority, + lifecycleChecks, + offset: pluginOffset, + isUnknown, + dataOffset, + dataLen, + }, + dataLenOffset, + ]; + }, + }; +} + export function getPluginRegistryV1AccountDataSerializer(): Serializer< PluginRegistryV1AccountDataArgs, PluginRegistryV1AccountData @@ -92,7 +182,9 @@ export function getPluginRegistryV1AccountDataSerializer(): Serializer< getRegistryRecordSerializer() ).deserialize(buffer, keyOffset); - // TODO deserialize externalPlugins once they are defined, purposefully ignore them now + const [externalRegistry, externalRegistryOffset] = array( + getAdapterRegistryRecordSerializer() + ).deserialize(buffer, registryOffset); return [ { @@ -100,9 +192,11 @@ export function getPluginRegistryV1AccountDataSerializer(): Serializer< registry: registry.filter( (record: RegistryRecordWithUnknown) => !record.isUnknown ), - externalPlugins: [], + externalRegistry: externalRegistry.filter( + (record: ExternalRegistryRecordWithUnknown) => !record.isUnknown + ), }, - registryOffset, + externalRegistryOffset, ]; }, }; diff --git a/clients/js/src/index.ts b/clients/js/src/index.ts index 9dcf9a52..3b144dfb 100644 --- a/clients/js/src/index.ts +++ b/clients/js/src/index.ts @@ -1,7 +1,6 @@ export * from './generated'; export * from './plugin'; export * from './hash'; -export * from './types'; export * from './authority'; export * from './plugins'; export * from './helpers'; diff --git a/clients/js/src/instructions/addPlugin.ts b/clients/js/src/instructions/addPlugin.ts new file mode 100644 index 00000000..f320c50f --- /dev/null +++ b/clients/js/src/instructions/addPlugin.ts @@ -0,0 +1,43 @@ +import { Context } from '@metaplex-foundation/umi'; +import { addPluginV1, addExternalPluginAdapterV1 } from '../generated'; +import { + AssetAddablePluginAuthorityPairArgsV2, + ExternalPluginAdapterInitInfoArgs, + createExternalPluginAdapterInitInfo, + isExternalPluginAdapterType, + pluginAuthorityPairV2, +} from '../plugins'; + +export type AddPluginArgsPlugin = + | AssetAddablePluginAuthorityPairArgsV2 + | ExternalPluginAdapterInitInfoArgs; + +export type AddPluginArgs = Omit< + Parameters[1], + 'plugin' | 'initAuthority' +> & { + plugin: AddPluginArgsPlugin; +}; + +export const addPlugin = ( + context: Pick, + { plugin, ...args }: AddPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return addExternalPluginAdapterV1(context, { + ...args, + initInfo: createExternalPluginAdapterInitInfo( + plugin as ExternalPluginAdapterInitInfoArgs + ), + }); + } + + const pair = pluginAuthorityPairV2( + plugin as AssetAddablePluginAuthorityPairArgsV2 + ); + return addPluginV1(context, { + ...args, + plugin: pair.plugin, + initAuthority: pair.authority, + }); +}; diff --git a/clients/js/src/instructions/approvePluginAuthority.ts b/clients/js/src/instructions/approvePluginAuthority.ts new file mode 100644 index 00000000..51d1e35c --- /dev/null +++ b/clients/js/src/instructions/approvePluginAuthority.ts @@ -0,0 +1,25 @@ +import { Context } from '@metaplex-foundation/umi'; +import { approvePluginAuthorityV1, PluginType } from '../generated'; +import { PluginAuthority, pluginAuthorityToBase } from '../plugins'; + +export type ApprovePluginAuthorityArgsPlugin = { + type: keyof typeof PluginType; +}; + +export type ApprovePluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' | 'newAuthority' +> & { + plugin: ApprovePluginAuthorityArgsPlugin; + newAuthority: PluginAuthority; +}; + +export const approvePluginAuthority = ( + context: Pick, + { plugin, newAuthority, ...args }: ApprovePluginAuthorityArgs +) => + approvePluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + newAuthority: pluginAuthorityToBase(newAuthority), + }); diff --git a/clients/js/src/instructions/burn.ts b/clients/js/src/instructions/burn.ts new file mode 100644 index 00000000..b3478f30 --- /dev/null +++ b/clients/js/src/instructions/burn.ts @@ -0,0 +1,39 @@ +import { Context } from '@metaplex-foundation/umi'; +import { CollectionV1, burnV1, AssetV1 } from '../generated'; +import { findExtraAccounts } from '../plugins'; +import { deriveExternalPluginAdapters } from '../helpers'; + +export type BurnArgs = Omit< + Parameters[1], + 'asset' | 'collection' +> & { + asset: Pick; + collection?: Pick; +}; + +export const burn = ( + context: Pick, + { asset, collection, ...args }: BurnArgs +) => { + const derivedExternalPluginAdapters = deriveExternalPluginAdapters( + asset, + collection + ); + + const extraAccounts = findExtraAccounts( + context, + 'burn', + derivedExternalPluginAdapters, + { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + } + ); + + return burnV1(context, { + ...args, + asset: asset.publicKey, + collection: collection?.publicKey, + }).addRemainingAccounts(extraAccounts); +}; diff --git a/clients/js/src/instructions/collection/addCollectionPlugin.ts b/clients/js/src/instructions/collection/addCollectionPlugin.ts new file mode 100644 index 00000000..95bff169 --- /dev/null +++ b/clients/js/src/instructions/collection/addCollectionPlugin.ts @@ -0,0 +1,49 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + addCollectionExternalPluginAdapterV1, + addCollectionPluginV1, +} from '../../generated'; +import { + CollectionAddablePluginAuthorityPairArgsV2, + pluginAuthorityPairV2, +} from '../../plugins'; + +import { + createExternalPluginAdapterInitInfo, + ExternalPluginAdapterInitInfoArgs, + isExternalPluginAdapterType, +} from '../../plugins/externalPluginAdapters'; + +export type AddCollectionPluginArgsPlugin = + | Exclude + | ExternalPluginAdapterInitInfoArgs; + +export type AddCollectionPluginArgs = Omit< + Parameters[1], + 'plugin' | 'initAuthority' +> & { + plugin: AddCollectionPluginArgsPlugin; +}; + +export const addCollectionPlugin = ( + context: Pick, + { plugin, ...args }: AddCollectionPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return addCollectionExternalPluginAdapterV1(context, { + ...args, + initInfo: createExternalPluginAdapterInitInfo( + plugin as ExternalPluginAdapterInitInfoArgs + ), + }); + } + + const pair = pluginAuthorityPairV2( + plugin as CollectionAddablePluginAuthorityPairArgsV2 + ); + return addCollectionPluginV1(context, { + ...args, + plugin: pair.plugin, + initAuthority: pair.authority, + }); +}; diff --git a/clients/js/src/instructions/collection/approveCollectionPluginAuthority.ts b/clients/js/src/instructions/collection/approveCollectionPluginAuthority.ts new file mode 100644 index 00000000..0786773d --- /dev/null +++ b/clients/js/src/instructions/collection/approveCollectionPluginAuthority.ts @@ -0,0 +1,26 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + approveCollectionPluginAuthorityV1, + PluginType, +} from '../../generated'; +import { PluginAuthority, pluginAuthorityToBase } from '../../plugins'; + +export type ApproveCollectionPluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' | 'newAuthority' +> & { + plugin: { + type: keyof typeof PluginType; + }; + newAuthority: PluginAuthority; +}; + +export const approveCollectionPluginAuthority = ( + context: Pick, + { plugin, newAuthority, ...args }: ApproveCollectionPluginAuthorityArgs +) => + approveCollectionPluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + newAuthority: pluginAuthorityToBase(newAuthority), + }); diff --git a/clients/js/src/instructions/collection/burnCollection.ts b/clients/js/src/instructions/collection/burnCollection.ts new file mode 100644 index 00000000..0d8a403d --- /dev/null +++ b/clients/js/src/instructions/collection/burnCollection.ts @@ -0,0 +1,3 @@ +import { burnCollectionV1 as burnCollection } from '../../generated'; + +export { burnCollection }; diff --git a/clients/js/src/instructions/collection/createCollection.ts b/clients/js/src/instructions/collection/createCollection.ts new file mode 100644 index 00000000..d9c7f181 --- /dev/null +++ b/clients/js/src/instructions/collection/createCollection.ts @@ -0,0 +1,47 @@ +import { Context } from '@metaplex-foundation/umi'; +import { createCollectionV2 } from '../../generated'; +import { + CollectionPluginAuthorityPairArgsV2, + createExternalPluginAdapterInitInfo, + pluginAuthorityPairV2, +} from '../../plugins'; + +import { + ExternalPluginAdapterInitInfoArgs, + isExternalPluginAdapterType, +} from '../../plugins/externalPluginAdapters'; + +export type CreateCollectionArgsPlugin = + | CollectionPluginAuthorityPairArgsV2 + | ExternalPluginAdapterInitInfoArgs; + +export type CreateCollectionArgs = Omit< + Parameters[1], + 'plugins' | 'externalPluginAdapters' +> & { + plugins?: CreateCollectionArgsPlugin[]; +}; + +export const createCollection = ( + context: Pick, + { plugins, ...args }: CreateCollectionArgs +) => { + const firstPartyPlugins: CollectionPluginAuthorityPairArgsV2[] = []; + const externalPluginAdapters: ExternalPluginAdapterInitInfoArgs[] = []; + + plugins?.forEach((plugin) => { + if (isExternalPluginAdapterType(plugin)) { + externalPluginAdapters.push(plugin as ExternalPluginAdapterInitInfoArgs); + } else { + firstPartyPlugins.push(plugin as CollectionPluginAuthorityPairArgsV2); + } + }); + + return createCollectionV2(context, { + ...args, + plugins: firstPartyPlugins.map(pluginAuthorityPairV2), + externalPluginAdapters: externalPluginAdapters.map( + createExternalPluginAdapterInitInfo + ), + }); +}; diff --git a/clients/js/src/instructions/collection/index.ts b/clients/js/src/instructions/collection/index.ts new file mode 100644 index 00000000..fbca7eba --- /dev/null +++ b/clients/js/src/instructions/collection/index.ts @@ -0,0 +1,8 @@ +export * from './addCollectionPlugin'; +export * from './approveCollectionPluginAuthority'; +export * from './burnCollection'; +export * from './createCollection'; +export * from './removeCollectionPlugin'; +export * from './revokeCollectionPluginAuthority'; +export * from './updateCollection'; +export * from './updateCollectionPlugin'; diff --git a/clients/js/src/instructions/collection/removeCollectionPlugin.ts b/clients/js/src/instructions/collection/removeCollectionPlugin.ts new file mode 100644 index 00000000..11a07561 --- /dev/null +++ b/clients/js/src/instructions/collection/removeCollectionPlugin.ts @@ -0,0 +1,42 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + PluginType, + removeCollectionExternalPluginAdapterV1, + removeCollectionPluginV1, +} from '../../generated'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../../plugins'; + +import { isExternalPluginAdapterType } from '../../plugins/externalPluginAdapters'; + +export type RemoveCollectionPluginArgsPlugin = + | { + type: Exclude; + } + | ExternalPluginAdapterKey; + +export type RemoveCollectionPluginArgs = Omit< + Parameters[1], + 'plugin' | 'pluginType' +> & { + plugin: RemoveCollectionPluginArgsPlugin; +}; + +export const removeCollectionPlugin = ( + context: Pick, + { plugin, ...args }: RemoveCollectionPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return removeCollectionExternalPluginAdapterV1(context, { + ...args, + key: externalPluginAdapterKeyToBase(plugin as ExternalPluginAdapterKey), + }); + } + + return removeCollectionPluginV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); +}; diff --git a/clients/js/src/instructions/collection/revokeCollectionPluginAuthority.ts b/clients/js/src/instructions/collection/revokeCollectionPluginAuthority.ts new file mode 100644 index 00000000..d4da63aa --- /dev/null +++ b/clients/js/src/instructions/collection/revokeCollectionPluginAuthority.ts @@ -0,0 +1,20 @@ +import { Context } from '@metaplex-foundation/umi'; +import { revokeCollectionPluginAuthorityV1, PluginType } from '../../generated'; + +export type RevokeCollectionPluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' +> & { + plugin: { + type: keyof typeof PluginType; + }; +}; + +export const revokeCollectionPluginAuthority = ( + context: Pick, + { plugin, ...args }: RevokeCollectionPluginAuthorityArgs +) => + revokeCollectionPluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); diff --git a/clients/js/src/instructions/collection/updateCollection.ts b/clients/js/src/instructions/collection/updateCollection.ts new file mode 100644 index 00000000..8f92f143 --- /dev/null +++ b/clients/js/src/instructions/collection/updateCollection.ts @@ -0,0 +1,23 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + updateCollectionV1, + UpdateCollectionV1InstructionDataArgs, +} from '../../generated'; + +export type UpdateCollectionArgs = Omit< + Parameters[1], + 'newName' | 'newUri' +> & { + name?: UpdateCollectionV1InstructionDataArgs['newName']; + uri?: UpdateCollectionV1InstructionDataArgs['newUri']; +}; + +export const updateCollection = ( + context: Pick, + { name, uri, ...args }: UpdateCollectionArgs +) => + updateCollectionV1(context, { + ...args, + newName: name, + newUri: uri, + }); diff --git a/clients/js/src/instructions/collection/updateCollectionPlugin.ts b/clients/js/src/instructions/collection/updateCollectionPlugin.ts new file mode 100644 index 00000000..5584adda --- /dev/null +++ b/clients/js/src/instructions/collection/updateCollectionPlugin.ts @@ -0,0 +1,43 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + updateCollectionPluginV1, + updateCollectionExternalPluginAdapterV1, +} from '../../generated'; +import { + createExternalPluginAdapterUpdateInfo, + createPluginV2, + externalPluginAdapterKeyToBase, + isExternalPluginAdapterType, + CollectionAllPluginArgsV2, +} from '../../plugins'; +import { ExternalPluginAdapterUpdateInfoArgs } from '../../plugins/externalPluginAdapters'; + +export type UpdateCollectionPluginArgsPlugin = + | CollectionAllPluginArgsV2 + | ExternalPluginAdapterUpdateInfoArgs; + +export type UpdateCollectionPluginArgs = Omit< + Parameters[1], + 'plugin' +> & { + plugin: UpdateCollectionPluginArgsPlugin; +}; + +export const updateCollectionPlugin = ( + context: Pick, + { plugin, ...args }: UpdateCollectionPluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + const plug = plugin as ExternalPluginAdapterUpdateInfoArgs; + return updateCollectionExternalPluginAdapterV1(context, { + ...args, + updateInfo: createExternalPluginAdapterUpdateInfo(plug), + key: externalPluginAdapterKeyToBase(plug.key), + }); + } + + return updateCollectionPluginV1(context, { + ...args, + plugin: createPluginV2(plugin as CollectionAllPluginArgsV2), + }); +}; diff --git a/clients/js/src/instructions/create.ts b/clients/js/src/instructions/create.ts new file mode 100644 index 00000000..e3099ac3 --- /dev/null +++ b/clients/js/src/instructions/create.ts @@ -0,0 +1,109 @@ +import { Context, publicKey } from '@metaplex-foundation/umi'; +import { + CollectionV1, + createV2, + ExternalPluginAdapterSchema, +} from '../generated'; +import { + createExternalPluginAdapterInitInfo, + findExtraAccounts, + AssetPluginAuthorityPairArgsV2, + pluginAuthorityPairV2, +} from '../plugins'; +import { deriveExternalPluginAdapters } from '../helpers'; +import { + ExternalPluginAdapterInitInfoArgs, + ExternalPluginAdaptersList, + isExternalPluginAdapterType, +} from '../plugins/externalPluginAdapters'; + +export type CreateArgsPlugin = + | AssetPluginAuthorityPairArgsV2 + | ExternalPluginAdapterInitInfoArgs; + +export type CreateArgs = Omit< + Parameters[1], + 'plugins' | 'externalPluginAdapters' | 'collection' +> & { + collection?: Pick; + plugins?: CreateArgsPlugin[]; +}; + +export const create = ( + context: Pick, + { asset, plugins, collection, ...args }: CreateArgs +) => { + const owner = args.owner || args.updateAuthority || args.payer; + + const assetExternalPluginAdapters: ExternalPluginAdaptersList = { + oracles: [], + lifecycleHooks: [], + }; + + const externalPluginAdapters: ExternalPluginAdapterInitInfoArgs[] = []; + const firstPartyPlugins: AssetPluginAuthorityPairArgsV2[] = []; + + // Create dummy external plugin adapters to resuse findExtraAccounts method + plugins?.forEach((plugin) => { + if (isExternalPluginAdapterType(plugin)) { + externalPluginAdapters.push(plugin as ExternalPluginAdapterInitInfoArgs); + switch (plugin.type) { + case 'Oracle': + assetExternalPluginAdapters.oracles?.push({ + ...plugin, + resultsOffset: plugin.resultsOffset || { type: 'NoOffset' }, + baseAddress: plugin.baseAddress, + authority: plugin.initPluginAuthority || { + type: 'UpdateAuthority', + }, + type: 'Oracle', + }); + break; + case 'DataStore': + // Do nothing, datastore has no extra accounts + break; + case 'LifecycleHook': + assetExternalPluginAdapters.lifecycleHooks?.push({ + ...plugin, + hookedProgram: plugin.hookedProgram, + authority: plugin.initPluginAuthority || { + type: 'UpdateAuthority', + }, + type: 'LifecycleHook', + schema: plugin.schema || ExternalPluginAdapterSchema.Binary, + }); + break; + default: + // Do nothing + } + } else { + firstPartyPlugins.push(plugin as AssetPluginAuthorityPairArgsV2); + } + }); + + const derivedExternalPluginAdapters = deriveExternalPluginAdapters( + assetExternalPluginAdapters, + collection + ); + const extraAccounts = findExtraAccounts( + context, + 'create', + derivedExternalPluginAdapters, + { + asset: asset.publicKey, + collection: collection ? collection.publicKey : undefined, + // need to replicate program behavior + owner: owner ? publicKey(owner) : context.identity.publicKey, + } + ); + + return createV2(context, { + ...args, + plugins: firstPartyPlugins.map(pluginAuthorityPairV2), + externalPluginAdapters: externalPluginAdapters.map( + createExternalPluginAdapterInitInfo + ), + asset, + collection: collection ? collection.publicKey : undefined, + }).addRemainingAccounts(extraAccounts); +}; diff --git a/clients/js/src/instructions/freeze.ts b/clients/js/src/instructions/freeze.ts index e6b79610..66fbef38 100644 --- a/clients/js/src/instructions/freeze.ts +++ b/clients/js/src/instructions/freeze.ts @@ -1,5 +1,6 @@ import { Context, + publicKey, PublicKey, Signer, transactionBuilder, @@ -19,7 +20,7 @@ import { addressPluginAuthority } from '../authority'; export type FreezeAssetArgs = { asset: AssetV1; - delegate: PublicKey; + delegate: PublicKey | Signer; authority?: Signer; collection?: CollectionV1; }; @@ -75,7 +76,7 @@ export function freezeAsset( type: 'FreezeDelegate', data: { frozen: true }, }), - initAuthority: addressPluginAuthority(delegate), + initAuthority: addressPluginAuthority(publicKey(delegate)), authority, }) ); diff --git a/clients/js/src/instructions/index.ts b/clients/js/src/instructions/index.ts index ef5ef72c..49825646 100644 --- a/clients/js/src/instructions/index.ts +++ b/clients/js/src/instructions/index.ts @@ -1,3 +1,13 @@ export * from './legacyDelegate'; export * from './legacyRevoke'; export * from './freeze'; +export * from './create'; +export * from './update'; +export * from './transfer'; +export * from './burn'; +export * from './addPlugin'; +export * from './removePlugin'; +export * from './updatePlugin'; +export * from './approvePluginAuthority'; +export * from './revokePluginAuthority'; +export * from './collection'; diff --git a/clients/js/src/instructions/legacyDelegate.ts b/clients/js/src/instructions/legacyDelegate.ts index a61cae7c..c64b83c3 100644 --- a/clients/js/src/instructions/legacyDelegate.ts +++ b/clients/js/src/instructions/legacyDelegate.ts @@ -4,15 +4,14 @@ import { transactionBuilder, } from '@metaplex-foundation/umi'; import { ERR_CANNOT_DELEGATE } from './errors'; +import { addPluginV1, AssetV1 } from '../generated'; import { - addPluginV1, - approvePluginAuthorityV1, - AssetV1, - PluginType, -} from '../generated'; -import { createPlugin, pluginKeyToPluginType } from '../plugins'; + AssetPluginsList, + createPlugin, + pluginKeyToPluginType, +} from '../plugins'; import { addressPluginAuthority } from '../authority'; -import { PluginsList } from '../types'; +import { approvePluginAuthority } from './approvePluginAuthority'; export function legacyDelegate( context: Pick, @@ -51,13 +50,16 @@ export function legacyDelegate( // Change the plugin authority of the defined plugins. definedPluginsKeys.forEach((pluginKey) => { - const plugType = pluginKeyToPluginType(pluginKey as keyof PluginsList); + const plugType = pluginKeyToPluginType(pluginKey as keyof AssetPluginsList); txBuilder = txBuilder.add( - approvePluginAuthorityV1(context, { + approvePluginAuthority(context, { asset: asset.publicKey, - pluginType: PluginType[plugType], - newAuthority: addressPluginAuthority(targetDelegate), + plugin: { type: plugType }, + newAuthority: { + type: 'Address', + address: targetDelegate, + }, }) ); }); diff --git a/clients/js/src/instructions/legacyRevoke.ts b/clients/js/src/instructions/legacyRevoke.ts index 6f3ec82f..2bcc5b0c 100644 --- a/clients/js/src/instructions/legacyRevoke.ts +++ b/clients/js/src/instructions/legacyRevoke.ts @@ -1,8 +1,7 @@ import { Context, transactionBuilder } from '@metaplex-foundation/umi'; import { AssetV1, PluginType, revokePluginAuthorityV1 } from '../generated'; import { ERR_CANNOT_REVOKE } from './errors'; -import { pluginKeyToPluginType } from '../plugins'; -import { PluginsList } from '../types'; +import { AssetPluginsList, pluginKeyToPluginType } from '../plugins'; export function legacyRevoke( context: Pick, @@ -39,7 +38,7 @@ export function legacyRevoke( // Change the plugin authority of the defined plugins. Object.keys(definedPlugins).forEach((pluginKey) => { - const plugType = pluginKeyToPluginType(pluginKey as keyof PluginsList); + const plugType = pluginKeyToPluginType(pluginKey as keyof AssetPluginsList); txBuilder = txBuilder.add( revokePluginAuthorityV1(context, { diff --git a/clients/js/src/instructions/removePlugin.ts b/clients/js/src/instructions/removePlugin.ts new file mode 100644 index 00000000..8b29e4f9 --- /dev/null +++ b/clients/js/src/instructions/removePlugin.ts @@ -0,0 +1,41 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + removePluginV1, + removeExternalPluginAdapterV1, + PluginType, +} from '../generated'; +import { isExternalPluginAdapterType } from '../plugins'; +import { + ExternalPluginAdapterKey, + externalPluginAdapterKeyToBase, +} from '../plugins/externalPluginAdapterKey'; + +export type RemovePluginArgsPlugin = + | { + type: Exclude; + } + | ExternalPluginAdapterKey; + +export type RemovePluginArgs = Omit< + Parameters[1], + 'pluginType' +> & { + plugin: RemovePluginArgsPlugin; +}; + +export const removePlugin = ( + context: Pick, + { plugin, ...args }: RemovePluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + return removeExternalPluginAdapterV1(context, { + ...args, + key: externalPluginAdapterKeyToBase(plugin as ExternalPluginAdapterKey), + }); + } + + return removePluginV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); +}; diff --git a/clients/js/src/instructions/revokePluginAuthority.ts b/clients/js/src/instructions/revokePluginAuthority.ts new file mode 100644 index 00000000..e83e29e8 --- /dev/null +++ b/clients/js/src/instructions/revokePluginAuthority.ts @@ -0,0 +1,22 @@ +import { Context } from '@metaplex-foundation/umi'; +import { revokePluginAuthorityV1, PluginType } from '../generated'; + +export type RevokePluginAuthorityArgsPlugin = { + type: keyof typeof PluginType; +}; + +export type RevokePluginAuthorityArgs = Omit< + Parameters[1], + 'pluginType' +> & { + plugin: RevokePluginAuthorityArgsPlugin; +}; + +export const revokePluginAuthority = ( + context: Pick, + { plugin, ...args }: RevokePluginAuthorityArgs +) => + revokePluginAuthorityV1(context, { + ...args, + pluginType: PluginType[plugin.type as keyof typeof PluginType], + }); diff --git a/clients/js/src/instructions/transfer.ts b/clients/js/src/instructions/transfer.ts new file mode 100644 index 00000000..43cf7bf4 --- /dev/null +++ b/clients/js/src/instructions/transfer.ts @@ -0,0 +1,40 @@ +import { Context, publicKey } from '@metaplex-foundation/umi'; +import { CollectionV1, transferV1, AssetV1 } from '../generated'; +import { findExtraAccounts } from '../plugins'; +import { deriveExternalPluginAdapters } from '../helpers'; + +export type TransferArgs = Omit< + Parameters[1], + 'asset' | 'collection' +> & { + asset: Pick; + collection?: Pick; +}; + +export const transfer = ( + context: Pick, + { asset, collection, ...args }: TransferArgs +) => { + const derivedExternalPluginAdapters = deriveExternalPluginAdapters( + asset, + collection + ); + + const extraAccounts = findExtraAccounts( + context, + 'transfer', + derivedExternalPluginAdapters, + { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + recipient: publicKey(args.newOwner), + } + ); + + return transferV1(context, { + ...args, + asset: asset.publicKey, + collection: collection?.publicKey, + }).addRemainingAccounts(extraAccounts); +}; diff --git a/clients/js/src/instructions/update.ts b/clients/js/src/instructions/update.ts new file mode 100644 index 00000000..a7b949e4 --- /dev/null +++ b/clients/js/src/instructions/update.ts @@ -0,0 +1,48 @@ +import { Context } from '@metaplex-foundation/umi'; +import { + CollectionV1, + updateV1, + AssetV1, + UpdateV1InstructionDataArgs, +} from '../generated'; +import { findExtraAccounts } from '../plugins'; +import { deriveExternalPluginAdapters } from '../helpers'; + +export type UpdateArgs = Omit< + Parameters[1], + 'asset' | 'collection' | 'newName' | 'newUri' +> & { + asset: Pick; + collection?: Pick; + name?: UpdateV1InstructionDataArgs['newName']; + uri?: UpdateV1InstructionDataArgs['newUri']; +}; + +export const update = ( + context: Pick, + { asset, collection, name, uri, ...args }: UpdateArgs +) => { + const derivedExternalPluginAdapters = deriveExternalPluginAdapters( + asset, + collection + ); + + const extraAccounts = findExtraAccounts( + context, + 'update', + derivedExternalPluginAdapters, + { + asset: asset.publicKey, + collection: collection?.publicKey, + owner: asset.owner, + } + ); + + return updateV1(context, { + ...args, + asset: asset.publicKey, + collection: collection?.publicKey, + newName: name, + newUri: uri, + }).addRemainingAccounts(extraAccounts); +}; diff --git a/clients/js/src/instructions/updatePlugin.ts b/clients/js/src/instructions/updatePlugin.ts new file mode 100644 index 00000000..276247b2 --- /dev/null +++ b/clients/js/src/instructions/updatePlugin.ts @@ -0,0 +1,40 @@ +import { Context } from '@metaplex-foundation/umi'; +import { updatePluginV1, updateExternalPluginAdapterV1 } from '../generated'; +import { + createExternalPluginAdapterUpdateInfo, + createPluginV2, + externalPluginAdapterKeyToBase, + isExternalPluginAdapterType, + AssetAllPluginArgsV2, +} from '../plugins'; +import { ExternalPluginAdapterUpdateInfoArgs } from '../plugins/externalPluginAdapters'; + +export type UpdatePluginArgsPlugin = + | AssetAllPluginArgsV2 + | ExternalPluginAdapterUpdateInfoArgs; + +export type UpdatePluginArgs = Omit< + Parameters[1], + 'plugin' +> & { + plugin: UpdatePluginArgsPlugin; +}; + +export const updatePlugin = ( + context: Pick, + { plugin, ...args }: UpdatePluginArgs +) => { + if (isExternalPluginAdapterType(plugin)) { + const plug = plugin as ExternalPluginAdapterUpdateInfoArgs; + return updateExternalPluginAdapterV1(context, { + ...args, + updateInfo: createExternalPluginAdapterUpdateInfo(plug), + key: externalPluginAdapterKeyToBase(plug.key), + }); + } + + return updatePluginV1(context, { + ...args, + plugin: createPluginV2(plugin as AssetAllPluginArgsV2), + }); +}; diff --git a/clients/js/src/plugins.ts b/clients/js/src/plugins.ts deleted file mode 100644 index d8450612..00000000 --- a/clients/js/src/plugins.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { none, some } from '@metaplex-foundation/umi'; - -import { - Key, - PluginHeaderV1, - Plugin as BasePlugin, - getPluginSerializer, - RegistryRecord, - PluginAuthorityPair, - PluginAuthority, - RoyaltiesArgs, - FreezeDelegateArgs, - AttributesArgs, - PermanentFreezeDelegateArgs, - PluginType, - UpdateDelegateArgs, - EditionArgs, - MasterEditionArgs, -} from './generated'; -import { BasePluginAuthority, PluginsList } from './types'; -import { mapPluginAuthority } from './authority'; -import { toWords } from './utils'; - -export function formPluginHeaderV1( - pluginRegistryOffset: bigint -): Omit { - return { - key: Key.PluginHeaderV1, - pluginRegistryOffset, - }; -} - -export type PluginAuthorityPairHelperArgs = CreatePluginArgs & { - authority?: PluginAuthority; -}; - -export type CreatePluginArgs = - | { - type: 'Royalties'; - data: RoyaltiesArgs; - } - | { - type: 'FreezeDelegate'; - data: FreezeDelegateArgs; - } - | { - type: 'BurnDelegate'; - } - | { - type: 'TransferDelegate'; - } - | { - type: 'UpdateDelegate'; - data?: UpdateDelegateArgs; - } - | { - type: 'Attributes'; - data: AttributesArgs; - } - | { - type: 'PermanentFreezeDelegate'; - data: PermanentFreezeDelegateArgs; - } - | { - type: 'PermanentTransferDelegate'; - } - | { - type: 'PermanentBurnDelegate'; - } - | { - type: 'Edition'; - data: EditionArgs; - } - | { - type: 'MasterEdition'; - data: MasterEditionArgs; - } - | { - type: 'ImmutableMetadata'; - } - | { - type: 'AddBlocker'; - }; - -export function createPlugin(args: CreatePluginArgs): BasePlugin { - // TODO refactor when there are more required empty fields in plugins - if (args.type === 'UpdateDelegate') { - return { - __kind: args.type, - fields: [ - (args as any).data || { - additionalDelegates: [], - }, - ], - }; - } - return { - __kind: args.type, - fields: [(args as any).data || {}], - }; -} - -export function pluginAuthorityPair( - args: PluginAuthorityPairHelperArgs -): PluginAuthorityPair { - const { type, authority, data } = args as any; - // TODO refactor when there are more required empty fields in plugins - if (type === 'UpdateDelegate') { - return { - plugin: { - __kind: type, - fields: [ - data || { - additionalDelegates: [], - }, - ], - }, - authority: authority ? some(authority) : none(), - }; - } - return { - plugin: { __kind: type, fields: [data || {}] }, - authority: authority ? some(authority) : none(), - }; -} - -export function mapPluginFields(fields: Array>) { - return fields.reduce((acc2, field) => ({ ...acc2, ...field }), {}); -} - -export function mapPlugin({ - plugin: plug, - authority, - offset, -}: { - plugin: Exclude; - authority: BasePluginAuthority; - offset: bigint; -}): PluginsList { - const pluginKey = toWords(plug.__kind) - .toLowerCase() - .split(' ') - .reduce((s, c) => s + (c.charAt(0).toUpperCase() + c.slice(1))); - - return { - [pluginKey]: { - authority, - offset, - ...('fields' in plug ? mapPluginFields(plug.fields) : {}), - }, - }; -} - -export function registryRecordsToPluginsList( - registryRecords: RegistryRecord[], - accountData: Uint8Array -) { - return registryRecords.reduce((acc: PluginsList, record) => { - const mappedAuthority = mapPluginAuthority(record.authority); - const deserializedPlugin = getPluginSerializer().deserialize( - accountData, - Number(record.offset) - )[0]; - - acc = { - ...acc, - ...mapPlugin({ - plugin: deserializedPlugin, - authority: mappedAuthority, - offset: record.offset, - }), - }; - - return acc; - }, {}); -} - -export function pluginKeyToPluginType(pluginKey: keyof PluginsList) { - return (pluginKey.charAt(0).toUpperCase() + - pluginKey.slice(1)) as keyof typeof PluginType; -} diff --git a/clients/js/src/plugins/dataStore.ts b/clients/js/src/plugins/dataStore.ts new file mode 100644 index 00000000..04aa1ca0 --- /dev/null +++ b/clients/js/src/plugins/dataStore.ts @@ -0,0 +1,93 @@ +import { + BaseDataStore, + BaseDataStoreInitInfoArgs, + BaseDataStoreUpdateInfoArgs, + ExternalPluginAdapterSchema, + ExternalRegistryRecord, +} from '../generated'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { parseExternalPluginAdapterData } from './lib'; +import { LifecycleChecks } from './lifecycleChecks'; +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; + +export type DataStore = Omit & { + dataAuthority: PluginAuthority; + data?: any; +}; + +export type DataStorePlugin = BaseExternalPluginAdapter & + DataStore & { + type: 'DataStore'; + dataAuthority: PluginAuthority; + }; + +export type DataStoreInitInfoArgs = Omit< + BaseDataStoreInitInfoArgs, + 'initPluginAuthority' | 'lifecycleChecks' | 'dataAuthority' +> & { + type: 'DataStore'; + initPluginAuthority?: PluginAuthority; + lifecycleChecks?: LifecycleChecks; + schema?: ExternalPluginAdapterSchema; + dataAuthority: PluginAuthority; +}; + +export type DataStoreUpdateInfoArgs = Omit< + BaseDataStoreUpdateInfoArgs, + 'schema' +> & { + key: ExternalPluginAdapterKey; + schema?: ExternalPluginAdapterSchema; +}; + +export function dataStoreInitInfoArgsToBase( + d: DataStoreInitInfoArgs +): BaseDataStoreInitInfoArgs { + return { + dataAuthority: pluginAuthorityToBase(d.dataAuthority), + initPluginAuthority: d.initPluginAuthority + ? pluginAuthorityToBase(d.initPluginAuthority) + : null, + schema: d.schema ? d.schema : null, + }; +} + +export function dataStoreUpdateInfoArgsToBase( + d: DataStoreUpdateInfoArgs +): BaseDataStoreUpdateInfoArgs { + return { + schema: d.schema ? d.schema : null, + }; +} + +export function dataStoreFromBase( + s: BaseDataStore, + r: ExternalRegistryRecord, + account: Uint8Array +): DataStore { + return { + ...s, + dataAuthority: pluginAuthorityFromBase(s.dataAuthority), + data: parseExternalPluginAdapterData(s, r, account), + }; +} + +export const dataStoreManifest: ExternalPluginAdapterManifest< + DataStore, + BaseDataStore, + DataStoreInitInfoArgs, + BaseDataStoreInitInfoArgs, + DataStoreUpdateInfoArgs, + BaseDataStoreUpdateInfoArgs +> = { + type: 'DataStore', + fromBase: dataStoreFromBase, + initToBase: dataStoreInitInfoArgsToBase, + updateToBase: dataStoreUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/externalPluginAdapterKey.ts b/clients/js/src/plugins/externalPluginAdapterKey.ts new file mode 100644 index 00000000..e86610b0 --- /dev/null +++ b/clients/js/src/plugins/externalPluginAdapterKey.ts @@ -0,0 +1,38 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { BaseExternalPluginAdapterKey } from '../generated'; +import { PluginAuthority, pluginAuthorityToBase } from './pluginAuthority'; + +export type ExternalPluginAdapterKey = + | { + type: 'Oracle'; + baseAddress: PublicKey; + } + | { + type: 'DataStore'; + dataAuthority: PluginAuthority; + } + | { + type: 'LifecycleHook'; + hookedProgram: PublicKey; + }; + +export function externalPluginAdapterKeyToBase( + e: ExternalPluginAdapterKey +): BaseExternalPluginAdapterKey { + if (e.type === 'Oracle') { + return { + __kind: 'Oracle', + fields: [e.baseAddress], + }; + } + if (e.type === 'DataStore') { + return { + __kind: 'DataStore', + fields: [pluginAuthorityToBase(e.dataAuthority)], + }; + } + return { + __kind: 'LifecycleHook', + fields: [e.hookedProgram], + }; +} diff --git a/clients/js/src/plugins/externalPluginAdapterManifest.ts b/clients/js/src/plugins/externalPluginAdapterManifest.ts new file mode 100644 index 00000000..b7704965 --- /dev/null +++ b/clients/js/src/plugins/externalPluginAdapterManifest.ts @@ -0,0 +1,20 @@ +import { ExternalRegistryRecord } from 'src/generated'; +import { ExternalPluginAdapterTypeString } from './externalPluginAdapters'; + +export type ExternalPluginAdapterManifest< + T extends Object, + Base extends Object, + Init extends Object, + InitBase extends Object, + Update extends Object, + UpdateBase extends Object, +> = { + type: ExternalPluginAdapterTypeString; + fromBase: ( + input: Base, + record: ExternalRegistryRecord, + account: Uint8Array + ) => T; + initToBase: (input: Init) => InitBase; + updateToBase: (input: Update) => UpdateBase; +}; diff --git a/clients/js/src/plugins/externalPluginAdapters.ts b/clients/js/src/plugins/externalPluginAdapters.ts new file mode 100644 index 00000000..d5dcaef0 --- /dev/null +++ b/clients/js/src/plugins/externalPluginAdapters.ts @@ -0,0 +1,228 @@ +import { AccountMeta, Context, PublicKey } from '@metaplex-foundation/umi'; +import { + lifecycleHookFromBase, + LifecycleHookInitInfoArgs, + lifecycleHookManifest, + LifecycleHookPlugin, + LifecycleHookUpdateInfoArgs, + pluginAuthorityFromBase, +} from '.'; +import { + BaseExternalPluginAdapterInitInfoArgs, + BaseExternalPluginAdapterKey, + BaseExternalPluginAdapterUpdateInfoArgs, + ExternalRegistryRecord, + getExternalPluginAdapterSerializer, +} from '../generated'; + +import { + dataStoreFromBase, + DataStoreInitInfoArgs, + dataStoreManifest, + DataStorePlugin, + DataStoreUpdateInfoArgs, +} from './dataStore'; +import { + LifecycleChecksContainer, + lifecycleChecksFromBase, + LifecycleEvent, +} from './lifecycleChecks'; +import { + oracleFromBase, + OracleInitInfoArgs, + oracleManifest, + OraclePlugin, + OracleUpdateInfoArgs, +} from './oracle'; +import { BasePlugin } from './types'; +import { extraAccountToAccountMeta } from './extraAccount'; + +export type ExternalPluginAdapterTypeString = + BaseExternalPluginAdapterKey['__kind']; + +export type BaseExternalPluginAdapter = BasePlugin & LifecycleChecksContainer; + +export type ExternalPluginAdaptersList = { + oracles?: OraclePlugin[]; + dataStores?: DataStorePlugin[]; + lifecycleHooks?: LifecycleHookPlugin[]; +}; + +export type ExternalPluginAdapterInitInfoArgs = + | ({ + type: 'Oracle'; + } & OracleInitInfoArgs) + | ({ + type: 'LifecycleHook'; + } & LifecycleHookInitInfoArgs) + | ({ + type: 'DataStore'; + } & DataStoreInitInfoArgs); + +export type ExternalPluginAdapterUpdateInfoArgs = + | ({ + type: 'Oracle'; + } & OracleUpdateInfoArgs) + | ({ + type: 'LifecycleHook'; + } & LifecycleHookUpdateInfoArgs) + | ({ + type: 'DataStore'; + } & DataStoreUpdateInfoArgs); + +export const externalPluginAdapterManifests = { + Oracle: oracleManifest, + DataStore: dataStoreManifest, + LifecycleHook: lifecycleHookManifest, +}; + +export type ExternalPluginAdapterData = { + dataLen: bigint; + dataOffset: bigint; +}; + +export function externalRegistryRecordsToExternalPluginAdapterList( + records: ExternalRegistryRecord[], + accountData: Uint8Array +): ExternalPluginAdaptersList { + const result: ExternalPluginAdaptersList = {}; + + records.forEach((record) => { + const deserializedPlugin = getExternalPluginAdapterSerializer().deserialize( + accountData, + Number(record.offset) + )[0]; + + const mappedPlugin: BaseExternalPluginAdapter = { + lifecycleChecks: + record.lifecycleChecks.__option === 'Some' + ? lifecycleChecksFromBase(record.lifecycleChecks.value) + : undefined, + authority: pluginAuthorityFromBase(record.authority), + offset: record.offset, + }; + + if (deserializedPlugin.__kind === 'Oracle') { + if (!result.oracles) { + result.oracles = []; + } + + result.oracles.push({ + type: 'Oracle', + ...mappedPlugin, + ...oracleFromBase(deserializedPlugin.fields[0], record, accountData), + }); + } else if (deserializedPlugin.__kind === 'DataStore') { + if (!result.dataStores) { + result.dataStores = []; + } + result.dataStores.push({ + type: 'DataStore', + ...mappedPlugin, + ...dataStoreFromBase(deserializedPlugin.fields[0], record, accountData), + }); + } else if (deserializedPlugin.__kind === 'LifecycleHook') { + if (!result.lifecycleHooks) { + result.lifecycleHooks = []; + } + result.lifecycleHooks.push({ + type: 'LifecycleHook', + ...mappedPlugin, + ...lifecycleHookFromBase( + deserializedPlugin.fields[0], + record, + accountData + ), + }); + } + }); + + return result; +} + +export const isExternalPluginAdapterType = (plugin: { type: string }) => { + if ( + plugin.type === 'Oracle' || + plugin.type === 'LifecycleHook' || + plugin.type === 'DataStore' + ) { + return true; + } + return false; +}; + +export function createExternalPluginAdapterInitInfo({ + type, + ...args +}: ExternalPluginAdapterInitInfoArgs): BaseExternalPluginAdapterInitInfoArgs { + const manifest = externalPluginAdapterManifests[type]; + return { + __kind: type, + fields: [manifest.initToBase(args as any)] as any, + }; +} + +export function createExternalPluginAdapterUpdateInfo({ + type, + ...args +}: ExternalPluginAdapterUpdateInfoArgs): BaseExternalPluginAdapterUpdateInfoArgs { + const manifest = externalPluginAdapterManifests[type]; + return { + __kind: type, + fields: [manifest.updateToBase(args as any)] as any, + }; +} + +export const findExtraAccounts = ( + context: Pick, + lifecycle: LifecycleEvent, + externalPluginAdapters: ExternalPluginAdaptersList, + inputs: { + asset: PublicKey; + collection?: PublicKey; + owner: PublicKey; + recipient?: PublicKey; + } +): AccountMeta[] => { + const accounts: AccountMeta[] = []; + + externalPluginAdapters.oracles?.forEach((oracle) => { + if (oracle.lifecycleChecks?.[lifecycle]) { + if (oracle.baseAddressConfig) { + accounts.push( + extraAccountToAccountMeta(context, oracle.baseAddressConfig, { + ...inputs, + program: oracle.baseAddress, + }) + ); + } else { + accounts.push({ + pubkey: oracle.baseAddress, + isSigner: false, + isWritable: false, + }); + } + } + }); + + externalPluginAdapters.lifecycleHooks?.forEach((hook) => { + if (hook.lifecycleChecks?.[lifecycle]) { + accounts.push({ + pubkey: hook.hookedProgram, + isSigner: false, + isWritable: false, + }); + + hook.extraAccounts?.forEach((extra) => { + accounts.push( + extraAccountToAccountMeta(context, extra, { + ...inputs, + program: hook.hookedProgram, + }) + ); + }); + } + }); + + return accounts; +}; diff --git a/clients/js/src/plugins/extraAccount.ts b/clients/js/src/plugins/extraAccount.ts new file mode 100644 index 00000000..cacab94b --- /dev/null +++ b/clients/js/src/plugins/extraAccount.ts @@ -0,0 +1,254 @@ +import { AccountMeta, Context, PublicKey } from '@metaplex-foundation/umi'; +import { + string, + publicKey as publicKeySerializer, +} from '@metaplex-foundation/umi/serializers'; +import { BaseExtraAccount } from '../generated'; +import { Seed, seedFromBase, seedToBase } from './seed'; +import { RenameToType, someOrNone, unwrapOption } from '../utils'; + +export const PRECONFIGURED_SEED = 'mpl-core'; + +export const findPreconfiguredPda = ( + context: Pick, + program: PublicKey, + key: PublicKey +) => + context.eddsa.findPda(program, [ + string({ size: 'variable' }).serialize(PRECONFIGURED_SEED), + publicKeySerializer().serialize(key), + ]); + +export type ExtraAccount = + | (Omit< + Exclude< + RenameToType, + { type: 'CustomPda' } | { type: 'Address' } + >, + 'isSigner' | 'isWritable' + > & { + isSigner?: boolean; + isWritable?: boolean; + }) + | { + type: 'CustomPda'; + seeds: Array; + customProgramId?: PublicKey; + isSigner?: boolean; + isWritable?: boolean; + } + | { + type: 'Address'; + address: PublicKey; + isSigner?: boolean; + isWritable?: boolean; + }; + +export function extraAccountToAccountMeta( + context: Pick, + e: ExtraAccount, + inputs: { + program?: PublicKey; + asset?: PublicKey; + collection?: PublicKey; + recipient?: PublicKey; + owner?: PublicKey; + } +): AccountMeta { + const acccountMeta: Pick = { + isSigner: e.isSigner || false, + isWritable: e.isWritable || false, + }; + + const requiredInputs = getExtraAccountRequiredInputs(e); + const missing: string[] = []; + + requiredInputs.forEach((input) => { + if (!inputs[input]) { + missing.push(input); + } + }); + + if (missing.length) { + throw new Error( + `Missing required inputs to derive account address: ${missing.join(', ')}` + ); + } + switch (e.type) { + case 'PreconfiguredProgram': + return { + ...acccountMeta, + pubkey: context.eddsa.findPda(inputs.program!, [ + string({ size: 'variable' }).serialize(PRECONFIGURED_SEED), + ])[0], + }; + case 'PreconfiguredCollection': + return { + ...acccountMeta, + pubkey: findPreconfiguredPda( + context, + inputs.program!, + inputs.collection! + )[0], + }; + case 'PreconfiguredOwner': + return { + ...acccountMeta, + pubkey: findPreconfiguredPda( + context, + inputs.program!, + inputs.owner! + )[0], + }; + case 'PreconfiguredRecipient': + return { + ...acccountMeta, + pubkey: findPreconfiguredPda( + context, + inputs.program!, + inputs.recipient! + )[0], + }; + case 'PreconfiguredAsset': + return { + ...acccountMeta, + pubkey: findPreconfiguredPda( + context, + inputs.program!, + inputs.asset! + )[0], + }; + case 'CustomPda': + return { + pubkey: context.eddsa.findPda( + e.customProgramId ? e.customProgramId : inputs.program!, + e.seeds.map((seed) => { + switch (seed.type) { + case 'Collection': + return publicKeySerializer().serialize(inputs.collection!); + case 'Owner': + return publicKeySerializer().serialize(inputs.owner!); + case 'Recipient': + return publicKeySerializer().serialize(inputs.recipient!); + case 'Asset': + return publicKeySerializer().serialize(inputs.asset!); + case 'Address': + return publicKeySerializer().serialize(seed.pubkey); + case 'Bytes': + return seed.bytes; + default: + throw new Error('Unknown seed type'); + } + }) + )[0], + ...acccountMeta, + }; + case 'Address': + return { + ...acccountMeta, + pubkey: e.address, + }; + default: + throw new Error('Unknown extra account type'); + } +} + +export function extraAccountToBase(s: ExtraAccount): BaseExtraAccount { + const acccountMeta: Pick = { + isSigner: s.isSigner || false, + isWritable: s.isWritable || false, + }; + if (s.type === 'CustomPda') { + return { + __kind: 'CustomPda', + ...acccountMeta, + seeds: s.seeds.map(seedToBase), + customProgramId: someOrNone(s.customProgramId), + }; + } + if (s.type === 'Address') { + return { + __kind: 'Address', + ...acccountMeta, + address: s.address, + }; + } + + return { + __kind: s.type, + ...acccountMeta, + }; +} + +export function extraAccountFromBase(s: BaseExtraAccount): ExtraAccount { + if (s.__kind === 'CustomPda') { + return { + type: 'CustomPda', + isSigner: s.isSigner, + isWritable: s.isWritable, + seeds: s.seeds.map(seedFromBase), + customProgramId: unwrapOption(s.customProgramId), + }; + } + if (s.__kind === 'Address') { + return { + type: 'Address', + isSigner: s.isSigner, + isWritable: s.isWritable, + address: s.address, + }; + } + + return { + type: s.__kind, + isSigner: s.isSigner, + isWritable: s.isWritable, + }; +} + +export type ExtraAccountInput = + | 'owner' + | 'recipient' + | 'asset' + | 'collection' + | 'program'; + +const EXTRA_ACCOUNT_INPUT_MAP: { + [type in ExtraAccount['type']]?: ExtraAccountInput; +} = { + PreconfiguredOwner: 'owner', + PreconfiguredRecipient: 'recipient', + PreconfiguredAsset: 'asset', + PreconfiguredCollection: 'collection', + PreconfiguredProgram: 'program', +}; + +export function getExtraAccountRequiredInputs( + s: ExtraAccount +): ExtraAccountInput[] { + const preconfigured = EXTRA_ACCOUNT_INPUT_MAP[s.type]; + if (preconfigured) { + return [preconfigured]; + } + + if (s.type === 'CustomPda') { + return s.seeds + .map((seed) => { + switch (seed.type) { + case 'Collection': + return 'collection'; + case 'Owner': + return 'owner'; + case 'Recipient': + return 'recipient'; + case 'Asset': + return 'asset'; + default: + return null; + } + }) + .filter((input) => input) as ExtraAccountInput[]; + } + + return []; +} diff --git a/clients/js/src/plugins/index.ts b/clients/js/src/plugins/index.ts new file mode 100644 index 00000000..61fab882 --- /dev/null +++ b/clients/js/src/plugins/index.ts @@ -0,0 +1,15 @@ +export * from './royalties'; +export * from './lib'; +export * from './dataStore'; +export * from './lifecycleChecks'; +export * from './lifecycleHook'; +export * from './oracle'; +export * from './externalPluginAdapterKey'; +export * from './externalPluginAdapterManifest'; +export * from './pluginAuthority'; +export * from './types'; +export * from './externalPluginAdapters'; +export * from './updateAuthority'; +export * from './seed'; +export * from './extraAccount'; +export * from './validationResultsOffset'; diff --git a/clients/js/src/plugins/lib.ts b/clients/js/src/plugins/lib.ts new file mode 100644 index 00000000..f96b1b5b --- /dev/null +++ b/clients/js/src/plugins/lib.ts @@ -0,0 +1,219 @@ +import { none, Option, some } from '@metaplex-foundation/umi'; + +import { + Key, + PluginHeaderV1, + Plugin as BasePlugin, + getPluginSerializer, + RegistryRecord, + PluginAuthorityPair, + PluginType, + ExternalPluginAdapterSchema, +} from '../generated'; + +import { toWords } from '../utils'; +import { + CreatePluginArgs, + AssetAllPluginArgsV2, + PluginAuthorityPairHelperArgs, + AssetPluginAuthorityPairArgsV2, + PluginsList, +} from './types'; +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; +import { royaltiesFromBase, royaltiesToBase } from './royalties'; +import { masterEditionFromBase, masterEditionToBase } from './masterEdition'; + +export function formPluginHeaderV1( + pluginRegistryOffset: bigint +): Omit { + return { + key: Key.PluginHeaderV1, + pluginRegistryOffset, + }; +} + +export function createPlugin(args: CreatePluginArgs): BasePlugin { + // TODO refactor when there are more required empty fields in plugins + if (args.type === 'UpdateDelegate') { + return { + __kind: args.type, + fields: [ + (args as any).data || { + additionalDelegates: [], + }, + ], + }; + } + return { + __kind: args.type, + fields: [(args as any).data || {}], + }; +} +export function pluginAuthorityPair( + args: PluginAuthorityPairHelperArgs +): PluginAuthorityPair { + const { type, authority, data } = args as any; + return { + plugin: createPlugin({ + type, + data, + }), + authority: authority ? some(authority) : none(), + }; +} + +export function createPluginV2(args: AssetAllPluginArgsV2): BasePlugin { + // TODO refactor when there are more required empty fields in plugins + const { type } = args; + if (type === 'UpdateDelegate') { + return { + __kind: type, + fields: [ + (args as any) || { + additionalDelegates: [], + }, + ], + }; + } + if (type === 'Royalties') { + return { + __kind: type, + fields: [royaltiesToBase(args)], + }; + } + if (type === 'MasterEdition') { + return { + __kind: type, + fields: [masterEditionToBase(args)], + }; + } + + return { + __kind: type, + fields: [(args as any) || {}], + }; +} + +export function pluginAuthorityPairV2({ + type, + authority, + ...args +}: AssetPluginAuthorityPairArgsV2): PluginAuthorityPair { + return { + plugin: createPluginV2({ + type, + ...args, + } as any), + authority: authority ? some(pluginAuthorityToBase(authority)) : none(), + }; +} + +export function mapPluginFields(fields: Array>) { + return fields.reduce((acc2, field) => ({ ...acc2, ...field }), {}); +} + +export function mapPlugin({ + plugin: plug, + authority, + offset, +}: { + plugin: Exclude; + authority: PluginAuthority; + offset: bigint; +}): PluginsList { + const pluginKey = toWords(plug.__kind) + .toLowerCase() + .split(' ') + .reduce((s, c) => s + (c.charAt(0).toUpperCase() + c.slice(1))); + + if (plug.__kind === 'Royalties') { + return { + [pluginKey]: { + authority, + offset, + ...royaltiesFromBase(plug.fields[0]), + }, + }; + } + + if (plug.__kind === 'MasterEdition') { + return { + [pluginKey]: { + authority, + offset, + ...masterEditionFromBase(plug.fields[0]), + }, + }; + } + + return { + [pluginKey]: { + authority, + offset, + ...('fields' in plug ? mapPluginFields(plug.fields) : {}), + }, + }; +} + +export function registryRecordsToPluginsList( + registryRecords: RegistryRecord[], + accountData: Uint8Array +) { + return registryRecords.reduce((acc: PluginsList, record) => { + const mappedAuthority = pluginAuthorityFromBase(record.authority); + const deserializedPlugin = getPluginSerializer().deserialize( + accountData, + Number(record.offset) + )[0]; + + acc = { + ...acc, + ...mapPlugin({ + plugin: deserializedPlugin, + authority: mappedAuthority, + offset: record.offset, + }), + }; + + return acc; + }, {}); +} + +export function pluginKeyToPluginType(pluginKey: keyof PluginsList) { + return (pluginKey.charAt(0).toUpperCase() + + pluginKey.slice(1)) as keyof typeof PluginType; +} + +export function parseExternalPluginAdapterData( + plugin: { + schema: ExternalPluginAdapterSchema; + }, + record: { + dataLen: Option; + dataOffset: Option; + }, + account: Uint8Array +): any { + let data; + const dataSlice = account.slice( + Number(record.dataOffset), + Number(record.dataOffset) + Number(record.dataLen) + ); + + if (plugin.schema === ExternalPluginAdapterSchema.Binary) { + data = dataSlice; + } else if (plugin.schema === ExternalPluginAdapterSchema.Json) { + data = JSON.parse(new TextDecoder().decode(dataSlice)); + } else if (plugin.schema === ExternalPluginAdapterSchema.MsgPack) { + // eslint-disable-next-line no-console + console.warn( + 'MsgPack schema currently not supported, falling back to binary' + ); + data = dataSlice; + } + return data; +} diff --git a/clients/js/src/plugins/lifecycleChecks.ts b/clients/js/src/plugins/lifecycleChecks.ts new file mode 100644 index 00000000..7539a6c7 --- /dev/null +++ b/clients/js/src/plugins/lifecycleChecks.ts @@ -0,0 +1,101 @@ +/* eslint-disable no-bitwise */ +import { ExternalCheckResult, HookableLifecycleEvent } from '../generated'; +import { capitalizeFirstLetter } from '../utils'; + +export type LifecycleEvent = 'create' | 'update' | 'transfer' | 'burn'; + +// ExternalCheckResult is a bit array +export enum CheckResult { + CAN_LISTEN, + CAN_APPROVE, + CAN_REJECT, +} + +export const adapterCheckResultToCheckResults = ( + check: ExternalCheckResult +): CheckResult[] => { + const results: CheckResult[] = []; + if (check.flags & 1) { + results.push(CheckResult.CAN_LISTEN); + } + if (check.flags & 2) { + results.push(CheckResult.CAN_APPROVE); + } + if (check.flags & 4) { + results.push(CheckResult.CAN_REJECT); + } + return results; +}; + +export const checkResultsToAdapterCheckResult = ( + results: CheckResult[] +): ExternalCheckResult => { + let flags = 0; + results.forEach((result) => { + switch (result) { + case CheckResult.CAN_LISTEN: + flags |= 1; + break; + case CheckResult.CAN_APPROVE: + flags |= 2; + break; + case CheckResult.CAN_REJECT: + flags |= 4; + break; + default: + // Do nothing + } + }); + return { flags }; +}; + +export type LifecycleChecks = { [key in LifecycleEvent]?: CheckResult[] }; +export type LifecycleChecksContainer = { + lifecycleChecks?: LifecycleChecks; +}; + +export function lifecycleCheckKeyToEnum( + key: keyof LifecycleChecks +): HookableLifecycleEvent { + return HookableLifecycleEvent[ + capitalizeFirstLetter(key) as keyof typeof HookableLifecycleEvent + ]; +} + +export function hookableLifecycleEventToLifecycleCheckKey( + event: HookableLifecycleEvent +): keyof LifecycleChecks { + return HookableLifecycleEvent[event].toLowerCase() as keyof LifecycleChecks; +} + +export function lifecycleChecksToBase( + l: LifecycleChecks +): [HookableLifecycleEvent, ExternalCheckResult][] { + return Object.keys(l) + .map((key) => { + const k = key as keyof LifecycleChecks; + const value = l[k]; + if (value) { + return [ + lifecycleCheckKeyToEnum(k), + checkResultsToAdapterCheckResult(value), + ]; + } + return null; + }) + .filter((x) => x !== null) as [ + HookableLifecycleEvent, + ExternalCheckResult, + ][]; +} + +export function lifecycleChecksFromBase( + l: [HookableLifecycleEvent, ExternalCheckResult][] +): LifecycleChecks { + const checks: LifecycleChecks = {}; + l.forEach(([event, check]) => { + checks[hookableLifecycleEventToLifecycleCheckKey(event)] = + adapterCheckResultToCheckResults(check); + }); + return checks; +} diff --git a/clients/js/src/plugins/lifecycleHook.ts b/clients/js/src/plugins/lifecycleHook.ts new file mode 100644 index 00000000..f3de62c1 --- /dev/null +++ b/clients/js/src/plugins/lifecycleHook.ts @@ -0,0 +1,131 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { + ExtraAccount, + extraAccountFromBase, + extraAccountToBase, +} from './extraAccount'; +import { + BaseLifecycleHook, + BaseLifecycleHookInitInfoArgs, + BaseLifecycleHookUpdateInfoArgs, + ExternalPluginAdapterSchema, + ExternalRegistryRecord, +} from '../generated'; +import { LifecycleChecks, lifecycleChecksToBase } from './lifecycleChecks'; +import { + PluginAuthority, + pluginAuthorityFromBase, + pluginAuthorityToBase, +} from './pluginAuthority'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +import { parseExternalPluginAdapterData } from './lib'; + +export type LifecycleHook = Omit< + BaseLifecycleHook, + 'extraAccounts' | 'dataAuthority' +> & { + extraAccounts?: Array; + dataAuthority?: PluginAuthority; + data?: any; +}; + +export type LifecycleHookPlugin = BaseExternalPluginAdapter & + LifecycleHook & { + type: 'LifecycleHook'; + hookedProgram: PublicKey; + }; + +export type LifecycleHookInitInfoArgs = Omit< + BaseLifecycleHookInitInfoArgs, + | 'initPluginAuthority' + | 'lifecycleChecks' + | 'schema' + | 'extraAccounts' + | 'dataAuthority' +> & { + type: 'LifecycleHook'; + initPluginAuthority?: PluginAuthority; + lifecycleChecks: LifecycleChecks; + schema?: ExternalPluginAdapterSchema; + extraAccounts?: Array; + dataAuthority?: PluginAuthority; +}; + +export type LifecycleHookUpdateInfoArgs = Omit< + BaseLifecycleHookUpdateInfoArgs, + 'lifecycleChecks' | 'extraAccounts' | 'schema' +> & { + key: ExternalPluginAdapterKey; + lifecycleChecks?: LifecycleChecks; + extraAccounts?: Array; + schema?: ExternalPluginAdapterSchema; +}; + +export function lifecycleHookInitInfoArgsToBase( + l: LifecycleHookInitInfoArgs +): BaseLifecycleHookInitInfoArgs { + return { + extraAccounts: l.extraAccounts + ? l.extraAccounts.map(extraAccountToBase) + : null, + hookedProgram: l.hookedProgram, + initPluginAuthority: l.initPluginAuthority + ? pluginAuthorityToBase(l.initPluginAuthority) + : null, + lifecycleChecks: lifecycleChecksToBase(l.lifecycleChecks), + schema: l.schema ? l.schema : null, + dataAuthority: l.dataAuthority + ? pluginAuthorityToBase(l.dataAuthority) + : null, + }; +} + +export function lifecycleHookUpdateInfoArgsToBase( + l: LifecycleHookUpdateInfoArgs +): BaseLifecycleHookUpdateInfoArgs { + return { + lifecycleChecks: l.lifecycleChecks + ? lifecycleChecksToBase(l.lifecycleChecks) + : null, + extraAccounts: l.extraAccounts + ? l.extraAccounts.map(extraAccountToBase) + : null, + schema: l.schema ? l.schema : null, + // TODO update dataAuthority? + }; +} + +export function lifecycleHookFromBase( + s: BaseLifecycleHook, + r: ExternalRegistryRecord, + account: Uint8Array +): LifecycleHook { + return { + ...s, + extraAccounts: + s.extraAccounts.__option === 'Some' + ? s.extraAccounts.value.map(extraAccountFromBase) + : undefined, + data: parseExternalPluginAdapterData(s, r, account), + dataAuthority: + s.dataAuthority.__option === 'Some' + ? pluginAuthorityFromBase(s.dataAuthority.value) + : undefined, + }; +} + +export const lifecycleHookManifest: ExternalPluginAdapterManifest< + LifecycleHook, + BaseLifecycleHook, + LifecycleHookInitInfoArgs, + BaseLifecycleHookInitInfoArgs, + LifecycleHookUpdateInfoArgs, + BaseLifecycleHookUpdateInfoArgs +> = { + type: 'LifecycleHook', + fromBase: lifecycleHookFromBase, + initToBase: lifecycleHookInitInfoArgsToBase, + updateToBase: lifecycleHookUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/masterEdition.ts b/clients/js/src/plugins/masterEdition.ts new file mode 100644 index 00000000..96f3815c --- /dev/null +++ b/clients/js/src/plugins/masterEdition.ts @@ -0,0 +1,26 @@ +import { BaseMasterEdition } from '../generated'; +import { someOrNone, unwrapOption } from '../utils'; + +export type MasterEdition = { + maxSupply?: number; + name?: string; + uri?: string; +}; + +export type MasterEditionArgs = MasterEdition; + +export function masterEditionToBase(s: MasterEdition): BaseMasterEdition { + return { + maxSupply: someOrNone(s.maxSupply), + name: someOrNone(s.name), + uri: someOrNone(s.uri), + }; +} + +export function masterEditionFromBase(s: BaseMasterEdition): MasterEdition { + return { + maxSupply: unwrapOption(s.maxSupply), + name: unwrapOption(s.name), + uri: unwrapOption(s.uri), + }; +} diff --git a/clients/js/src/plugins/oracle.ts b/clients/js/src/plugins/oracle.ts new file mode 100644 index 00000000..712d5635 --- /dev/null +++ b/clients/js/src/plugins/oracle.ts @@ -0,0 +1,156 @@ +import { Context, PublicKey } from '@metaplex-foundation/umi'; +import { + ExtraAccount, + extraAccountFromBase, + extraAccountToAccountMeta, + extraAccountToBase, +} from './extraAccount'; +import { + BaseOracle, + BaseOracleInitInfoArgs, + BaseOracleUpdateInfoArgs, + ExternalRegistryRecord, + getOracleValidationSerializer, + OracleValidation, +} from '../generated'; +import { LifecycleChecks, lifecycleChecksToBase } from './lifecycleChecks'; +import { PluginAuthority, pluginAuthorityToBase } from './pluginAuthority'; +import { ExternalPluginAdapterManifest } from './externalPluginAdapterManifest'; +import { BaseExternalPluginAdapter } from './externalPluginAdapters'; +import { ExternalPluginAdapterKey } from './externalPluginAdapterKey'; +import { + ValidationResultsOffset, + validationResultsOffsetFromBase, + validationResultsOffsetToBase, +} from './validationResultsOffset'; + +export type Oracle = Omit & { + baseAddressConfig?: ExtraAccount; + resultsOffset: ValidationResultsOffset; +}; + +export type OraclePlugin = BaseExternalPluginAdapter & + Oracle & { + type: 'Oracle'; + }; + +export type OracleInitInfoArgs = Omit< + BaseOracleInitInfoArgs, + | 'initPluginAuthority' + | 'lifecycleChecks' + | 'baseAddressConfig' + | 'resultsOffset' +> & { + type: 'Oracle'; + initPluginAuthority?: PluginAuthority; + lifecycleChecks: LifecycleChecks; + baseAddressConfig?: ExtraAccount; + resultsOffset?: ValidationResultsOffset; +}; + +export type OracleUpdateInfoArgs = Omit< + BaseOracleUpdateInfoArgs, + 'lifecycleChecks' | 'baseAddressConfig' | 'resultsOffset' +> & { + key: ExternalPluginAdapterKey; + lifecycleChecks?: LifecycleChecks; + baseAddressConfig?: ExtraAccount; + resultsOffset?: ValidationResultsOffset; +}; + +export function oracleInitInfoArgsToBase( + o: OracleInitInfoArgs +): BaseOracleInitInfoArgs { + return { + baseAddress: o.baseAddress, + baseAddressConfig: o.baseAddressConfig + ? extraAccountToBase(o.baseAddressConfig) + : null, + lifecycleChecks: lifecycleChecksToBase(o.lifecycleChecks), + initPluginAuthority: o.initPluginAuthority + ? pluginAuthorityToBase(o.initPluginAuthority) + : null, + resultsOffset: o.resultsOffset + ? validationResultsOffsetToBase(o.resultsOffset) + : null, + }; +} + +export function oracleUpdateInfoArgsToBase( + o: OracleUpdateInfoArgs +): BaseOracleUpdateInfoArgs { + return { + baseAddressConfig: o.baseAddressConfig + ? extraAccountToBase(o.baseAddressConfig) + : null, + lifecycleChecks: o.lifecycleChecks + ? lifecycleChecksToBase(o.lifecycleChecks) + : null, + resultsOffset: o.resultsOffset + ? validationResultsOffsetToBase(o.resultsOffset) + : null, + }; +} + +export function oracleFromBase( + s: BaseOracle, + r: ExternalRegistryRecord, + account: Uint8Array +): Oracle { + return { + ...s, + baseAddressConfig: + s.baseAddressConfig.__option === 'Some' + ? extraAccountFromBase(s.baseAddressConfig.value) + : undefined, + resultsOffset: validationResultsOffsetFromBase(s.resultsOffset), + }; +} + +export function findOracleAccount( + context: Pick, + oracle: Pick, + inputs: { + asset?: PublicKey; + collection?: PublicKey; + recipient?: PublicKey; + owner?: PublicKey; + } +): PublicKey { + if (!oracle.baseAddressConfig) { + return oracle.baseAddress; + } + + return extraAccountToAccountMeta(context, oracle.baseAddressConfig, { + ...inputs, + program: oracle.baseAddress, + }).pubkey; +} + +export function deserializeOracleValidation( + data: Uint8Array, + offset: ValidationResultsOffset +): OracleValidation { + let offs = 0; + if (offset.type === 'Custom') { + offs = Number(offset.offset); + } else if (offset.type === 'Anchor') { + offs = 8; + } + + return getOracleValidationSerializer().deserialize(data, offs)[0]; +} + +export const oracleManifest: ExternalPluginAdapterManifest< + Oracle, + BaseOracle, + OracleInitInfoArgs, + BaseOracleInitInfoArgs, + OracleUpdateInfoArgs, + BaseOracleUpdateInfoArgs +> = { + type: 'Oracle', + fromBase: oracleFromBase, + initToBase: oracleInitInfoArgsToBase, + updateToBase: oracleUpdateInfoArgsToBase, +}; diff --git a/clients/js/src/plugins/pluginAuthority.ts b/clients/js/src/plugins/pluginAuthority.ts new file mode 100644 index 00000000..b0a4f0b3 --- /dev/null +++ b/clients/js/src/plugins/pluginAuthority.ts @@ -0,0 +1,30 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { BasePluginAuthority } from '../generated'; + +export type PluginAuthority = { + type: PluginAuthorityType; + address?: PublicKey; +}; + +export type PluginAuthorityType = BasePluginAuthority['__kind']; + +export function pluginAuthorityToBase(u: PluginAuthority): BasePluginAuthority { + if (u.type === 'Address') { + return { + __kind: 'Address', + address: u.address as PublicKey, + }; + } + return { + __kind: u.type, + }; +} + +export function pluginAuthorityFromBase( + authority: BasePluginAuthority +): PluginAuthority { + return { + type: authority.__kind, + address: (authority as any).address, + }; +} diff --git a/clients/js/src/plugins/royalties.ts b/clients/js/src/plugins/royalties.ts new file mode 100644 index 00000000..adb19770 --- /dev/null +++ b/clients/js/src/plugins/royalties.ts @@ -0,0 +1,79 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { BaseRoyalties, BaseRuleSet } from '../generated'; +import { BasePlugin } from './types'; + +// do jank stuff for backwards compatibility +export type UnwrappedRuleSet = + | { + type: 'None'; + } + | { + type: 'ProgramAllowList'; + addresses: PublicKey[]; + } + | { + type: 'ProgramDenyList'; + addresses: PublicKey[]; + }; + +export type RuleSet = UnwrappedRuleSet | BaseRuleSet; + +export type Royalties = Omit & { + ruleSet: RuleSet; +}; + +export type RoyaltiesArgs = Royalties; + +export type RoyaltiesPlugin = BasePlugin & Royalties; + +export function ruleSetToBase(r: RuleSet): BaseRuleSet { + const base = r as BaseRuleSet; + if (base.__kind) { + return r as BaseRuleSet; + } + const ruleSet = r as UnwrappedRuleSet; + + if ( + ruleSet.type === 'ProgramAllowList' || + ruleSet.type === 'ProgramDenyList' + ) { + return { + __kind: ruleSet.type, + fields: [ruleSet.addresses], + }; + } + return { __kind: ruleSet.type }; +} + +export function royaltiesToBase(r: Royalties): BaseRoyalties { + return { + ...r, + ruleSet: ruleSetToBase(r.ruleSet), + }; +} + +export function royaltiesFromBase(r: BaseRoyalties): Royalties { + let ruleSet: RuleSet; + if (r.ruleSet.__kind === 'ProgramAllowList') { + ruleSet = { + ...r.ruleSet, + type: 'ProgramAllowList', + addresses: r.ruleSet.fields[0], + }; + } else if (r.ruleSet.__kind === 'ProgramDenyList') { + ruleSet = { + ...r.ruleSet, + type: 'ProgramDenyList', + addresses: r.ruleSet.fields[0], + }; + } else { + ruleSet = { + ...r.ruleSet, + type: r.ruleSet.__kind, + }; + } + return { + ...r, + ruleSet, + }; +} diff --git a/clients/js/src/plugins/seed.ts b/clients/js/src/plugins/seed.ts new file mode 100644 index 00000000..5d501bfe --- /dev/null +++ b/clients/js/src/plugins/seed.ts @@ -0,0 +1,51 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { BaseSeed } from '../generated'; +import { RenameToType } from '../utils'; + +export type Seed = + | Exclude, { type: 'Address' } | { type: 'Bytes' }> + | { + type: 'Address'; + pubkey: PublicKey; + } + | { + type: 'Bytes'; + bytes: Uint8Array; + }; + +export function seedToBase(s: Seed): BaseSeed { + if (s.type === 'Address') { + return { + __kind: 'Address', + fields: [s.pubkey], + }; + } + + if (s.type === 'Bytes') { + return { + __kind: 'Bytes', + fields: [s.bytes], + }; + } + return { + __kind: s.type, + }; +} + +export function seedFromBase(s: BaseSeed): Seed { + if (s.__kind === 'Address') { + return { + type: 'Address', + pubkey: s.fields[0], + }; + } + if (s.__kind === 'Bytes') { + return { + type: 'Bytes', + bytes: s.fields[0], + }; + } + return { + type: s.__kind, + }; +} diff --git a/clients/js/src/plugins/types.ts b/clients/js/src/plugins/types.ts new file mode 100644 index 00000000..74ccdb40 --- /dev/null +++ b/clients/js/src/plugins/types.ts @@ -0,0 +1,195 @@ +import { + BurnDelegate, + FreezeDelegate, + PermanentFreezeDelegate, + TransferDelegate, + UpdateDelegate, + Attributes, + PermanentTransferDelegate, + PermanentBurnDelegate, + Edition, + basePluginAuthority as pluginAuthority, + baseUpdateAuthority as updateAuthority, + baseRuleSet as ruleSet, + FreezeDelegateArgs, + UpdateDelegateArgs, + AttributesArgs, + PermanentFreezeDelegateArgs, + EditionArgs, + BasePluginAuthority, + BaseRoyaltiesArgs, + BaseMasterEditionArgs, + AddBlocker, + ImmutableMetadata, +} from '../generated'; +import { RoyaltiesArgs, RoyaltiesPlugin } from './royalties'; +import { PluginAuthority } from './pluginAuthority'; +import { MasterEdition, MasterEditionArgs } from './masterEdition'; + +// for backwards compatibility +export { pluginAuthority, updateAuthority, ruleSet }; + +export type BasePlugin = { + authority: PluginAuthority; + offset?: bigint; +}; + +export type PluginAuthorityPairHelperArgs = CreatePluginArgs & { + authority?: BasePluginAuthority; +}; + +export type CreatePluginArgs = + | { + type: 'Royalties'; + data: BaseRoyaltiesArgs; + } + | { + type: 'FreezeDelegate'; + data: FreezeDelegateArgs; + } + | { + type: 'BurnDelegate'; + } + | { + type: 'TransferDelegate'; + } + | { + type: 'UpdateDelegate'; + data?: UpdateDelegateArgs; + } + | { + type: 'Attributes'; + data: AttributesArgs; + } + | { + type: 'PermanentFreezeDelegate'; + data: PermanentFreezeDelegateArgs; + } + | { + type: 'PermanentTransferDelegate'; + } + | { + type: 'PermanentBurnDelegate'; + } + | { + type: 'Edition'; + data: EditionArgs; + } + | { + type: 'MasterEdition'; + data: BaseMasterEditionArgs; + } + | { + type: 'ImmutableMetadata'; + } + | { + type: 'AddBlocker'; + }; + +export type AuthorityArgsV2 = { + authority?: PluginAuthority; +}; + +export type CreateOnlyPluginArgsV2 = + | ({ + type: 'PermanentFreezeDelegate'; + } & PermanentFreezeDelegateArgs) + | { + type: 'PermanentTransferDelegate'; + } + | { + type: 'PermanentBurnDelegate'; + } + | ({ + type: 'Edition'; + } & EditionArgs); + +export type OwnerManagedPluginArgsV2 = + | ({ + type: 'FreezeDelegate'; + } & FreezeDelegateArgs) + | { + type: 'BurnDelegate'; + } + | { + type: 'TransferDelegate'; + }; + +export type AuthorityManagedPluginArgsV2 = + | ({ + type: 'Royalties'; + } & RoyaltiesArgs) + | ({ + type: 'UpdateDelegate'; + } & UpdateDelegateArgs) + | ({ + type: 'Attributes'; + } & AttributesArgs) + | ({ + type: 'MasterEdition'; + } & MasterEditionArgs) + | { + type: 'ImmutableMetadata'; + } + | { + type: 'AddBlocker'; + }; + +export type AssetAddablePluginArgsV2 = + | OwnerManagedPluginArgsV2 + | AuthorityManagedPluginArgsV2; +export type AssetAllPluginArgsV2 = + | AssetAddablePluginArgsV2 + | CreateOnlyPluginArgsV2; +export type AssetPluginAuthorityPairArgsV2 = AssetAllPluginArgsV2 & + AuthorityArgsV2; +export type AssetAddablePluginAuthorityPairArgsV2 = AssetAddablePluginArgsV2 & + AuthorityArgsV2; + +export type CollectionAddablePluginArgsV2 = AuthorityManagedPluginArgsV2; +export type CollectionAllPluginArgsV2 = + | CreateOnlyPluginArgsV2 + | CollectionAddablePluginArgsV2; +export type CollectionPluginAuthorityPairArgsV2 = CollectionAllPluginArgsV2 & + AuthorityArgsV2; +export type CollectionAddablePluginAuthorityPairArgsV2 = + CollectionAddablePluginArgsV2 & AuthorityArgsV2; + +export type FreezeDelegatePlugin = BasePlugin & FreezeDelegate; +export type BurnDelegatePlugin = BasePlugin & BurnDelegate; +export type TransferDelegatePlugin = BasePlugin & TransferDelegate; +export type UpdateDelegatePlugin = BasePlugin & UpdateDelegate; +export type PermanentFreezeDelegatePlugin = BasePlugin & + PermanentFreezeDelegate; +export type AttributesPlugin = BasePlugin & Attributes; +export type PermanentTransferDelegatePlugin = BasePlugin & + PermanentTransferDelegate; +export type PermanentBurnDelegatePlugin = BasePlugin & PermanentBurnDelegate; +export type EditionPlugin = BasePlugin & Edition; +export type MasterEditionPlugin = BasePlugin & MasterEdition; +export type AddBlockerPlugin = BasePlugin & AddBlocker; +export type ImmutableMetadataPlugin = BasePlugin & ImmutableMetadata; + +export type CommonPluginsList = { + attributes?: AttributesPlugin; + royalties?: RoyaltiesPlugin; + updateDelegate?: UpdateDelegatePlugin; + permanentFreezeDelegate?: PermanentFreezeDelegatePlugin; + permanentTransferDelegate?: PermanentTransferDelegatePlugin; + permanentBurnDelegate?: PermanentBurnDelegatePlugin; + addBlocker?: AddBlockerPlugin; + immutableMetadata?: ImmutableMetadataPlugin; +}; + +export type AssetPluginsList = { + freezeDelegate?: FreezeDelegatePlugin; + burnDelegate?: BurnDelegatePlugin; + transferDelegate?: TransferDelegatePlugin; + edition?: EditionPlugin; +} & CommonPluginsList; + +export type CollectionPluginsList = { + masterEdition?: MasterEditionPlugin; +} & CommonPluginsList; + +export type PluginsList = AssetPluginsList & CollectionPluginsList; diff --git a/clients/js/src/plugins/updateAuthority.ts b/clients/js/src/plugins/updateAuthority.ts new file mode 100644 index 00000000..0de3d5c0 --- /dev/null +++ b/clients/js/src/plugins/updateAuthority.ts @@ -0,0 +1,21 @@ +import { PublicKey } from '@metaplex-foundation/umi'; +import { BaseUpdateAuthority } from '../generated'; + +export type UpdateAuthorityType = BaseUpdateAuthority['__kind']; + +export type UpdateAuthority = { + type: UpdateAuthorityType; + address?: PublicKey; +}; + +export function updateAuthorityToBase(u: UpdateAuthority): BaseUpdateAuthority { + if (u.type === 'None') { + return { + __kind: 'None', + }; + } + return { + __kind: u.type, + fields: [u.address as PublicKey], + }; +} diff --git a/clients/js/src/plugins/validationResultsOffset.ts b/clients/js/src/plugins/validationResultsOffset.ts new file mode 100644 index 00000000..8e35eb2e --- /dev/null +++ b/clients/js/src/plugins/validationResultsOffset.ts @@ -0,0 +1,36 @@ +import { BaseValidationResultsOffset } from '../generated'; + +export type ValidationResultsOffset = + | { type: 'NoOffset' } + | { type: 'Anchor' } + | { type: 'Custom'; offset: bigint }; + +export function validationResultsOffsetToBase( + e: ValidationResultsOffset +): BaseValidationResultsOffset { + if (e.type === 'Custom') { + return { + __kind: 'Custom', + fields: [e.offset], + }; + } + + return { + __kind: e.type, + }; +} + +export function validationResultsOffsetFromBase( + e: BaseValidationResultsOffset +): ValidationResultsOffset { + if (e.__kind === 'Custom') { + return { + type: 'Custom', + offset: e.fields[0], + }; + } + + return { + type: e.__kind, + }; +} diff --git a/clients/js/src/types.ts b/clients/js/src/types.ts deleted file mode 100644 index ca56bf03..00000000 --- a/clients/js/src/types.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { PublicKey } from '@metaplex-foundation/umi'; -import { - PluginAuthority, - BurnDelegate, - FreezeDelegate, - PermanentFreezeDelegate, - Royalties, - TransferDelegate, - UpdateDelegate, - Attributes, - PermanentTransferDelegate, - UpdateAuthority, - PermanentBurnDelegate, - Edition, - MasterEdition, - ImmutableMetadata, - AddBlocker, -} from './generated'; - -export type BasePluginAuthority = { - type: PluginAuthorityType; - address?: PublicKey; -}; - -export type BaseUpdateAuthority = { - type: UpdateAuthorityType; - address?: PublicKey; -}; - -export type UpdateAuthorityType = UpdateAuthority['__kind']; -export type PluginAuthorityType = PluginAuthority['__kind']; - -export type BasePlugin = { - authority: BasePluginAuthority; - offset?: bigint; -}; - -export type RoyaltiesPlugin = BasePlugin & Royalties; -export type FreezeDelegatePlugin = BasePlugin & FreezeDelegate; -export type BurnDelegatePlugin = BasePlugin & BurnDelegate; -export type TransferDelegatePlugin = BasePlugin & TransferDelegate; -export type UpdateDelegatePlugin = BasePlugin & UpdateDelegate; -export type PermanentFreezeDelegatePlugin = BasePlugin & - PermanentFreezeDelegate; -export type AttributesPlugin = BasePlugin & Attributes; -export type PermanentTransferDelegatePlugin = BasePlugin & - PermanentTransferDelegate; -export type PermanentBurnDelegatePlugin = BasePlugin & PermanentBurnDelegate; -export type EditionPlugin = BasePlugin & Edition; -export type MasterEditionPlugin = BasePlugin & MasterEdition; -export type AddBlockerPlugin = BasePlugin & AddBlocker; -export type ImmutableMetadataPlugin = BasePlugin & ImmutableMetadata; - -export type PluginsList = { - royalties?: RoyaltiesPlugin; - freezeDelegate?: FreezeDelegatePlugin; - burnDelegate?: BurnDelegatePlugin; - transferDelegate?: TransferDelegatePlugin; - updateDelegate?: UpdateDelegatePlugin; - attributes?: AttributesPlugin; - permanentFreezeDelegate?: PermanentFreezeDelegatePlugin; - permanentTransferDelegate?: PermanentTransferDelegatePlugin; - permanentBurnDelegate?: PermanentBurnDelegatePlugin; - edition?: EditionPlugin; - masterEdition?: MasterEditionPlugin; - addBlocker?: AddBlockerPlugin; - immutableMetadata?: ImmutableMetadataPlugin; -}; diff --git a/clients/js/src/utils.ts b/clients/js/src/utils.ts index 20b0414c..a9f36a12 100644 --- a/clients/js/src/utils.ts +++ b/clients/js/src/utils.ts @@ -1,3 +1,15 @@ +import { none, Option, some } from '@metaplex-foundation/umi'; + +export type RenameField = Omit< + T, + K +> & + (undefined extends T[K] ? { [P in R]?: T[K] } : { [P in R]: T[K] }); + +export type RenameToType = T extends T + ? RenameField + : never; + export function toWords(str: string) { const camelCaseRegex = /([a-z0-9])([A-Z])/g; return str.replace(camelCaseRegex, '$1 $2'); @@ -10,3 +22,11 @@ export function capitalizeFirstLetter(str: string) { export function lowercaseFirstLetter(str: string) { return str.charAt(0).toLowerCase() + str.slice(1); } + +export function someOrNone(value: T | undefined): Option { + return value !== undefined ? some(value) : none(); +} + +export function unwrapOption(value: Option): T | undefined { + return value.__option === 'Some' ? value.value : undefined; +} diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setupRaw.ts similarity index 90% rename from clients/js/test/_setup.ts rename to clients/js/test/_setupRaw.ts index d39db174..685ba375 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setupRaw.ts @@ -5,6 +5,7 @@ import { PublicKey, Signer, Umi, + assertAccountExists, generateSigner, publicKey, } from '@metaplex-foundation/umi'; @@ -18,9 +19,11 @@ import { createCollectionV1 as baseCreateCollection, CollectionV1, AssetV1, - PluginsList, PluginAuthorityPairArgs, - BaseUpdateAuthority, + UpdateAuthority, + ExternalPluginAdaptersList, + AssetPluginsList, + CollectionPluginsList, } from '../src'; export const createUmi = async () => (await basecreateUmi()).use(mplCore()); @@ -59,7 +62,7 @@ export const createAsset = async ( const updateAuthority = input.updateAuthority ? publicKey(input.updateAuthority) : undefined; - // const tx = + await createV1(umi, { owner, payer, @@ -140,10 +143,11 @@ export const assertAsset = async ( input: { asset: PublicKey | Signer; owner: PublicKey | Signer; - updateAuthority?: BaseUpdateAuthority; + updateAuthority?: UpdateAuthority; name?: string | RegExp; uri?: string | RegExp; - } & PluginsList + } & AssetPluginsList & + ExternalPluginAdaptersList ) => { const { asset, owner, name, uri, ...rest } = input; const assetAddress = publicKey(input.asset); @@ -177,7 +181,8 @@ export const assertCollection = async ( uri?: string | RegExp; numMinted?: number; currentSize?: number; - } & PluginsList + } & CollectionPluginsList & + ExternalPluginAdaptersList ) => { const { collection, name, uri, updateAuthority, ...rest } = input; @@ -204,3 +209,16 @@ export const assertCollection = async ( t.like(collectionWithPlugins, testObj); }; + +export const assertBurned = async ( + t: Assertions, + umi: Umi, + asset: PublicKey +) => { + const account = await umi.rpc.getAccount(asset); + t.true(account.exists); + assertAccountExists(account); + t.is(account.data.length, 1); + t.is(account.data[0], Key.Uninitialized); + return account; +}; diff --git a/clients/js/test/_setupSdk.ts b/clients/js/test/_setupSdk.ts new file mode 100644 index 00000000..9478f0b5 --- /dev/null +++ b/clients/js/test/_setupSdk.ts @@ -0,0 +1,126 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { + PublicKey, + Signer, + Umi, + generateSigner, + publicKey, +} from '@metaplex-foundation/umi'; +import { + DataState, + fetchAssetV1, + fetchCollectionV1, + CollectionV1, + AssetV1, + AssetPluginAuthorityPairArgsV2, + ExternalPluginAdapterInitInfoArgs, + create, + createCollection as baseCreateCollection, + CollectionPluginAuthorityPairArgsV2, +} from '../src'; +import { DEFAULT_ASSET, DEFAULT_COLLECTION } from './_setupRaw'; + +export type CreateAssetHelperArgs = { + owner?: PublicKey | Signer; + payer?: Signer; + asset?: Signer; + dataState?: DataState; + name?: string; + uri?: string; + authority?: Signer; + updateAuthority?: PublicKey | Signer; + collection?: PublicKey | CollectionV1; + plugins?: ( + | ExternalPluginAdapterInitInfoArgs + | AssetPluginAuthorityPairArgsV2 + )[]; +}; +export const createAsset = async ( + umi: Umi, + input: CreateAssetHelperArgs = {} +) => { + const payer = input.payer || umi.identity; + const owner = publicKey(input.owner || input.payer || umi.identity); + const asset = input.asset || generateSigner(umi); + const updateAuthority = input.updateAuthority + ? publicKey(input.updateAuthority) + : undefined; + + const col = + typeof input.collection === 'string' + ? await fetchCollectionV1(umi, input.collection as PublicKey) + : (input.collection as CollectionV1 | undefined); + + await create(umi, { + owner, + payer, + dataState: input.dataState, + asset, + updateAuthority, + name: input.name || DEFAULT_ASSET.name, + uri: input.uri || DEFAULT_ASSET.uri, + plugins: input.plugins, + collection: col, + authority: input.authority, + }).sendAndConfirm(umi); + + return fetchAssetV1(umi, publicKey(asset)); +}; + +export type CreateCollectionHelperArgs = { + payer?: Signer; + collection?: Signer; + name?: string; + uri?: string; + updateAuthority?: PublicKey | Signer; + plugins?: ( + | ExternalPluginAdapterInitInfoArgs + | CollectionPluginAuthorityPairArgsV2 + )[]; +}; + +export const createCollection = async ( + umi: Umi, + input: CreateCollectionHelperArgs = {} +) => { + const payer = input.payer || umi.identity; + const collection = input.collection || generateSigner(umi); + const updateAuthority = publicKey(input.updateAuthority || payer); + await baseCreateCollection(umi, { + name: input.name || DEFAULT_COLLECTION.name, + uri: input.uri || DEFAULT_COLLECTION.uri, + collection, + payer, + updateAuthority, + plugins: input.plugins, + }).sendAndConfirm(umi); + + return fetchCollectionV1(umi, publicKey(collection)); +}; + +export const createAssetWithCollection: ( + umi: Umi, + assetInput: CreateAssetHelperArgs & { collection?: PublicKey | Signer }, + collectionInput?: CreateCollectionHelperArgs +) => Promise<{ + asset: AssetV1; + collection: CollectionV1; +}> = async (umi, assetInput, collectionInput = {}) => { + const collection = assetInput.collection + ? await fetchCollectionV1(umi, publicKey(assetInput.collection)) + : await createCollection(umi, { + payer: assetInput.payer, + updateAuthority: assetInput.updateAuthority, + ...collectionInput, + }); + + const asset = await createAsset(umi, { + ...assetInput, + collection, + }); + + return { + asset, + collection, + }; +}; diff --git a/clients/js/test/addPlugin.test.ts b/clients/js/test/addPlugin.test.ts index 8b1e150c..b9f125bf 100644 --- a/clients/js/test/addPlugin.test.ts +++ b/clients/js/test/addPlugin.test.ts @@ -18,7 +18,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can add a plugin to an asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/approveAuthority.test.ts b/clients/js/test/approveAuthority.test.ts index 76557266..3bf4ae13 100644 --- a/clients/js/test/approveAuthority.test.ts +++ b/clients/js/test/approveAuthority.test.ts @@ -18,7 +18,7 @@ import { createAsset, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can add an authority to a plugin', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/asset.test.ts b/clients/js/test/asset.test.ts index 7f9f990c..da64b9bf 100644 --- a/clients/js/test/asset.test.ts +++ b/clients/js/test/asset.test.ts @@ -1,7 +1,7 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; import { Key, getAssetV1GpaBuilder, updateAuthority } from '../src'; -import { createAsset, createCollection, createUmi } from './_setup'; +import { createAsset, createCollection, createUmi } from './_setupRaw'; test('it can gpa fetch assets by owner', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/burn.test.ts b/clients/js/test/burn.test.ts index f32cd6f0..d1c314de 100644 --- a/clients/js/test/burn.test.ts +++ b/clients/js/test/burn.test.ts @@ -1,24 +1,21 @@ -import { - assertAccountExists, - generateSigner, - sol, -} from '@metaplex-foundation/umi'; +import { generateSigner, sol } from '@metaplex-foundation/umi'; import test from 'ava'; -import { burnCollectionV1, burnV1, Key, pluginAuthorityPair } from '../src'; +import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; +import { burnCollectionV1, burnV1, pluginAuthorityPair } from '../src'; import { DEFAULT_ASSET, DEFAULT_COLLECTION, assertAsset, + assertBurned, assertCollection, createAsset, createAssetWithCollection, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can burn an asset as the owner', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const asset = await createAsset(umi); await assertAsset(t, umi, { @@ -33,16 +30,11 @@ test('it can burn an asset as the owner', async (t) => { }).sendAndConfirm(umi); // And the asset address still exists but was resized to 1. - const afterAsset = await umi.rpc.getAccount(asset.publicKey); - t.true(afterAsset.exists); - assertAccountExists(afterAsset); + const afterAsset = await assertBurned(t, umi, asset.publicKey); t.deepEqual(afterAsset.lamports, sol(0.00089784 + 0.0015)); - t.is(afterAsset.data.length, 1); - t.is(afterAsset.data[0], Key.Uninitialized); }); test('it cannot burn an asset if not the owner', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const attacker = generateSigner(umi); @@ -69,7 +61,6 @@ test('it cannot burn an asset if not the owner', async (t) => { }); test('it cannot burn an asset if it is frozen', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const asset = await createAsset(umi, { @@ -114,7 +105,6 @@ test('it cannot burn an asset if it is frozen', async (t) => { }); test('it cannot burn asset in collection if no collection specified', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const { asset, collection } = await createAssetWithCollection(umi, {}); @@ -133,7 +123,6 @@ test('it cannot burn asset in collection if no collection specified', async (t) }); test('it cannot burn an asset if collection permanently frozen', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const { asset, collection } = await createAssetWithCollection( @@ -182,7 +171,6 @@ test('it cannot burn an asset if collection permanently frozen', async (t) => { }); test('it cannot use an invalid system program for assets', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const asset = await createAsset(umi); const fakeSystemProgram = generateSigner(umi); @@ -202,7 +190,6 @@ test('it cannot use an invalid system program for assets', async (t) => { }); test('it cannot use an invalid noop program for assets', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const asset = await createAsset(umi); const fakeLogWrapper = generateSigner(umi); @@ -222,7 +209,6 @@ test('it cannot use an invalid noop program for assets', async (t) => { }); test('it cannot use an invalid noop program for collections', async (t) => { - // Given a Umi instance and a new signer. const umi = await createUmi(); const collection = await createCollection(umi); const fakeLogWrapper = generateSigner(umi); @@ -241,6 +227,29 @@ test('it cannot use an invalid noop program for collections', async (t) => { await t.throwsAsync(result, { name: 'InvalidLogWrapperProgram' }); }); +test('it can burn using owner authority', async (t) => { + const umi = await createUmi(); + const owner = await generateSignerWithSol(umi); + const asset = await createAsset(umi, { + owner: owner.publicKey, + }); + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + }); + + await burnV1(umi, { + asset: asset.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + // And the asset address still exists but was resized to 1. + const afterAsset = await assertBurned(t, umi, asset.publicKey); + t.deepEqual(afterAsset.lamports, sol(0.00089784 + 0.0015)); +}); + test('it cannot burn an asset with the wrong collection specified', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); diff --git a/clients/js/test/collect.test.ts b/clients/js/test/collect.test.ts index e3496cc0..d57611a8 100644 --- a/clients/js/test/collect.test.ts +++ b/clients/js/test/collect.test.ts @@ -16,7 +16,7 @@ import { pluginAuthorityPair, removePluginV1, } from '../src'; -import { createAsset, createUmi } from './_setup'; +import { createAsset, createUmi } from './_setupRaw'; const recipient1 = publicKey('8AT6o8Qk5T9QnZvPThMrF9bcCQLTGkyGvVZZzHgCw11v'); const recipient2 = publicKey('MmHsqX4LxTfifxoH8BVRLUKrwDn1LPCac6YcCZTHhwt'); diff --git a/clients/js/test/collection.test.ts b/clients/js/test/collection.test.ts index 259baa9b..770e9133 100644 --- a/clients/js/test/collection.test.ts +++ b/clients/js/test/collection.test.ts @@ -1,7 +1,7 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; import { Key, getCollectionV1GpaBuilder } from '../src'; -import { createUmi, createCollection } from './_setup'; +import { createUmi, createCollection } from './_setupRaw'; test('it can gpa fetch collections by updateAuthority', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/collectionSize.test.ts b/clients/js/test/collectionSize.test.ts index 3d7c248e..c085009e 100644 --- a/clients/js/test/collectionSize.test.ts +++ b/clients/js/test/collectionSize.test.ts @@ -1,6 +1,6 @@ import test from 'ava'; import { burnV1, fetchCollectionV1 } from '../src'; -import { createUmi, createCollection, createAsset } from './_setup'; +import { createUmi, createCollection, createAsset } from './_setupRaw'; test('it can burn an asset which is the part of a collection', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/compress.test.ts b/clients/js/test/compress.test.ts index dcd58985..7715b8b4 100644 --- a/clients/js/test/compress.test.ts +++ b/clients/js/test/compress.test.ts @@ -13,7 +13,7 @@ import { HashedAssetSchema, } from '../src'; -import { createAsset, createUmi } from './_setup'; +import { createAsset, createUmi } from './_setupRaw'; test.skip('it can compress an asset without any plugins as the owner', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index 8dcf0832..becf2118 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -16,7 +16,7 @@ import { createCollection, createUmi, DEFAULT_ASSET, -} from './_setup'; +} from './_setupRaw'; test('it can create a new asset in account state', async (t) => { // Given an Umi instance and a new signer. @@ -139,7 +139,7 @@ test('it can create a new asset in account state with plugins', async (t) => { authority: { type: 'Owner', }, - offset: BigInt(119), + offset: 119n, frozen: false, }, }); diff --git a/clients/js/test/createCollection.test.ts b/clients/js/test/createCollection.test.ts index c1050d6f..15578446 100644 --- a/clients/js/test/createCollection.test.ts +++ b/clients/js/test/createCollection.test.ts @@ -16,7 +16,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can create a new collection', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/decompress.test.ts b/clients/js/test/decompress.test.ts index 63cdbcd6..055dafa1 100644 --- a/clients/js/test/decompress.test.ts +++ b/clients/js/test/decompress.test.ts @@ -14,7 +14,7 @@ import { HashedAssetSchema, updateAuthority, } from '../src'; -import { createUmi } from './_setup'; +import { createUmi } from './_setupRaw'; test.skip('it can decompress a previously compressed asset as the owner', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/externalPlugins/lifecycleHook.test.ts b/clients/js/test/externalPlugins/lifecycleHook.test.ts new file mode 100644 index 00000000..c225b643 --- /dev/null +++ b/clients/js/test/externalPlugins/lifecycleHook.test.ts @@ -0,0 +1,111 @@ +import test from 'ava'; + +import { mplCoreOracleExample } from '@metaplex-foundation/mpl-core-oracle-example'; +import { generateSigner } from '@metaplex-foundation/umi'; +import { + assertAsset, + createUmi as baseCreateUmi, + DEFAULT_ASSET, +} from '../_setupRaw'; +import { createAsset } from '../_setupSdk'; +import { addPlugin, CheckResult, updatePlugin } from '../../src'; + +const createUmi = async () => + (await baseCreateUmi()).use(mplCoreOracleExample()); + +test.skip('it cannot create asset with lifecycle hook that has no lifecycle checks', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const result = createAsset(umi, { + owner, + plugins: [ + { + type: 'LifecycleHook', + hookedProgram: account.publicKey, + lifecycleChecks: {}, + }, + ], + }); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); +}); + +test.skip('it cannot add lifecycle hook with no lifecycle checks to asset', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); + + const result = addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'LifecycleHook', + hookedProgram: account.publicKey, + lifecycleChecks: {}, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); +}); + +test.skip('it cannot update lifecycle hook to have no lifecycle checks', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'LifecycleHook', + hookedProgram: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_LISTEN], + }, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); + + const result = updatePlugin(umi, { + asset: asset.publicKey, + plugin: { + key: { + type: 'LifecycleHook', + hookedProgram: account.publicKey, + }, + type: 'LifecycleHook', + lifecycleChecks: {}, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); +}); diff --git a/clients/js/test/externalPlugins/oracle.test.ts b/clients/js/test/externalPlugins/oracle.test.ts new file mode 100644 index 00000000..bee3e1fa --- /dev/null +++ b/clients/js/test/externalPlugins/oracle.test.ts @@ -0,0 +1,2778 @@ +import test from 'ava'; + +import { + mplCoreOracleExample, + fixedAccountInit, + fixedAccountSet, + preconfiguredAssetPdaInit, + MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + preconfiguredAssetPdaSet, + preconfiguredProgramPdaInit, + preconfiguredProgramPdaSet, + preconfiguredOwnerPdaInit, + preconfiguredOwnerPdaSet, + preconfiguredRecipientPdaInit, + preconfiguredRecipientPdaSet, + customPdaAllSeedsInit, + customPdaAllSeedsSet, + customPdaTypicalInit, + customPdaTypicalSet, + preconfiguredAssetPdaCustomOffsetInit, + preconfiguredAssetPdaCustomOffsetSet, + close, +} from '@metaplex-foundation/mpl-core-oracle-example'; +import { generateSigner, sol } from '@metaplex-foundation/umi'; +import { generateSignerWithSol } from '@metaplex-foundation/umi-bundle-tests'; +import { createAccount } from '@metaplex-foundation/mpl-toolbox'; +import { + assertAsset, + assertBurned, + assertCollection, + createUmi as baseCreateUmi, + DEFAULT_ASSET, + DEFAULT_COLLECTION, +} from '../_setupRaw'; +import { createAsset, createAssetWithCollection } from '../_setupSdk'; +import { + burn, + CheckResult, + create, + findOracleAccount, + OracleInitInfoArgs, + transfer, + update, + addPlugin, + updatePlugin, + fetchAssetV1, + ExternalValidationResult, +} from '../../src'; + +const createUmi = async () => + (await baseCreateUmi()).use(mplCoreOracleExample()); + +test('it can use fixed address oracle to deny update', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + const result = update(umi, { + asset, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await fixedAccountSet(umi, { + account: account.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'new name 2', + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it can use fixed address oracle to deny update via collection', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const { asset, collection } = await createAssetWithCollection( + umi, + {}, + { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + } + ); + + const result = update(umi, { + asset, + collection, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await fixedAccountSet(umi, { + account: account.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + collection, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'new name 2', + }); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it can use fixed address oracle to deny transfer', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + const newOwner = generateSigner(umi); + + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await fixedAccountSet(umi, { + account: account.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot create asset with oracle that has no lifecycle checks', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + // Oracle with no lifecycle checks + const result = createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: {}, + baseAddress: account.publicKey, + }, + ], + }); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); +}); + +test('it can add oracle that can reject to asset', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); + + await addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot add oracle with no lifecycle checks to asset', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + }); + + // Oracle with no lifecycle checks + const result = addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: {}, + baseAddress: account.publicKey, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); +}); + +test('it cannot update oracle to have no lifecycle checks', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + // Oracle with no lifecycle checks + const result = updatePlugin(umi, { + asset: asset.publicKey, + + plugin: { + key: { + type: 'Oracle', + baseAddress: account.publicKey, + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: {}, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'RequiresLifecycleCheck' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot create asset with oracle that can approve', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + // Oracle with `CheckResult.CAN_APPROVE` + const result = createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); +}); + +test('it cannot create asset with oracle that can approve in addition to reject', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + // Oracle with `CheckResult.CAN_APPROVE` + const result = createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE, CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); +}); + +test('it cannot add oracle to asset that can approve', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); + + // Oracle with `CheckResult.CAN_APPROVE` + const result = addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE], + }, + baseAddress: account.publicKey, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); +}); + +test('it cannot add oracle to asset that can approve in addition to reject', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); + + // Oracle with `CheckResult.CAN_APPROVE` + const result = addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE, CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); +}); + +test('it cannot update oracle to approve', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const result = updatePlugin(umi, { + asset: asset.publicKey, + + plugin: { + key: { + type: 'Oracle', + baseAddress: account.publicKey, + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE], + }, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot update oracle to approve in addition to reject', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const result = updatePlugin(umi, { + asset: asset.publicKey, + + plugin: { + key: { + type: 'Oracle', + baseAddress: account.publicKey, + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_APPROVE, CheckResult.CAN_REJECT], + }, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot create asset with oracle that can listen', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + // Oracle with `CheckResult.CAN_LISTEN` + const result = createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_LISTEN], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); +}); + +test('it cannot add oracle to asset that can listen', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); + + // Oracle with `CheckResult.CAN_LISTEN` + const result = addPlugin(umi, { + asset: asset.publicKey, + plugin: { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_LISTEN], + }, + baseAddress: account.publicKey, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: undefined, + }); +}); + +test('it cannot update oracle to listen', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const result = updatePlugin(umi, { + asset: asset.publicKey, + + plugin: { + key: { + type: 'Oracle', + baseAddress: account.publicKey, + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_LISTEN], + }, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'OracleCanRejectOnly' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it cannot use fixed address oracle to deny transfer if not registered for lifecycle event', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + const owner = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + const newOwner = generateSigner(umi); + + await transfer(umi, { + asset, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it can use fixed address oracle to deny create', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Rejected, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const assetSigner = generateSigner(umi); + const result = create(umi, { + ...DEFAULT_ASSET, + asset: assetSigner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + await fixedAccountSet(umi, { + account: account.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await create(umi, { + ...DEFAULT_ASSET, + asset: assetSigner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: assetSigner.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account.publicKey, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it can use fixed address oracle to deny burn', async (t) => { + const umi = await createUmi(); + const account = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing the oracle account + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: account.publicKey, + }, + ], + }); + + const result = burn(umi, { + asset, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await fixedAccountSet(umi, { + account: account.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await burn(umi, { + asset, + }).sendAndConfirm(umi); + + await assertBurned(t, umi, asset.publicKey); +}); + +test('it can use preconfigured program pda oracle to deny update', async (t) => { + const umi = await createUmi(); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredProgram', + }, + }; + + const asset = await createAsset(umi, { + plugins: [oraclePlugin], + }); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, {}); + + // Need to close program account from previous test runs on same amman/validator session. + try { + await close(umi, { + account, + signer: umi.identity, + payer: umi.identity, + }).sendAndConfirm(umi); + } catch (error) { + if (error.name === 'ProgramErrorNotRecognizedError') { + // Do nothing. + } else { + throw error; + } + } + + // write to the PDA which corresponds to the asset + await preconfiguredProgramPdaInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = update(umi, { + asset, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await preconfiguredProgramPdaSet(umi, { + account, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'new name 2', + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredProgram', + }, + }, + ], + }); +}); + +test('it can use preconfigured collection pda oracle to deny update', async (t) => { + const umi = await createUmi(); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredCollection', + }, + }; + + const { asset, collection } = await createAssetWithCollection( + umi, + { + plugins: [oraclePlugin], + }, + {} + ); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + collection: collection.publicKey, + }); + + // write to the PDA which corresponds to the asset + await preconfiguredAssetPdaInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + additionalPubkey: collection.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = update(umi, { + asset, + collection, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await preconfiguredAssetPdaSet(umi, { + account, + signer: umi.identity, + args: { + additionalPubkey: collection.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + collection, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + updateAuthority: { type: 'Collection', address: collection.publicKey }, + owner: umi.identity.publicKey, + name: 'new name 2', + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredCollection', + }, + }, + ], + }); +}); + +test('it can use preconfigured owner pda oracle to deny burn', async (t) => { + const umi = await createUmi(); + const owner = await generateSignerWithSol(umi); + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredOwner', + }, + }; + + const asset = await createAsset(umi, { + owner, + plugins: [oraclePlugin], + }); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + owner: owner.publicKey, + }); + + // write to the PDA which corresponds to the asset + await preconfiguredOwnerPdaInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + additionalPubkey: owner.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + const result = burn(umi, { + asset, + authority: owner, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + burn: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredOwner', + }, + }, + ], + }); + + await preconfiguredOwnerPdaSet(umi, { + account, + signer: umi.identity, + args: { + additionalPubkey: owner.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await burn(umi, { + asset, + authority: owner, + }).sendAndConfirm(umi); + + await assertBurned(t, umi, asset.publicKey); +}); + +test('it can use preconfigured recipient pda oracle to deny transfer', async (t) => { + const umi = await createUmi(); + const newOwner = generateSigner(umi); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredRecipient', + }, + }; + + const asset = await createAsset(umi, { + plugins: [oraclePlugin], + }); + + // Find the oracle PDA for the new owner. + const account = findOracleAccount(umi, oraclePlugin, { + recipient: newOwner.publicKey, + }); + + // write to the PDA which corresponds to the new owner. + await preconfiguredRecipientPdaInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + additionalPubkey: newOwner.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await preconfiguredRecipientPdaSet(umi, { + account, + signer: umi.identity, + args: { + additionalPubkey: newOwner.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredRecipient', + }, + }, + ], + }); +}); + +test('it can use preconfigured asset pda oracle to deny update', async (t) => { + const umi = await createUmi(); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredAsset', + }, + }; + + const asset = await createAsset(umi, { + plugins: [oraclePlugin], + }); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + asset: asset.publicKey, + }); + + // write to the PDA which corresponds to the asset + await preconfiguredAssetPdaInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + additionalPubkey: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = update(umi, { + asset, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await preconfiguredAssetPdaSet(umi, { + account, + signer: umi.identity, + args: { + additionalPubkey: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'new name 2', + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredAsset', + }, + }, + ], + }); +}); + +test('it can use custom pda (all seeds) oracle to deny transfer', async (t) => { + const umi = await createUmi(); + const seedPubkey = generateSigner(umi).publicKey; + const owner = generateSigner(umi); + const newOwner = generateSigner(umi); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { type: 'Collection' }, + { type: 'Owner' }, + { type: 'Recipient' }, + { type: 'Asset' }, + { type: 'Address', pubkey: seedPubkey }, + { + type: 'Bytes', + bytes: Buffer.from('example-seed-bytes', 'utf8'), + }, + ], + }, + }; + + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner, + plugins: [oraclePlugin], + }, + {} + ); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + collection: collection.publicKey, + owner: owner.publicKey, + recipient: newOwner.publicKey, + asset: asset.publicKey, + }); + + // write to the PDA which corresponds to the asset + await customPdaAllSeedsInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + collection: collection.publicKey, + owner: owner.publicKey, + recipient: newOwner.publicKey, + asset: asset.publicKey, + address: seedPubkey, + bytes: Buffer.from('example-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await customPdaAllSeedsSet(umi, { + account, + signer: umi.identity, + args: { + collection: collection.publicKey, + owner: owner.publicKey, + recipient: newOwner.publicKey, + asset: asset.publicKey, + address: seedPubkey, + bytes: Buffer.from('example-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { type: 'Collection' }, + { type: 'Owner' }, + { type: 'Recipient' }, + { type: 'Asset' }, + { type: 'Address', pubkey: seedPubkey }, + { + type: 'Bytes', + bytes: new Uint8Array(Buffer.from('example-seed-bytes', 'utf8')), + }, + ], + }, + }, + ], + }); +}); + +test('it can use custom pda (typical) oracle to deny transfer', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const newOwner = generateSigner(umi); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { + type: 'Bytes', + bytes: Buffer.from('prefix-seed-bytes', 'utf8'), + }, + { type: 'Collection' }, + { + type: 'Bytes', + bytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + }, + ], + }, + }; + + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner, + plugins: [oraclePlugin], + }, + {} + ); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + collection: collection.publicKey, + }); + + // write to the PDA + await customPdaTypicalInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + prefixBytes: Buffer.from('prefix-seed-bytes', 'utf8'), + collection: collection.publicKey, + additionalBytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await customPdaTypicalSet(umi, { + account, + signer: umi.identity, + args: { + prefixBytes: Buffer.from('prefix-seed-bytes', 'utf8'), + collection: collection.publicKey, + additionalBytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { + type: 'Bytes', + bytes: new Uint8Array(Buffer.from('prefix-seed-bytes', 'utf8')), + }, + { type: 'Collection' }, + { + type: 'Bytes', + bytes: new Uint8Array( + Buffer.from('additional-bytes-seed-bytes', 'utf8') + ), + }, + ], + }, + }, + ], + }); +}); + +test('it can use custom pda (with custom program ID) oracle to deny transfer', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const newOwner = generateSigner(umi); + + // Configure an Oracle plugin to have a custom program ID. In order to reuse the oracle + // example program we will set the base address to a random Pubkey, and set the custom program + // ID to the oracle example program ID. + const randomProgramId = generateSigner(umi).publicKey; + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: randomProgramId, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { + type: 'Bytes', + bytes: Buffer.from('prefix-seed-bytes', 'utf8'), + }, + { type: 'Collection' }, + { + type: 'Bytes', + bytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + }, + ], + customProgramId: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + }, + }; + + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner, + plugins: [oraclePlugin], + }, + {} + ); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + collection: collection.publicKey, + }); + + // write to the PDA + await customPdaTypicalInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + prefixBytes: Buffer.from('prefix-seed-bytes', 'utf8'), + collection: collection.publicKey, + additionalBytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const result = transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await customPdaTypicalSet(umi, { + account, + signer: umi.identity, + args: { + prefixBytes: Buffer.from('prefix-seed-bytes', 'utf8'), + collection: collection.publicKey, + additionalBytes: Buffer.from('additional-bytes-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: randomProgramId, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { + type: 'Bytes', + bytes: new Uint8Array(Buffer.from('prefix-seed-bytes', 'utf8')), + }, + { type: 'Collection' }, + { + type: 'Bytes', + bytes: new Uint8Array( + Buffer.from('additional-bytes-seed-bytes', 'utf8') + ), + }, + ], + customProgramId: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + }, + }, + ], + }); +}); + +test('it can use preconfigured asset pda custom offset oracle to deny update', async (t) => { + const umi = await createUmi(); + + // This test uses an oracle with the data struct: + // pub struct CustomDataValidation { + // pub authority: Pubkey, + // pub sequence_num: u64, + // pub validation: OracleValidation, + // } + // + // Thus the `resultsOffset` below is set to 48. This is because the anchor discriminator, the + // `authority` `Pubkey`, and the `sequence_num` all precede the `OracleValidation` struct + // within the account: + // + // 8 (anchor discriminator) + 32 (authority) + 8 (sequence number) = 48. + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Custom', + offset: 48n, + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'PreconfiguredAsset', + }, + }; + + const asset = await createAsset(umi, { + plugins: [oraclePlugin], + }); + + // Find the oracle PDA based on the asset we just created + const account = findOracleAccount(umi, oraclePlugin, { + asset: asset.publicKey, + }); + + const dataAuthority = generateSigner(umi); + // write to the PDA which corresponds to the asset + await preconfiguredAssetPdaCustomOffsetInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + authority: dataAuthority.publicKey, + asset: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }).sendAndConfirm(umi); + + const updateResult = update(umi, { + asset, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(updateResult, { name: 'InvalidAuthority' }); + + // Making sure the incorrect authority cannot update the oracle. This is more just a test of the + // example program functionality. + const setResult = preconfiguredAssetPdaCustomOffsetSet(umi, { + account, + authority: umi.identity, + sequenceNum: 2, + asset: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }).sendAndConfirm(umi); + + await t.throwsAsync(setResult, { name: 'ProgramErrorNotRecognizedError' }); + + // Making sure a lower sequence number passes but does not update the oracle. This is also just + // a test of the example program functionality. + await preconfiguredAssetPdaCustomOffsetSet(umi, { + account, + authority: dataAuthority, + sequenceNum: 0, + asset: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }).sendAndConfirm(umi); + + // Validate still cannot update the mpl-core asset because the oracle did not change. + const updateResult2 = update(umi, { + asset, + name: 'new name', + }).sendAndConfirm(umi); + + await t.throwsAsync(updateResult2, { name: 'InvalidAuthority' }); + + // Oracle update that works. + await preconfiguredAssetPdaCustomOffsetSet(umi, { + account, + authority: dataAuthority, + sequenceNum: 2, + asset: asset.publicKey, + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }).sendAndConfirm(umi); + + await update(umi, { + asset, + name: 'new name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'new name 2', + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Custom', + offset: 48n, + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: { + type: 'PreconfiguredAsset', + }, + }, + ], + }); +}); + +test('it can use one fixed address oracle to deny transfer when a second oracle allows it', async (t) => { + const umi = await createUmi(); + const account1 = generateSigner(umi); + const account2 = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: account1, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: account2, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + // create asset referencing both oracle accounts + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account1.publicKey, + }, + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: account2.publicKey, + }, + ], + }); + + const newOwner = generateSigner(umi); + + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidAuthority' }); + + await fixedAccountSet(umi, { + account: account1.publicKey, + signer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + await transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: newOwner.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account1.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: account2.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + ], + }); +}); + +test('it can update asset to different size name with oracle', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + await fixedAccountInit(umi, { + signer: umi.identity, + account: oracleSigner, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + const asset = generateSigner(umi); + await create(umi, { + asset, + name: 'Test name', + uri: 'https://example.com', + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await fixedAccountSet(umi, { + signer: umi.identity, + account: oracleSigner.publicKey, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const a = await fetchAssetV1(umi, asset.publicKey); + + await update(umi, { + asset: a, + name: 'name 2', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + uri: 'https://example.com', + name: 'name 2', + owner: umi.identity.publicKey, + asset: asset.publicKey, + oracles: [ + { + authority: { + type: 'UpdateAuthority', + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it can update oracle to different size external plugin adapter', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + await fixedAccountInit(umi, { + signer: umi.identity, + account: oracleSigner, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + const asset = generateSigner(umi); + await create(umi, { + asset, + name: 'Test name', + uri: 'https://example.com', + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + update: [CheckResult.CAN_REJECT], + transfer: [CheckResult.CAN_REJECT], + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + uri: 'https://example.com', + name: 'Test name', + owner: umi.identity.publicKey, + asset: asset.publicKey, + oracles: [ + { + authority: { + type: 'UpdateAuthority', + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + update: [CheckResult.CAN_REJECT], + transfer: [CheckResult.CAN_REJECT], + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + baseAddressConfig: undefined, + }, + ], + }); + + await updatePlugin(umi, { + asset: asset.publicKey, + + plugin: { + key: { + type: 'Oracle', + baseAddress: oracleSigner.publicKey, + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + uri: 'https://example.com', + name: 'Test name', + owner: umi.identity.publicKey, + asset: asset.publicKey, + oracles: [ + { + authority: { + type: 'UpdateAuthority', + }, + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + baseAddressConfig: undefined, + }, + ], + }); +}); + +test('it create fails but does not panic when oracle account does not exist', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + + const asset = generateSigner(umi); + const result = create(umi, { + asset, + name: 'Test name', + uri: 'https://example.com', + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + update: [CheckResult.CAN_REJECT], + transfer: [CheckResult.CAN_REJECT], + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); + +test('it transfer fails but does not panic when oracle account does not exist', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: oracleSigner.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const newOwner = generateSigner(umi); + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); + +test('it transfer fails but does not panic when oracle account is too small', async (t) => { + const umi = await createUmi(); + const newAccount = generateSigner(umi); + + // Create an invalid oracle account that is an account with 3 bytes. + await createAccount(umi, { + newAccount, + lamports: sol(0.1), + space: 3, + programId: umi.programs.get('mplCore').publicKey, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: newAccount.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: newAccount.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const newOwner = generateSigner(umi); + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); + +test('it empty account does not default to valid oracle', async (t) => { + const umi = await createUmi(); + const newAccount = generateSigner(umi); + + // Create an invalid oracle account that is an account with 42 bytes. + await createAccount(umi, { + newAccount, + lamports: sol(0.1), + space: 42, + programId: umi.programs.get('mplCore').publicKey, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: newAccount.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: newAccount.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddressConfig: undefined, + }, + ], + }); + + const newOwner = generateSigner(umi); + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'UninitializedOracleAccount' }); +}); diff --git a/clients/js/test/getProgram.test.ts b/clients/js/test/getProgram.test.ts index 593c501e..8721654c 100644 --- a/clients/js/test/getProgram.test.ts +++ b/clients/js/test/getProgram.test.ts @@ -1,6 +1,6 @@ import test from 'ava'; import { MPL_CORE_PROGRAM_ID } from '../src'; -import { createUmi } from './_setup'; +import { createUmi } from './_setupRaw'; test('it registers the program', async (t) => { // Given a Umi instance using the project's plugin. diff --git a/clients/js/test/helps/authority.test.ts b/clients/js/test/helps/authority.test.ts index d7fd14bb..3aad723f 100644 --- a/clients/js/test/helps/authority.test.ts +++ b/clients/js/test/helps/authority.test.ts @@ -10,7 +10,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from '../_setup'; +} from '../_setupRaw'; test('it throws when not matching asset and collection', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/helps/fetch.test.ts b/clients/js/test/helps/fetch.test.ts new file mode 100644 index 00000000..5375a57b --- /dev/null +++ b/clients/js/test/helps/fetch.test.ts @@ -0,0 +1,159 @@ +import test from 'ava'; + +import { generateSigner } from '@metaplex-foundation/umi'; +import { + fetchAssetsByCollection, + fetchAssetsByOwner, + fetchCollectionsByUpdateAuthority, +} from '../../src'; +import { createUmi } from '../_setupRaw'; +import { createAsset, createCollection } from '../_setupSdk'; + +test('it can use the helper to fetch assets by owner', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + + const assets = await Promise.all( + Array(5) + .fill(0) + .map(() => createAsset(umi, { owner: owner.publicKey })) + ); + + const fetchedAssets = await fetchAssetsByOwner(umi, owner.publicKey); + + t.is(fetchedAssets.length, assets.length); + t.deepEqual( + fetchedAssets.map((asset) => asset.publicKey).sort(), + assets.map((asset) => asset.publicKey).sort() + ); +}); + +test('it can use helper to fetch assets by collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + const assets = await Promise.all( + Array(5) + .fill(0) + .map(() => createAsset(umi, { collection })) + ); + + const fetchedAssets = await fetchAssetsByCollection( + umi, + collection.publicKey + ); + + t.is(fetchedAssets.length, assets.length); + t.deepEqual( + fetchedAssets.map((asset) => asset.publicKey).sort(), + assets.map((asset) => asset.publicKey).sort() + ); +}); + +test('it can use helper to fetch collections by update authority', async (t) => { + const umi = await createUmi(); + const updateAuthority = generateSigner(umi); + + const collections = await Promise.all( + Array(5) + .fill(0) + .map(() => + createCollection(umi, { updateAuthority: updateAuthority.publicKey }) + ) + ); + + const fetchedCollections = await fetchCollectionsByUpdateAuthority( + umi, + updateAuthority.publicKey + ); + + t.is(fetchedCollections.length, collections.length); + t.deepEqual( + fetchedCollections.map((collection) => collection.publicKey).sort(), + collections.map((collection) => collection.publicKey).sort() + ); +}); + +test('it can use helper to fetch assets by collection and derive plugins', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi, { + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'collection', + value: 'col', + }, + ], + }, + ], + }); + + const override = await createAsset(umi, { + collection, + plugins: [ + { + type: 'Attributes', + attributeList: [ + { + key: 'asset', + value: 'asset', + }, + ], + }, + ], + }); + + const assets = await Promise.all( + Array(4) + .fill(0) + .map(() => + createAsset(umi, { + collection, + plugins: [ + { + type: 'FreezeDelegate', + frozen: true, + }, + ], + }) + ) + ); + + const fetchedAssets = await fetchAssetsByCollection( + umi, + collection.publicKey + ); + + t.is(fetchedAssets.length, assets.length + 1); + + fetchedAssets.forEach((asset) => { + if (asset.publicKey === override.publicKey) { + t.like(asset, { + attributes: { + attributeList: [ + { + key: 'asset', + value: 'asset', + }, + ], + }, + }); + } else { + t.like(asset, { + freezeDelegate: { + frozen: true, + }, + attributes: { + attributeList: [ + { + key: 'collection', + value: 'col', + }, + ], + }, + }); + } + }); +}); diff --git a/clients/js/test/helps/lifecycle.test.ts b/clients/js/test/helps/lifecycle.test.ts index 1fc38975..b947cba6 100644 --- a/clients/js/test/helps/lifecycle.test.ts +++ b/clients/js/test/helps/lifecycle.test.ts @@ -1,12 +1,24 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; import { - addressPluginAuthority, + customPdaAllSeedsInit, + fixedAccountInit, + MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, +} from '@metaplex-foundation/mpl-core-oracle-example'; +import { canBurn, canTransfer, - pluginAuthorityPair, + CheckResult, + findOracleAccount, + LifecycleValidationError, + OracleInitInfoArgs, + validateBurn, + validateTransfer, + validateUpdate, + ExternalValidationResult, } from '../../src'; -import { createAsset, createAssetWithCollection, createUmi } from '../_setup'; +import { createUmi } from '../_setupRaw'; +import { createAsset, createAssetWithCollection } from '../_setupSdk'; test('it can detect transferrable on basic asset', async (t) => { const umi = await createUmi(); @@ -17,6 +29,10 @@ test('it can detect transferrable on basic asset', async (t) => { }); t.assert(canTransfer(owner.publicKey, asset)); + t.is( + await validateTransfer(umi, { authority: owner.publicKey, asset }), + null + ); }); test('it can detect non transferrable from frozen asset', async (t) => { @@ -26,14 +42,18 @@ test('it can detect non transferrable from frozen asset', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }); t.assert(!canTransfer(owner.publicKey, asset)); + t.is( + await validateTransfer(umi, { authority: owner.publicKey, asset }), + LifecycleValidationError.AssetFrozen + ); }); test('it can detect transferrable on asset with transfer delegate', async (t) => { @@ -44,14 +64,21 @@ test('it can detect transferrable on asset with transfer delegate', async (t) => const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'TransferDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], }); t.assert(canTransfer(delegate.publicKey, asset)); + t.is( + await validateTransfer(umi, { authority: delegate.publicKey, asset }), + null + ); }); test('it can detect transferrable from permanent transfer', async (t) => { @@ -62,14 +89,21 @@ test('it can detect transferrable from permanent transfer', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'PermanentTransferDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], }); t.assert(canTransfer(delegate.publicKey, asset)); + t.is( + await validateTransfer(umi, { authority: delegate.publicKey, asset }), + null + ); }); test('it can detect transferrable when frozen with permanent transfer', async (t) => { @@ -80,19 +114,30 @@ test('it can detect transferrable when frozen with permanent transfer', async (t const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'PermanentTransferDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), - pluginAuthorityPair({ + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }); t.assert(!canTransfer(owner.publicKey, asset)); t.assert(canTransfer(delegate.publicKey, asset)); + t.is( + await validateTransfer(umi, { authority: owner.publicKey, asset }), + LifecycleValidationError.AssetFrozen + ); + t.is( + await validateTransfer(umi, { authority: delegate.publicKey, asset }), + null + ); }); test('it can detect transferrable when frozen with permanent collection transfer delegate', async (t) => { @@ -105,24 +150,43 @@ test('it can detect transferrable when frozen with permanent collection transfer { owner, plugins: [ - pluginAuthorityPair({ + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }, { plugins: [ - pluginAuthorityPair({ + { type: 'PermanentTransferDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], } ); t.assert(!canTransfer(owner.publicKey, asset, collection)); t.assert(canTransfer(delegate.publicKey, asset, collection)); + t.is( + await validateTransfer(umi, { + authority: owner.publicKey, + asset, + collection, + }), + LifecycleValidationError.AssetFrozen + ); + t.is( + await validateTransfer(umi, { + authority: delegate.publicKey, + asset, + collection, + }), + null + ); }); test('it can detect burnable on basic asset', async (t) => { @@ -134,6 +198,7 @@ test('it can detect burnable on basic asset', async (t) => { }); t.assert(canBurn(owner.publicKey, asset)); + t.is(await validateBurn(umi, { authority: owner.publicKey, asset }), null); }); test('it can detect non burnable from frozen asset', async (t) => { @@ -143,14 +208,18 @@ test('it can detect non burnable from frozen asset', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }); t.assert(!canBurn(owner.publicKey, asset)); + t.is( + await validateBurn(umi, { authority: owner.publicKey, asset }), + LifecycleValidationError.AssetFrozen + ); }); test('it can detect burnable on asset with burn delegate', async (t) => { @@ -161,14 +230,18 @@ test('it can detect burnable on asset with burn delegate', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'BurnDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], }); t.assert(canBurn(delegate.publicKey, asset)); + t.is(await validateBurn(umi, { authority: delegate.publicKey, asset }), null); }); test('it can detect burnable from permanent burn', async (t) => { @@ -179,14 +252,18 @@ test('it can detect burnable from permanent burn', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'PermanentBurnDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], }); t.assert(canBurn(delegate.publicKey, asset)); + t.is(await validateBurn(umi, { authority: delegate.publicKey, asset }), null); }); test('it can detect burnable when frozen with permanent burn', async (t) => { @@ -197,19 +274,27 @@ test('it can detect burnable when frozen with permanent burn', async (t) => { const asset = await createAsset(umi, { owner, plugins: [ - pluginAuthorityPair({ + { type: 'PermanentBurnDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), - pluginAuthorityPair({ + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }); t.assert(!canBurn(owner.publicKey, asset)); t.assert(canBurn(delegate.publicKey, asset)); + t.is( + await validateBurn(umi, { authority: owner.publicKey, asset }), + LifecycleValidationError.AssetFrozen + ); + t.is(await validateBurn(umi, { authority: delegate.publicKey, asset }), null); }); test('it can detect burnable when frozen with permanent collection burn delegate', async (t) => { @@ -222,22 +307,446 @@ test('it can detect burnable when frozen with permanent collection burn delegate { owner, plugins: [ - pluginAuthorityPair({ + { type: 'FreezeDelegate', - data: { frozen: true }, - }), + frozen: true, + }, ], }, { plugins: [ - pluginAuthorityPair({ + { type: 'PermanentBurnDelegate', - authority: addressPluginAuthority(delegate.publicKey), - }), + authority: { + type: 'Address', + address: delegate.publicKey, + }, + }, ], } ); t.assert(!canBurn(owner.publicKey, asset, collection)); t.assert(canBurn(delegate.publicKey, asset, collection)); + t.is( + await validateBurn(umi, { authority: owner.publicKey, asset, collection }), + LifecycleValidationError.AssetFrozen + ); + t.is( + await validateBurn(umi, { + authority: delegate.publicKey, + asset, + collection, + }), + null + ); +}); + +test('it can validate non-transferrable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateTransfer(umi, { + authority: owner.publicKey, + asset, + }), + LifecycleValidationError.OracleValidationFailed + ); +}); + +test('it can validate transferrable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + const oracle2 = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + { + type: 'Oracle', + baseAddress: oracle2.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateTransfer(umi, { + authority: owner.publicKey, + asset, + }), + null + ); +}); + +test('it can validate non-transferrable asset with oracle with recipient seed', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const seedPubkey = generateSigner(umi).publicKey; + const newOwner = generateSigner(umi); + + const oraclePlugin: OracleInitInfoArgs = { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { type: 'Collection' }, + { type: 'Owner' }, + { type: 'Recipient' }, + { type: 'Asset' }, + { type: 'Address', pubkey: seedPubkey }, + { + type: 'Bytes', + bytes: Buffer.from('example-seed-bytes', 'utf8'), + }, + ], + }, + }; + + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner, + plugins: [oraclePlugin], + }, + {} + ); + + const account = findOracleAccount(umi, oraclePlugin, { + collection: collection.publicKey, + owner: owner.publicKey, + recipient: newOwner.publicKey, + asset: asset.publicKey, + }); + + // write to example program oracle account + await customPdaAllSeedsInit(umi, { + account, + signer: umi.identity, + payer: umi.identity, + args: { + collection: collection.publicKey, + owner: owner.publicKey, + recipient: newOwner.publicKey, + asset: asset.publicKey, + address: seedPubkey, + bytes: Buffer.from('example-seed-bytes', 'utf8'), + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Rejected, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + t.is( + await validateTransfer(umi, { + authority: owner.publicKey, + asset, + recipient: newOwner.publicKey, + collection, + }), + LifecycleValidationError.OracleValidationFailed + ); +}); + +test('it can validate and skip transferrable asset with oracle with recipient seed if missing recipient', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + + const { asset, collection } = await createAssetWithCollection( + umi, + { + owner, + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: MPL_CORE_ORACLE_EXAMPLE_PROGRAM_ID, + baseAddressConfig: { + type: 'CustomPda', + seeds: [ + { + type: 'Recipient', + }, + ], + }, + }, + ], + }, + {} + ); + + t.is( + await validateTransfer(umi, { + authority: owner.publicKey, + collection, + asset, + }), + null + ); +}); + +test('it can validate non-burnable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + burn: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateBurn(umi, { + authority: owner.publicKey, + asset, + }), + LifecycleValidationError.OracleValidationFailed + ); +}); + +test('it can validate burnable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateBurn(umi, { + authority: owner.publicKey, + asset, + }), + null + ); +}); + +test('it can validate non-updatable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Rejected, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Rejected, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + update: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateUpdate(umi, { + authority: owner.publicKey, + asset, + }), + LifecycleValidationError.OracleValidationFailed + ); +}); + +test('it can validate updatable asset with oracle', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const oracle = generateSigner(umi); + + // write to example program oracle account + await fixedAccountInit(umi, { + account: oracle, + signer: umi.identity, + payer: umi.identity, + args: { + oracleData: { + __kind: 'V1', + create: ExternalValidationResult.Pass, + update: ExternalValidationResult.Pass, + transfer: ExternalValidationResult.Pass, + burn: ExternalValidationResult.Pass, + }, + }, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Oracle', + baseAddress: oracle.publicKey, + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + }, + ], + }); + + t.is( + await validateUpdate(umi, { + authority: umi.identity.publicKey, + asset, + }), + null + ); }); diff --git a/clients/js/test/helps/plugin.test.ts b/clients/js/test/helps/plugin.test.ts index a430b54b..7e5601bf 100644 --- a/clients/js/test/helps/plugin.test.ts +++ b/clients/js/test/helps/plugin.test.ts @@ -10,7 +10,7 @@ import { pluginTypeFromAssetPluginKey, updatePluginAuthority, } from '../../src'; -import { createAssetWithCollection, createUmi } from '../_setup'; +import { createAssetWithCollection, createUmi } from '../_setupRaw'; test('it can convert plugin key to plugin type', async (t) => { const key: AssetPluginKey = 'royalties'; diff --git a/clients/js/test/helps/state.test.ts b/clients/js/test/helps/state.test.ts index 3fad7816..3f82c6bf 100644 --- a/clients/js/test/helps/state.test.ts +++ b/clients/js/test/helps/state.test.ts @@ -1,6 +1,10 @@ import test from 'ava'; import { isFrozen, pluginAuthorityPair } from '../../src'; -import { createAsset, createAssetWithCollection, createUmi } from '../_setup'; +import { + createAsset, + createAssetWithCollection, + createUmi, +} from '../_setupRaw'; test('it can detect frozen from freeze delegate', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/info.test.ts b/clients/js/test/info.test.ts index b72a8b9b..ff051e4f 100644 --- a/clients/js/test/info.test.ts +++ b/clients/js/test/info.test.ts @@ -1,7 +1,7 @@ import { generateSigner, publicKey } from '@metaplex-foundation/umi'; import test from 'ava'; import { DataState, createV1 /* fetchAsset, fetchHashedAsset */ } from '../src'; -import { createUmi } from './_setup'; +import { createUmi } from './_setupRaw'; test('fetch account info for account state', async (t) => { // Given a Umi instance and a new signer. @@ -46,7 +46,7 @@ test.skip('fetch account info for ledger state', async (t) => { // Print the size of the account. const account = await umi.rpc.getAccount(assetAddress.publicKey); if (account.exists) { - console.log(`Account Size ${account.data.length} bytes`); + // console.log(`Account Size ${account.data.length} bytes`); } // Then an account was created with the correct data. diff --git a/clients/js/test/instructions/freeze.test.ts b/clients/js/test/instructions/freeze.test.ts index e29ea6d7..fcd7a7a1 100644 --- a/clients/js/test/instructions/freeze.test.ts +++ b/clients/js/test/instructions/freeze.test.ts @@ -12,7 +12,7 @@ import { createAsset, createAssetWithCollection, createUmi, -} from '../_setup'; +} from '../_setupRaw'; test('it can use the freeze helper to freeze an asset', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/instructions/legacyDelegate.test.ts b/clients/js/test/instructions/legacyDelegate.test.ts index fe0af251..b97c10ed 100644 --- a/clients/js/test/instructions/legacyDelegate.test.ts +++ b/clients/js/test/instructions/legacyDelegate.test.ts @@ -1,6 +1,6 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { assertAsset, createAsset, createUmi } from '../_setup'; +import { assertAsset, createAsset, createUmi } from '../_setupRaw'; import { addressPluginAuthority, pluginAuthorityPair, diff --git a/clients/js/test/instructions/legacyRevoke.test.ts b/clients/js/test/instructions/legacyRevoke.test.ts index 2aedc7f6..c98baa2d 100644 --- a/clients/js/test/instructions/legacyRevoke.test.ts +++ b/clients/js/test/instructions/legacyRevoke.test.ts @@ -1,6 +1,6 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { assertAsset, createAsset, createUmi } from '../_setup'; +import { assertAsset, createAsset, createUmi } from '../_setupRaw'; import { pluginAuthorityPair, addressPluginAuthority, diff --git a/clients/js/test/plugins/asset/addBlocker.test.ts b/clients/js/test/plugins/asset/addBlocker.test.ts index cf6c875e..515976c7 100644 --- a/clients/js/test/plugins/asset/addBlocker.test.ts +++ b/clients/js/test/plugins/asset/addBlocker.test.ts @@ -1,13 +1,9 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { addPluginV1, createPlugin, pluginAuthorityPair } from '../../../src'; -import { - DEFAULT_ASSET, - assertAsset, - createAsset, - createUmi, -} from '../../_setup'; +import { addPlugin } from '../../../src'; +import { DEFAULT_ASSET, assertAsset, createUmi } from '../../_setupRaw'; +import { createAsset } from '../../_setupSdk'; test('it cannot add UA-managed plugin if addBlocker had been added on creation', async (t) => { // Given a Umi instance and a new signer. @@ -15,20 +11,18 @@ test('it cannot add UA-managed plugin if addBlocker had been added on creation', const asset = await createAsset(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'AddBlocker', - }), + }, ], }); - const result = addPluginV1(umi, { + const result = addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'Attributes', - data: { - attributeList: [], - }, - }), + attributeList: [], + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -40,21 +34,20 @@ test('it can add plugins unless AddBlocker is added', async (t) => { const umi = await createUmi(); const asset = await createAsset(umi); - await addPluginV1(umi, { + await addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'Attributes', - data: { - attributeList: [], - }, - }), + + attributeList: [], + }, }).sendAndConfirm(umi); - await addPluginV1(umi, { + await addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'AddBlocker', - }), + }, }).sendAndConfirm(umi); await assertAsset(t, umi, { @@ -74,14 +67,13 @@ test('it can add plugins unless AddBlocker is added', async (t) => { }, }); - const result = addPluginV1(umi, { + const result = addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'Attributes', - data: { - attributeList: [], - }, - }), + + attributeList: [], + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -95,15 +87,15 @@ test('it can add owner-managed plugins even if AddBlocker had been added', async const asset = await createAsset(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'AddBlocker', - }), + }, ], }); - await addPluginV1(umi, { + await addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ type: 'FreezeDelegate', data: { frozen: false } }), + plugin: { type: 'FreezeDelegate', frozen: false }, }).sendAndConfirm(umi); await assertAsset(t, umi, { @@ -133,12 +125,12 @@ test('it states that UA is the only one who can add the AddBlocker', async (t) = }); // random keypair can't add AddBlocker - let result = addPluginV1(umi, { + let result = addPlugin(umi, { authority: randomUser, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'AddBlocker', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -146,12 +138,12 @@ test('it states that UA is the only one who can add the AddBlocker', async (t) = }); // Owner can't add AddBlocker - result = addPluginV1(umi, { + result = addPlugin(umi, { authority: umi.identity, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'AddBlocker', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -159,10 +151,10 @@ test('it states that UA is the only one who can add the AddBlocker', async (t) = }); // UA CAN add AddBlocker - await addPluginV1(umi, { + await addPlugin(umi, { authority: updateAuthority, asset: asset.publicKey, - plugin: createPlugin({ type: 'AddBlocker' }), + plugin: { type: 'AddBlocker' }, }).sendAndConfirm(umi); await assertAsset(t, umi, { diff --git a/clients/js/test/plugins/asset/attributes.test.ts b/clients/js/test/plugins/asset/attributes.test.ts index 4a5aef3a..c8792a4d 100644 --- a/clients/js/test/plugins/asset/attributes.test.ts +++ b/clients/js/test/plugins/asset/attributes.test.ts @@ -9,7 +9,7 @@ import { assertAsset, createAsset, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can add attributes to an asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/delegate.test.ts b/clients/js/test/plugins/asset/delegate.test.ts index 422a92d7..723cf735 100644 --- a/clients/js/test/plugins/asset/delegate.test.ts +++ b/clients/js/test/plugins/asset/delegate.test.ts @@ -13,7 +13,7 @@ import { assertAsset, createAsset, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can delegate a new authority', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/delegateTransfer.test.ts b/clients/js/test/plugins/asset/delegateTransfer.test.ts index 99415273..42601f9b 100644 --- a/clients/js/test/plugins/asset/delegateTransfer.test.ts +++ b/clients/js/test/plugins/asset/delegateTransfer.test.ts @@ -13,7 +13,7 @@ import { assertAsset, createAsset, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('a delegate can transfer the asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/edition.test.ts b/clients/js/test/plugins/asset/edition.test.ts index 382c9c9f..932acce7 100644 --- a/clients/js/test/plugins/asset/edition.test.ts +++ b/clients/js/test/plugins/asset/edition.test.ts @@ -17,7 +17,7 @@ import { createAsset, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can create asset with edition plugin', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/plugins/asset/freeze.test.ts b/clients/js/test/plugins/asset/freeze.test.ts index ece446e1..fa6f0f7e 100644 --- a/clients/js/test/plugins/asset/freeze.test.ts +++ b/clients/js/test/plugins/asset/freeze.test.ts @@ -17,7 +17,7 @@ import { assertAsset, createAsset, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can freeze and unfreeze an asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/immutableMetadata.test.ts b/clients/js/test/plugins/asset/immutableMetadata.test.ts index b6e65c07..7662c115 100644 --- a/clients/js/test/plugins/asset/immutableMetadata.test.ts +++ b/clients/js/test/plugins/asset/immutableMetadata.test.ts @@ -1,18 +1,9 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { - addPluginV1, - createPlugin, - pluginAuthorityPair, - updateV1, -} from '../../../src'; -import { - DEFAULT_ASSET, - assertAsset, - createAsset, - createUmi, -} from '../../_setup'; +import { addPlugin, update } from '../../../src'; +import { DEFAULT_ASSET, assertAsset, createUmi } from '../../_setupRaw'; +import { createAsset } from '../../_setupSdk'; test('it can prevent the asset from metadata updating', async (t) => { // Given a Umi instance and a new signer. @@ -20,16 +11,16 @@ test('it can prevent the asset from metadata updating', async (t) => { const asset = await createAsset(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'ImmutableMetadata', - }), + }, ], }); - const result = updateV1(umi, { - asset: asset.publicKey, - newName: 'bread', - newUri: 'https://example.com/bread', + const result = update(umi, { + asset, + name: 'bread', + uri: 'https://example.com/bread', }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -41,10 +32,10 @@ test('it can mutate its metadata unless ImmutableMetadata plugin is added', asyn const umi = await createUmi(); const asset = await createAsset(umi); - await updateV1(umi, { - asset: asset.publicKey, - newName: 'Test Bread 2', - newUri: 'https://example.com/bread2', + await update(umi, { + asset, + name: 'Test Bread 2', + uri: 'https://example.com/bread2', }).sendAndConfirm(umi); await assertAsset(t, umi, { @@ -55,17 +46,17 @@ test('it can mutate its metadata unless ImmutableMetadata plugin is added', asyn uri: 'https://example.com/bread2', }); - await addPluginV1(umi, { + await addPlugin(umi, { asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); - const result = updateV1(umi, { - asset: asset.publicKey, - newName: 'Test Bread 3', - newUri: 'https://example.com/bread3', + const result = update(umi, { + asset, + name: 'Test Bread 3', + uri: 'https://example.com/bread3', }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -82,12 +73,12 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn }); // random keypair can't add ImmutableMetadata - let result = addPluginV1(umi, { + let result = addPlugin(umi, { authority: randomUser, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -95,12 +86,12 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn }); // Owner can't add ImmutableMetadata - result = addPluginV1(umi, { + result = addPlugin(umi, { authority: umi.identity, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -108,10 +99,10 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn }); // UA CAN add ImmutableMetadata - await addPluginV1(umi, { + await addPlugin(umi, { authority: updateAuthority, asset: asset.publicKey, - plugin: createPlugin({ type: 'ImmutableMetadata' }), + plugin: { type: 'ImmutableMetadata' }, }).sendAndConfirm(umi); await assertAsset(t, umi, { diff --git a/clients/js/test/plugins/asset/permanentBurn.test.ts b/clients/js/test/plugins/asset/permanentBurn.test.ts index 536aa739..5270d37b 100644 --- a/clients/js/test/plugins/asset/permanentBurn.test.ts +++ b/clients/js/test/plugins/asset/permanentBurn.test.ts @@ -17,10 +17,11 @@ import { } from '../../../src'; import { assertAsset, + assertBurned, createAsset, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can burn an assets as an owner', async (t) => { // Given a Umi instance and a new signer. @@ -39,12 +40,8 @@ test('it can burn an assets as an owner', async (t) => { asset: asset.publicKey, }).sendAndConfirm(umi); - const afterAsset = await umi.rpc.getAccount(asset.publicKey); - t.true(afterAsset.exists); - assertAccountExists(afterAsset); + const afterAsset = await assertBurned(t, umi, asset.publicKey); t.deepEqual(afterAsset.lamports, sol(0.00089784 + 0.0015)); - t.is(afterAsset.data.length, 1); - t.is(afterAsset.data[0], Key.Uninitialized); }); test('it can burn an assets as a delegate', async (t) => { diff --git a/clients/js/test/plugins/asset/permanentFreeze.test.ts b/clients/js/test/plugins/asset/permanentFreeze.test.ts index 438b7ab8..62bcbd30 100644 --- a/clients/js/test/plugins/asset/permanentFreeze.test.ts +++ b/clients/js/test/plugins/asset/permanentFreeze.test.ts @@ -15,7 +15,7 @@ import { createAsset, createAssetWithCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can freeze and unfreeze an asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/permanentTransfer.test.ts b/clients/js/test/plugins/asset/permanentTransfer.test.ts index 1c8f2df3..fe5f817f 100644 --- a/clients/js/test/plugins/asset/permanentTransfer.test.ts +++ b/clients/js/test/plugins/asset/permanentTransfer.test.ts @@ -19,7 +19,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it cannot add permanentTransfer after creation', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/royalties.test.ts b/clients/js/test/plugins/asset/royalties.test.ts index 58f1dfd6..bc641c4f 100644 --- a/clients/js/test/plugins/asset/royalties.test.ts +++ b/clients/js/test/plugins/asset/royalties.test.ts @@ -21,7 +21,7 @@ import { createAsset, createAssetWithCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can transfer an asset with royalties', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/plugins/asset/updateDelegate.test.ts b/clients/js/test/plugins/asset/updateDelegate.test.ts index 53b32096..15a72822 100644 --- a/clients/js/test/plugins/asset/updateDelegate.test.ts +++ b/clients/js/test/plugins/asset/updateDelegate.test.ts @@ -15,7 +15,7 @@ import { assertAsset, createAsset, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can create an asset with updateDelegate', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/plugins/collection/addBlocker.test.ts b/clients/js/test/plugins/collection/addBlocker.test.ts index 1228d633..f5ef6f47 100644 --- a/clients/js/test/plugins/collection/addBlocker.test.ts +++ b/clients/js/test/plugins/collection/addBlocker.test.ts @@ -1,30 +1,23 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { - createPlugin, - addCollectionPluginV1, - pluginAuthorityPair, - addPluginV1, - updatePluginAuthority, -} from '../../../src'; import { DEFAULT_COLLECTION, assertCollection, - createAsset, - createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; +import { createAsset, createCollection } from '../../_setupSdk'; +import { addCollectionPlugin, addPlugin } from '../../../src'; test('it can add addBlocker to collection', async (t) => { const umi = await createUmi(); const collection = await createCollection(umi); - await addCollectionPluginV1(umi, { + await addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'AddBlocker', - }), + }, }).sendAndConfirm(umi); await assertCollection(t, umi, { @@ -43,17 +36,17 @@ test('it cannot add UA-managed plugin to a collection if addBlocker had been add const umi = await createUmi(); const collection = await createCollection(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'AddBlocker', - }), + }, ], }); - const result = addCollectionPluginV1(umi, { + const result = addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -65,21 +58,21 @@ test('it cannot add UA-managed plugin to an asset in a collection if addBlocker const umi = await createUmi(); const collection = await createCollection(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'AddBlocker', - }), + }, ], }); const asset = await createAsset(umi, { collection: collection.publicKey, }); - const result = addPluginV1(umi, { + const result = addPlugin(umi, { collection: collection.publicKey, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -92,10 +85,12 @@ test('it prevents plugins from being added to both collection and plugins when c const updateAuthority = generateSigner(umi); const collection = await createCollection(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'AddBlocker', - authority: updatePluginAuthority(), - }), + authority: { + type: 'UpdateAuthority', + }, + }, ], updateAuthority, }); @@ -104,23 +99,23 @@ test('it prevents plugins from being added to both collection and plugins when c authority: updateAuthority, }); - let result = addCollectionPluginV1(umi, { + let result = addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { name: 'InvalidAuthority', }); - result = addPluginV1(umi, { + result = addPlugin(umi, { collection: collection.publicKey, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -133,30 +128,30 @@ test('it prevents plugins from being added to both collection and plugins when A const collection = await createCollection(umi); const asset = await createAsset(umi, { collection: collection.publicKey }); - await addCollectionPluginV1(umi, { + await addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'AddBlocker', - }), + }, }).sendAndConfirm(umi); - let result = addCollectionPluginV1(umi, { + let result = addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { name: 'InvalidAuthority', }); - result = addPluginV1(umi, { + result = addPlugin(umi, { collection: collection.publicKey, asset: asset.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { diff --git a/clients/js/test/plugins/collection/immutableMetadata.test.ts b/clients/js/test/plugins/collection/immutableMetadata.test.ts index eb4a8d88..f58e810d 100644 --- a/clients/js/test/plugins/collection/immutableMetadata.test.ts +++ b/clients/js/test/plugins/collection/immutableMetadata.test.ts @@ -1,30 +1,23 @@ import test from 'ava'; import { generateSigner } from '@metaplex-foundation/umi'; -import { - createPlugin, - addCollectionPluginV1, - updateV1, - updateCollectionV1, - updatePluginAuthority, - pluginAuthorityPair, -} from '../../../src'; + import { DEFAULT_COLLECTION, assertCollection, - createAsset, - createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; +import { addCollectionPlugin, update, updateCollection } from '../../../src'; +import { createAsset, createCollection } from '../../_setupSdk'; test('it can add immutableMetadata to collection', async (t) => { const umi = await createUmi(); const collection = await createCollection(umi); - await addCollectionPluginV1(umi, { + await addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await assertCollection(t, umi, { @@ -43,22 +36,22 @@ test('it can prevent collection assets metadata from being updated', async (t) = const umi = await createUmi(); const collection = await createCollection(umi); - await addCollectionPluginV1(umi, { + await addCollectionPlugin(umi, { collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); const asset = await createAsset(umi, { collection: collection.publicKey, }); - const result = updateV1(umi, { - collection: collection.publicKey, - asset: asset.publicKey, - newName: 'Test Bread 3', - newUri: 'https://example.com/bread3', + const result = update(umi, { + collection, + asset, + name: 'Test Bread 3', + uri: 'https://example.com/bread3', }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -73,12 +66,12 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn const collection = await createCollection(umi, { updateAuthority }); // random keypair can't add ImmutableMetadata - let result = addCollectionPluginV1(umi, { + let result = addCollectionPlugin(umi, { authority: randomUser, collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -86,12 +79,12 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn }); // Payer for the the collection can't add ImmutableMetadata - result = addCollectionPluginV1(umi, { + result = addCollectionPlugin(umi, { authority: umi.identity, collection: collection.publicKey, - plugin: createPlugin({ + plugin: { type: 'ImmutableMetadata', - }), + }, }).sendAndConfirm(umi); await t.throwsAsync(result, { @@ -99,10 +92,10 @@ test('it states that UA is the only one who can add the ImmutableMetadata', asyn }); // UA CAN add ImmutableMetadata - await addCollectionPluginV1(umi, { + await addCollectionPlugin(umi, { authority: updateAuthority, collection: collection.publicKey, - plugin: createPlugin({ type: 'ImmutableMetadata' }), + plugin: { type: 'ImmutableMetadata' }, }).sendAndConfirm(umi); await assertCollection(t, umi, { @@ -121,10 +114,12 @@ test('it prevents both collection and asset from their meta updating when Immuta const updateAuthority = generateSigner(umi); const collection = await createCollection(umi, { plugins: [ - pluginAuthorityPair({ + { type: 'ImmutableMetadata', - authority: updatePluginAuthority(), - }), + authority: { + type: 'UpdateAuthority', + }, + }, ], updateAuthority, }); @@ -133,22 +128,22 @@ test('it prevents both collection and asset from their meta updating when Immuta authority: updateAuthority, }); - let result = updateV1(umi, { - collection: collection.publicKey, - asset: asset.publicKey, - newName: 'Test Bread 2', - newUri: 'https://example.com/bread2', + let result = update(umi, { + collection, + asset, + name: 'Test Bread 2', + uri: 'https://example.com/bread2', }).sendAndConfirm(umi); await t.throwsAsync(result, { name: 'InvalidAuthority', }); - result = updateCollectionV1(umi, { + result = updateCollection(umi, { authority: updateAuthority, collection: collection.publicKey, - newName: 'Test', - newUri: 'Test', + name: 'Test', + uri: 'Test', }).sendAndConfirm(umi); await t.throwsAsync(result, { diff --git a/clients/js/test/plugins/collection/masterEdition.test.ts b/clients/js/test/plugins/collection/masterEdition.test.ts index f99d676d..2544d47e 100644 --- a/clients/js/test/plugins/collection/masterEdition.test.ts +++ b/clients/js/test/plugins/collection/masterEdition.test.ts @@ -1,5 +1,5 @@ import test from 'ava'; -import { none, some } from '@metaplex-foundation/umi'; + import { pluginAuthorityPair, updatePluginAuthority, @@ -13,7 +13,7 @@ import { createAsset, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can add masterEdition to collection', async (t) => { const umi = await createUmi(); @@ -39,9 +39,9 @@ test('it can add masterEdition to collection', async (t) => { authority: { type: 'UpdateAuthority', }, - maxSupply: some(100), - name: some('name'), - uri: some('uri'), + maxSupply: 100, + name: 'name', + uri: 'uri', }, }); }); @@ -71,9 +71,9 @@ test('it can create collection with masterEdition', async (t) => { authority: { type: 'UpdateAuthority', }, - maxSupply: some(100), - name: some('name'), - uri: some('uri'), + maxSupply: 100, + name: 'name', + uri: 'uri', }, }); }); @@ -103,9 +103,9 @@ test('it can create master edition with default values', async (t) => { authority: { type: 'UpdateAuthority', }, - maxSupply: none(), - name: none(), - uri: none(), + maxSupply: undefined, + name: undefined, + uri: undefined, }, }); }); diff --git a/clients/js/test/plugins/collection/permanentBurn.test.ts b/clients/js/test/plugins/collection/permanentBurn.test.ts index 59c4789f..96e68b60 100644 --- a/clients/js/test/plugins/collection/permanentBurn.test.ts +++ b/clients/js/test/plugins/collection/permanentBurn.test.ts @@ -12,7 +12,7 @@ import { assertCollection, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can add permanentBurnDelegate to collection', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/plugins/collection/permanentFreeze.test.ts b/clients/js/test/plugins/collection/permanentFreeze.test.ts index 7439f631..8d2f32d0 100644 --- a/clients/js/test/plugins/collection/permanentFreeze.test.ts +++ b/clients/js/test/plugins/collection/permanentFreeze.test.ts @@ -12,7 +12,7 @@ import { assertCollection, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can add permanentFreezeDelegate to collection', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/plugins/collection/permanentTransfer.test.ts b/clients/js/test/plugins/collection/permanentTransfer.test.ts index f464d969..9f859b6a 100644 --- a/clients/js/test/plugins/collection/permanentTransfer.test.ts +++ b/clients/js/test/plugins/collection/permanentTransfer.test.ts @@ -13,7 +13,7 @@ import { createCollection, createUmi, DEFAULT_COLLECTION, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can add permanentTransferDelegate to collection', async (t) => { const umi = await createUmi(); diff --git a/clients/js/test/plugins/collection/updateDelegate.test.ts b/clients/js/test/plugins/collection/updateDelegate.test.ts index b7fed683..a4044136 100644 --- a/clients/js/test/plugins/collection/updateDelegate.test.ts +++ b/clients/js/test/plugins/collection/updateDelegate.test.ts @@ -20,7 +20,7 @@ import { createAsset, createCollection, createUmi, -} from '../../_setup'; +} from '../../_setupRaw'; test('it can create a new asset with a collection if it is the collection updateDelegate', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/removePlugin.test.ts b/clients/js/test/removePlugin.test.ts index 4bf5420d..1d0f4196 100644 --- a/clients/js/test/removePlugin.test.ts +++ b/clients/js/test/removePlugin.test.ts @@ -22,7 +22,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can remove a plugin from an asset', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/revokeAuthority.test.ts b/clients/js/test/revokeAuthority.test.ts index 650a3fb0..6be327ad 100644 --- a/clients/js/test/revokeAuthority.test.ts +++ b/clients/js/test/revokeAuthority.test.ts @@ -17,7 +17,7 @@ import { createAsset, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can remove an authority from a plugin', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/sdkv1.test.ts b/clients/js/test/sdkv1.test.ts new file mode 100644 index 00000000..33764222 --- /dev/null +++ b/clients/js/test/sdkv1.test.ts @@ -0,0 +1,1033 @@ +import { assertAccountExists, generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +import { + AssetAddablePluginAuthorityPairArgsV2, + addPlugin, + burn, + Key, + AssetAllPluginArgsV2, + removePlugin, + transfer, + update, + updateCollection, + updateCollectionPlugin, + updatePlugin, + CollectionAllPluginArgsV2, + CollectionAddablePluginAuthorityPairArgsV2, + addCollectionPlugin, + removeCollectionPlugin, +} from '../src'; +import { + createAsset, + createAssetWithCollection, + createCollection, +} from './_setupSdk'; +import { + assertAsset, + assertCollection, + createUmi, + DEFAULT_ASSET, + DEFAULT_COLLECTION, +} from './_setupRaw'; + +test('it can create asset and collection with all update auth managed party plugins', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + + const collection = await createCollection(umi, { + plugins: [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: owner.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [owner.publicKey], + }, + authority: { + type: 'Address', + address: owner.publicKey, + }, + }, + { + type: 'PermanentBurnDelegate', + authority: { + type: 'None', + }, + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + { + type: 'PermanentTransferDelegate', + authority: { + type: 'UpdateAuthority', + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + { + type: 'MasterEdition', + maxSupply: 100, + name: 'master', + uri: 'uri master', + }, + ], + }); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + royalties: { + basisPoints: 500, + creators: [ + { + address: owner.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [owner.publicKey], + }, + authority: { + type: 'Address', + address: owner.publicKey, + }, + }, + permanentBurnDelegate: { + authority: { + type: 'None', + }, + }, + permanentFreezeDelegate: { + frozen: false, + authority: { + type: 'UpdateAuthority', + }, + }, + permanentTransferDelegate: { + authority: { + type: 'UpdateAuthority', + }, + }, + attributes: { + attributeList: [ + { + key: '123', + value: '456', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + masterEdition: { + authority: { + type: 'UpdateAuthority', + }, + maxSupply: 100, + name: 'master', + uri: 'uri master', + }, + }); + + const asset = await createAsset(umi, { + owner, + collection, + plugins: [ + { + type: 'Edition', + authority: { + type: 'UpdateAuthority', + }, + number: 1, + }, + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: owner.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [owner.publicKey], + }, + authority: { + type: 'Address', + address: owner.publicKey, + }, + }, + { + type: 'PermanentBurnDelegate', + authority: { + type: 'None', + }, + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + { + type: 'PermanentTransferDelegate', + authority: { + type: 'UpdateAuthority', + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + ], + }); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { + type: 'Collection', + address: collection.publicKey, + }, + edition: { + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + royalties: { + basisPoints: 500, + creators: [ + { + address: owner.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [owner.publicKey], + }, + authority: { + type: 'Address', + address: owner.publicKey, + }, + }, + permanentBurnDelegate: { + authority: { + type: 'None', + }, + }, + permanentFreezeDelegate: { + frozen: false, + authority: { + type: 'UpdateAuthority', + }, + }, + permanentTransferDelegate: { + authority: { + type: 'UpdateAuthority', + }, + }, + attributes: { + attributeList: [ + { + key: '123', + value: '456', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can create all owner and update auth managed party plugins to asset', async (t) => { + const umi = await createUmi(); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + { + type: 'PermanentBurnDelegate', + authority: { + type: 'None', + }, + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + { + type: 'PermanentTransferDelegate', + authority: { + type: 'UpdateAuthority', + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + { + type: 'Edition', + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + { + type: 'FreezeDelegate', + frozen: false, + }, + { + type: 'BurnDelegate', + authority: { + type: 'None', + }, + }, + { + type: 'TransferDelegate', + authority: { + type: 'UpdateAuthority', + }, + }, + ], + }); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + royalties: { + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + permanentBurnDelegate: { + authority: { + type: 'None', + }, + }, + permanentFreezeDelegate: { + frozen: false, + authority: { + type: 'UpdateAuthority', + }, + }, + permanentTransferDelegate: { + authority: { + type: 'UpdateAuthority', + }, + }, + attributes: { + attributeList: [ + { + key: '123', + value: '456', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + edition: { + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + freezeDelegate: { + frozen: false, + authority: { + type: 'Owner', + }, + }, + burnDelegate: { + authority: { + type: 'None', + }, + }, + transferDelegate: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can add and remove all owner and update auth managed party plugins to asset', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi); + + const plugins: AssetAddablePluginAuthorityPairArgsV2[] = [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + { + type: 'FreezeDelegate', + frozen: false, + }, + { + type: 'BurnDelegate', + }, + { + type: 'TransferDelegate', + authority: { + type: 'UpdateAuthority', + }, + }, + ]; + + await Promise.all( + plugins.map(async (plugin) => + addPlugin(umi, { + asset: asset.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + royalties: { + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + attributes: { + attributeList: [ + { + key: '123', + value: '456', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + freezeDelegate: { + frozen: false, + authority: { + type: 'Owner', + }, + }, + burnDelegate: { + authority: { + type: 'Owner', + }, + }, + transferDelegate: { + authority: { + type: 'UpdateAuthority', + }, + }, + }); + + await Promise.all( + plugins.map(async (plugin) => + removePlugin(umi, { + asset: asset.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + attributes: undefined, + royalties: undefined, + freezeDelegate: undefined, + burnDelegate: undefined, + transferDelegate: undefined, + }); +}); + +test('it can update all updatable plugins on asset', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + { + type: 'FreezeDelegate', + frozen: false, + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + { + type: 'Edition', + number: 1, + }, + ], + }); + + const updates: AssetAllPluginArgsV2[] = [ + { + type: 'Royalties', + basisPoints: 1000, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramAllowList', + addresses: [umi.identity.publicKey], + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: 'abc', + value: 'xyz', + }, + ], + }, + { + type: 'FreezeDelegate', + frozen: true, + }, + { + type: 'PermanentFreezeDelegate', + frozen: true, + }, + { + type: 'Edition', + number: 2, + }, + ]; + + await Promise.all( + updates.map(async (plugin) => + updatePlugin(umi, { + asset: asset.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + royalties: { + basisPoints: 1000, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramAllowList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'UpdateAuthority', + }, + }, + attributes: { + attributeList: [ + { + key: 'abc', + value: 'xyz', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + freezeDelegate: { + frozen: true, + authority: { + type: 'Owner', + }, + }, + permanentFreezeDelegate: { + frozen: true, + authority: { + type: 'UpdateAuthority', + }, + }, + edition: { + number: 2, + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can update all updatable plugins on collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi, { + plugins: [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + { + type: 'PermanentFreezeDelegate', + frozen: false, + }, + { + type: 'MasterEdition', + maxSupply: 100, + name: 'master', + uri: 'uri master', + }, + ], + }); + + const updates: CollectionAllPluginArgsV2[] = [ + { + type: 'Royalties', + basisPoints: 1000, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramAllowList', + addresses: [umi.identity.publicKey], + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: 'abc', + value: 'xyz', + }, + ], + }, + { + type: 'PermanentFreezeDelegate', + frozen: true, + }, + { + type: 'MasterEdition', + maxSupply: 200, + name: 'master2', + uri: 'uri master2', + }, + ]; + + await Promise.all( + updates.map(async (plugin) => + updateCollectionPlugin(umi, { + collection: collection.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertCollection(t, umi, { + collection: collection.publicKey, + royalties: { + basisPoints: 1000, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramAllowList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'UpdateAuthority', + }, + }, + attributes: { + attributeList: [ + { + key: 'abc', + value: 'xyz', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + permanentFreezeDelegate: { + frozen: true, + authority: { + type: 'UpdateAuthority', + }, + }, + masterEdition: { + authority: { + type: 'UpdateAuthority', + }, + maxSupply: 200, + name: 'master2', + uri: 'uri master2', + }, + }); +}); + +test('it can add and remove all update auth managed party plugins to collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi); + + const plugins: CollectionAddablePluginAuthorityPairArgsV2[] = [ + { + type: 'Royalties', + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + { + type: 'Attributes', + attributeList: [ + { + key: '123', + value: '456', + }, + ], + }, + ]; + + await Promise.all( + plugins.map(async (plugin) => + addCollectionPlugin(umi, { + collection: collection.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertCollection(t, umi, { + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + royalties: { + basisPoints: 500, + creators: [ + { + address: umi.identity.publicKey, + percentage: 100, + }, + ], + ruleSet: { + type: 'ProgramDenyList', + addresses: [umi.identity.publicKey], + }, + authority: { + type: 'Address', + address: umi.identity.publicKey, + }, + }, + attributes: { + attributeList: [ + { + key: '123', + value: '456', + }, + ], + authority: { + type: 'UpdateAuthority', + }, + }, + }); + + await Promise.all( + plugins.map(async (plugin) => + removeCollectionPlugin(umi, { + collection: collection.publicKey, + plugin, + }).sendAndConfirm(umi) + ) + ); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + attributes: undefined, + royalties: undefined, + }); +}); + +test('it can transfer asset', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const newOwner = generateSigner(umi); + const asset = await createAsset(umi, { + owner, + plugins: [ + { + type: 'Edition', + number: 1, + }, + ], + }); + + await transfer(umi, { + asset, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: newOwner.publicKey, + edition: { + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can transfer asset in collection', async (t) => { + const umi = await createUmi(); + const owner = generateSigner(umi); + const newOwner = generateSigner(umi); + const { asset, collection } = await createAssetWithCollection(umi, { + owner, + plugins: [ + { + type: 'Edition', + number: 1, + }, + ], + }); + + await transfer(umi, { + asset, + collection, + newOwner: newOwner.publicKey, + authority: owner, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: newOwner.publicKey, + updateAuthority: { + type: 'Collection', + address: collection.publicKey, + }, + edition: { + number: 1, + authority: { + type: 'UpdateAuthority', + }, + }, + }); +}); + +test('it can update asset', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi, {}); + + await update(umi, { + asset, + name: 'bleh', + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + name: 'bleh', + }); +}); + +test('it can update collection', async (t) => { + const umi = await createUmi(); + const collection = await createCollection(umi, {}); + + await updateCollection(umi, { + collection: collection.publicKey, + name: 'bleh', + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + name: 'bleh', + }); +}); + +test('it can burn asset', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi, {}); + + await burn(umi, { + asset, + }).sendAndConfirm(umi); + + const afterAsset = await umi.rpc.getAccount(asset.publicKey); + t.true(afterAsset.exists); + assertAccountExists(afterAsset); + t.is(afterAsset.data.length, 1); + t.is(afterAsset.data[0], Key.Uninitialized); +}); + +test('it can burn asset in collection', async (t) => { + const umi = await createUmi(); + const { asset, collection } = await createAssetWithCollection(umi, {}); + + await burn(umi, { + asset, + collection, + }).sendAndConfirm(umi); + + const afterAsset = await umi.rpc.getAccount(asset.publicKey); + t.true(afterAsset.exists); + assertAccountExists(afterAsset); + t.is(afterAsset.data.length, 1); + t.is(afterAsset.data[0], Key.Uninitialized); +}); diff --git a/clients/js/test/signers/burn.test.ts b/clients/js/test/signers/burn.test.ts index dce4f0c5..abc016a4 100644 --- a/clients/js/test/signers/burn.test.ts +++ b/clients/js/test/signers/burn.test.ts @@ -6,7 +6,7 @@ import { } from '@metaplex-foundation/umi'; import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; import test from 'ava'; -import { assertAsset, createAsset } from '../_setup'; +import { assertAsset, createAsset } from '../_setupRaw'; import { Key, burnV1 } from '../../src'; test('it can burn an asset as the owner', async (t) => { diff --git a/clients/js/test/signers/create.test.ts b/clients/js/test/signers/create.test.ts index a9b63b08..e3d61351 100644 --- a/clients/js/test/signers/create.test.ts +++ b/clients/js/test/signers/create.test.ts @@ -5,7 +5,7 @@ import { } from '@metaplex-foundation/umi'; import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; import test from 'ava'; -import { DEFAULT_ASSET, assertAsset } from '../_setup'; +import { DEFAULT_ASSET, assertAsset } from '../_setupRaw'; import { createV1 } from '../../src'; test('it can create a new asset', async (t) => { diff --git a/clients/js/test/signers/createCollection.test.ts b/clients/js/test/signers/createCollection.test.ts index 1d6d4ff0..7d02ca6a 100644 --- a/clients/js/test/signers/createCollection.test.ts +++ b/clients/js/test/signers/createCollection.test.ts @@ -5,7 +5,7 @@ import { } from '@metaplex-foundation/umi'; import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; import test from 'ava'; -import { DEFAULT_COLLECTION, assertCollection } from '../_setup'; +import { DEFAULT_COLLECTION, assertCollection } from '../_setupRaw'; import { createCollectionV1 } from '../../src'; test('it can create a new asset', async (t) => { diff --git a/clients/js/test/signers/transfer.test.ts b/clients/js/test/signers/transfer.test.ts index af7f87ad..f81cb64e 100644 --- a/clients/js/test/signers/transfer.test.ts +++ b/clients/js/test/signers/transfer.test.ts @@ -5,7 +5,7 @@ import { } from '@metaplex-foundation/umi'; import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; import test from 'ava'; -import { assertAsset, createAsset } from '../_setup'; +import { assertAsset, createAsset } from '../_setupRaw'; import { transferV1 } from '../../src'; test('it can transfer an asset as the owner', async (t) => { diff --git a/clients/js/test/signers/update.test.ts b/clients/js/test/signers/update.test.ts index 07c34ab8..319b9488 100644 --- a/clients/js/test/signers/update.test.ts +++ b/clients/js/test/signers/update.test.ts @@ -6,7 +6,7 @@ import { } from '@metaplex-foundation/umi'; import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; import test from 'ava'; -import { DEFAULT_ASSET, assertAsset, createAsset } from '../_setup'; +import { DEFAULT_ASSET, assertAsset, createAsset } from '../_setupRaw'; import { updateV1 } from '../../src'; test('it can update an asset as the update authority', async (t) => { diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index ecb3cfd8..02588e1f 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -8,7 +8,7 @@ import { createAssetWithCollection, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it can transfer an asset as the owner', async (t) => { // Given a Umi instance and a new signer. @@ -148,7 +148,7 @@ test('authorities on owner-managed plugins are reset on transfer', async (t) => type: 'Address', address: freezeDelegate.publicKey, }, - offset: BigInt(119), + offset: 119n, frozen: false, }, }); @@ -166,7 +166,7 @@ test('authorities on owner-managed plugins are reset on transfer', async (t) => authority: { type: 'Owner', }, - offset: BigInt(119), + offset: 119n, frozen: false, }, }); @@ -199,7 +199,7 @@ test('authorities on permanent plugins should not be reset on transfer', async ( type: 'Address', address: freezeDelegate.publicKey, }, - offset: BigInt(119), + offset: 119n, frozen: false, }, }); @@ -218,7 +218,7 @@ test('authorities on permanent plugins should not be reset on transfer', async ( type: 'Address', address: freezeDelegate.publicKey, }, - offset: BigInt(119), + offset: 119n, frozen: false, }, }); @@ -249,7 +249,7 @@ test('authorities on authority-managed plugin should not be reset on transfer', type: 'Address', address: delegate.publicKey, }, - offset: BigInt(119), + offset: 119n, attributeList: [], }, }); @@ -268,7 +268,7 @@ test('authorities on authority-managed plugin should not be reset on transfer', type: 'Address', address: delegate.publicKey, }, - offset: BigInt(119), + offset: 119n, attributeList: [], }, }); diff --git a/clients/js/test/update.test.ts b/clients/js/test/update.test.ts index 27f6aec3..506d823b 100644 --- a/clients/js/test/update.test.ts +++ b/clients/js/test/update.test.ts @@ -14,7 +14,7 @@ import { createCollection, createUmi, DEFAULT_ASSET, -} from './_setup'; +} from './_setupRaw'; test('it can update an asset to be larger', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/js/test/updatePlugin.test.ts b/clients/js/test/updatePlugin.test.ts index 7191964d..267b84a5 100644 --- a/clients/js/test/updatePlugin.test.ts +++ b/clients/js/test/updatePlugin.test.ts @@ -16,7 +16,7 @@ import { createAsset, createCollection, createUmi, -} from './_setup'; +} from './_setupRaw'; test('it cannot use an invalid system program for assets', async (t) => { // Given a Umi instance and a new signer. diff --git a/clients/rust/src/generated/accounts/plugin_registry_v1.rs b/clients/rust/src/generated/accounts/plugin_registry_v1.rs index dee2a2b6..89ecb74e 100644 --- a/clients/rust/src/generated/accounts/plugin_registry_v1.rs +++ b/clients/rust/src/generated/accounts/plugin_registry_v1.rs @@ -5,7 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::ExternalPluginRecord; +use crate::generated::types::ExternalRegistryRecord; use crate::generated::types::Key; use crate::generated::types::RegistryRecord; #[cfg(feature = "anchor")] @@ -20,7 +20,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub struct PluginRegistryV1 { pub key: Key, pub registry: Vec, - pub external_plugins: Vec, + pub external_registry: Vec, } impl PluginRegistryV1 { diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index 12a8d9b9..e079f3f4 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -103,6 +103,33 @@ pub enum MplCoreError { /// 30 (0x1E) - Invalid Log Wrapper Program #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, + /// 31 (0x1F) - External PluginExternalPluginAdapter not found + #[error("External PluginExternalPluginAdapter not found")] + ExternalPluginAdapterNotFound, + /// 32 (0x20) - External PluginExternalPluginAdapter already exists + #[error("External PluginExternalPluginAdapter already exists")] + ExternalPluginAdapterAlreadyExists, + /// 33 (0x21) - Missing asset needed for extra account PDA derivation + #[error("Missing asset needed for extra account PDA derivation")] + MissingAsset, + /// 34 (0x22) - Missing account needed for external plugin adapter + #[error("Missing account needed for external plugin adapter")] + MissingExternalPluginAdapterAccount, + /// 35 (0x23) - Oracle external plugin adapter can only be configured to reject + #[error("Oracle external plugin adapter can only be configured to reject")] + OracleCanRejectOnly, + /// 36 (0x24) - External plugin adapter must have at least one lifecycle check + #[error("External plugin adapter must have at least one lifecycle check")] + RequiresLifecycleCheck, + /// 37 (0x25) - Duplicate lifecycle checks were provided for external plugin adapter + #[error("Duplicate lifecycle checks were provided for external plugin adapter ")] + DuplicateLifecycleChecks, + /// 38 (0x26) - Could not read from oracle account + #[error("Could not read from oracle account")] + InvalidOracleAccountData, + /// 39 (0x27) - Oracle account is uninitialized + #[error("Oracle account is uninitialized")] + UninitializedOracleAccount, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/clients/rust/src/generated/instructions/add_collection_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/add_collection_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..117d693c --- /dev/null +++ b/clients/rust/src/generated/instructions/add_collection_external_plugin_adapter_v1.rs @@ -0,0 +1,530 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterInitInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddCollectionExternalPluginAdapterV1 { + /// The address of the asset + pub collection: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl AddCollectionExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: AddCollectionExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: AddCollectionExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = AddCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddCollectionExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl AddCollectionExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 23 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddCollectionExternalPluginAdapterV1InstructionArgs { + pub init_info: ExternalPluginAdapterInitInfo, +} + +/// Instruction builder for `AddCollectionExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct AddCollectionExternalPluginAdapterV1Builder { + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + init_info: Option, + __remaining_accounts: Vec, +} + +impl AddCollectionExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn collection(&mut self, collection: solana_program::pubkey::Pubkey) -> &mut Self { + self.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.init_info = Some(init_info); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = AddCollectionExternalPluginAdapterV1 { + collection: self.collection.expect("collection is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = AddCollectionExternalPluginAdapterV1InstructionArgs { + init_info: self.init_info.clone().expect("init_info is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `add_collection_external_plugin_adapter_v1` CPI accounts. +pub struct AddCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `add_collection_external_plugin_adapter_v1` CPI instruction. +pub struct AddCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: AddCollectionExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> AddCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: AddCollectionExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = AddCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.collection.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `AddCollectionExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct AddCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddCollectionExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + init_info: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn collection( + &mut self, + collection: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.instruction.init_info = Some(init_info); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = AddCollectionExternalPluginAdapterV1InstructionArgs { + init_info: self + .instruction + .init_info + .clone() + .expect("init_info is not set"), + }; + let instruction = AddCollectionExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + collection: self.instruction.collection.expect("collection is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct AddCollectionExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + init_info: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/add_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/add_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..58f773a9 --- /dev/null +++ b/clients/rust/src/generated/instructions/add_external_plugin_adapter_v1.rs @@ -0,0 +1,582 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterInitInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct AddExternalPluginAdapterV1 { + /// The address of the asset + pub asset: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl AddExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: AddExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: AddExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset, false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = AddExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct AddExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl AddExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 22 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddExternalPluginAdapterV1InstructionArgs { + pub init_info: ExternalPluginAdapterInitInfo, +} + +/// Instruction builder for `AddExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper +#[derive(Default)] +pub struct AddExternalPluginAdapterV1Builder { + asset: Option, + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + init_info: Option, + __remaining_accounts: Vec, +} + +impl AddExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.init_info = Some(init_info); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = AddExternalPluginAdapterV1 { + asset: self.asset.expect("asset is not set"), + collection: self.collection, + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = AddExternalPluginAdapterV1InstructionArgs { + init_info: self.init_info.clone().expect("init_info is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `add_external_plugin_adapter_v1` CPI accounts. +pub struct AddExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `add_external_plugin_adapter_v1` CPI instruction. +pub struct AddExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: AddExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> AddExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: AddExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: AddExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + asset: accounts.asset, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = AddExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `AddExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper +pub struct AddExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> AddExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(AddExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + asset: None, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + init_info: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn init_info(&mut self, init_info: ExternalPluginAdapterInitInfo) -> &mut Self { + self.instruction.init_info = Some(init_info); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = AddExternalPluginAdapterV1InstructionArgs { + init_info: self + .instruction + .init_info + .clone() + .expect("init_info is not set"), + }; + let instruction = AddExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + asset: self.instruction.asset.expect("asset is not set"), + + collection: self.instruction.collection, + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct AddExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + init_info: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/burn_collection_v1.rs b/clients/rust/src/generated/instructions/burn_collection_v1.rs index e749e05a..95450532 100644 --- a/clients/rust/src/generated/instructions/burn_collection_v1.rs +++ b/clients/rust/src/generated/instructions/burn_collection_v1.rs @@ -45,7 +45,7 @@ impl BurnCollectionV1 { self.payer, true, )); if let Some(authority) = self.authority { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( + accounts.push(solana_program::instruction::AccountMeta::new( authority, true, )); } else { @@ -104,7 +104,7 @@ pub struct BurnCollectionV1InstructionArgs { /// /// 0. `[writable]` collection /// 1. `[writable, signer]` payer -/// 2. `[signer, optional]` authority +/// 2. `[writable, signer, optional]` authority /// 3. `[optional]` log_wrapper #[derive(Default)] pub struct BurnCollectionV1Builder { @@ -275,7 +275,7 @@ impl<'a, 'b> BurnCollectionV1Cpi<'a, 'b> { true, )); if let Some(authority) = self.authority { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( + accounts.push(solana_program::instruction::AccountMeta::new( *authority.key, true, )); @@ -340,7 +340,7 @@ impl<'a, 'b> BurnCollectionV1Cpi<'a, 'b> { /// /// 0. `[writable]` collection /// 1. `[writable, signer]` payer -/// 2. `[signer, optional]` authority +/// 2. `[writable, signer, optional]` authority /// 3. `[optional]` log_wrapper pub struct BurnCollectionV1CpiBuilder<'a, 'b> { instruction: Box>, diff --git a/clients/rust/src/generated/instructions/burn_v1.rs b/clients/rust/src/generated/instructions/burn_v1.rs index a58d762b..ca904d32 100644 --- a/clients/rust/src/generated/instructions/burn_v1.rs +++ b/clients/rust/src/generated/instructions/burn_v1.rs @@ -58,7 +58,7 @@ impl BurnV1 { self.payer, true, )); if let Some(authority) = self.authority { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( + accounts.push(solana_program::instruction::AccountMeta::new( authority, true, )); } else { @@ -129,7 +129,7 @@ pub struct BurnV1InstructionArgs { /// 0. `[writable]` asset /// 1. `[writable, optional]` collection /// 2. `[writable, signer]` payer -/// 3. `[signer, optional]` authority +/// 3. `[writable, signer, optional]` authority /// 4. `[optional]` system_program /// 5. `[optional]` log_wrapper #[derive(Default)] @@ -343,7 +343,7 @@ impl<'a, 'b> BurnV1Cpi<'a, 'b> { true, )); if let Some(authority) = self.authority { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( + accounts.push(solana_program::instruction::AccountMeta::new( *authority.key, true, )); @@ -426,7 +426,7 @@ impl<'a, 'b> BurnV1Cpi<'a, 'b> { /// 0. `[writable]` asset /// 1. `[writable, optional]` collection /// 2. `[writable, signer]` payer -/// 3. `[signer, optional]` authority +/// 3. `[writable, signer, optional]` authority /// 4. `[optional]` system_program /// 5. `[optional]` log_wrapper pub struct BurnV1CpiBuilder<'a, 'b> { diff --git a/clients/rust/src/generated/instructions/create_collection_v2.rs b/clients/rust/src/generated/instructions/create_collection_v2.rs new file mode 100644 index 00000000..49778729 --- /dev/null +++ b/clients/rust/src/generated/instructions/create_collection_v2.rs @@ -0,0 +1,529 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterInitInfo; +use crate::generated::types::PluginAuthorityPair; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct CreateCollectionV2 { + /// The address of the new asset + pub collection: solana_program::pubkey::Pubkey, + /// The authority of the new asset + pub update_authority: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, +} + +impl CreateCollectionV2 { + pub fn instruction( + &self, + args: CreateCollectionV2InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: CreateCollectionV2InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection, + true, + )); + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + update_authority, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = CreateCollectionV2InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct CreateCollectionV2InstructionData { + discriminator: u8, +} + +impl CreateCollectionV2InstructionData { + pub fn new() -> Self { + Self { discriminator: 21 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CreateCollectionV2InstructionArgs { + pub name: String, + pub uri: String, + pub plugins: Option>, + pub external_plugin_adapters: Option>, +} + +/// Instruction builder for `CreateCollectionV2`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` collection +/// 1. `[optional]` update_authority +/// 2. `[writable, signer]` payer +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Default)] +pub struct CreateCollectionV2Builder { + collection: Option, + update_authority: Option, + payer: Option, + system_program: Option, + name: Option, + uri: Option, + plugins: Option>, + external_plugin_adapters: Option>, + __remaining_accounts: Vec, +} + +impl CreateCollectionV2Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the new asset + #[inline(always)] + pub fn collection(&mut self, collection: solana_program::pubkey::Pubkey) -> &mut Self { + self.collection = Some(collection); + self + } + /// `[optional account]` + /// The authority of the new asset + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option, + ) -> &mut Self { + self.update_authority = update_authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.uri = Some(uri); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn plugins(&mut self, plugins: Vec) -> &mut Self { + self.plugins = Some(plugins); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn external_plugin_adapters( + &mut self, + external_plugin_adapters: Vec, + ) -> &mut Self { + self.external_plugin_adapters = Some(external_plugin_adapters); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = CreateCollectionV2 { + collection: self.collection.expect("collection is not set"), + update_authority: self.update_authority, + payer: self.payer.expect("payer is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = CreateCollectionV2InstructionArgs { + name: self.name.clone().expect("name is not set"), + uri: self.uri.clone().expect("uri is not set"), + plugins: self.plugins.clone(), + external_plugin_adapters: self.external_plugin_adapters.clone(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `create_collection_v2` CPI accounts. +pub struct CreateCollectionV2CpiAccounts<'a, 'b> { + /// The address of the new asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new asset + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `create_collection_v2` CPI instruction. +pub struct CreateCollectionV2Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the new asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new asset + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: CreateCollectionV2InstructionArgs, +} + +impl<'a, 'b> CreateCollectionV2Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: CreateCollectionV2CpiAccounts<'a, 'b>, + args: CreateCollectionV2InstructionArgs, + ) -> Self { + Self { + __program: program, + collection: accounts.collection, + update_authority: accounts.update_authority, + payer: accounts.payer, + system_program: accounts.system_program, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection.key, + true, + )); + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *update_authority.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = CreateCollectionV2InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.collection.clone()); + if let Some(update_authority) = self.update_authority { + account_infos.push(update_authority.clone()); + } + account_infos.push(self.payer.clone()); + account_infos.push(self.system_program.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `CreateCollectionV2` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` collection +/// 1. `[optional]` update_authority +/// 2. `[writable, signer]` payer +/// 3. `[]` system_program +pub struct CreateCollectionV2CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> CreateCollectionV2CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(CreateCollectionV2CpiBuilderInstruction { + __program: program, + collection: None, + update_authority: None, + payer: None, + system_program: None, + name: None, + uri: None, + plugins: None, + external_plugin_adapters: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the new asset + #[inline(always)] + pub fn collection( + &mut self, + collection: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection = Some(collection); + self + } + /// `[optional account]` + /// The authority of the new asset + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.update_authority = update_authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.instruction.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.instruction.uri = Some(uri); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn plugins(&mut self, plugins: Vec) -> &mut Self { + self.instruction.plugins = Some(plugins); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn external_plugin_adapters( + &mut self, + external_plugin_adapters: Vec, + ) -> &mut Self { + self.instruction.external_plugin_adapters = Some(external_plugin_adapters); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = CreateCollectionV2InstructionArgs { + name: self.instruction.name.clone().expect("name is not set"), + uri: self.instruction.uri.clone().expect("uri is not set"), + plugins: self.instruction.plugins.clone(), + external_plugin_adapters: self.instruction.external_plugin_adapters.clone(), + }; + let instruction = CreateCollectionV2Cpi { + __program: self.instruction.__program, + + collection: self.instruction.collection.expect("collection is not set"), + + update_authority: self.instruction.update_authority, + + payer: self.instruction.payer.expect("payer is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct CreateCollectionV2CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + name: Option, + uri: Option, + plugins: Option>, + external_plugin_adapters: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/create_v2.rs b/clients/rust/src/generated/instructions/create_v2.rs new file mode 100644 index 00000000..d5b91499 --- /dev/null +++ b/clients/rust/src/generated/instructions/create_v2.rs @@ -0,0 +1,771 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::DataState; +use crate::generated::types::ExternalPluginAdapterInitInfo; +use crate::generated::types::PluginAuthorityPair; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct CreateV2 { + /// The address of the new asset + pub asset: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The authority signing for creation + pub authority: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner of the new asset. Defaults to the authority if not present. + pub owner: Option, + /// The authority on the new asset + pub update_authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl CreateV2 { + pub fn instruction( + &self, + args: CreateV2InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: CreateV2InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(8 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset, true, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(owner) = self.owner { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + owner, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + update_authority, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = CreateV2InstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct CreateV2InstructionData { + discriminator: u8, +} + +impl CreateV2InstructionData { + pub fn new() -> Self { + Self { discriminator: 20 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CreateV2InstructionArgs { + pub data_state: DataState, + pub name: String, + pub uri: String, + pub plugins: Option>, + pub external_plugin_adapters: Option>, +} + +/// Instruction builder for `CreateV2`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` asset +/// 1. `[writable, optional]` collection +/// 2. `[signer, optional]` authority +/// 3. `[writable, signer]` payer +/// 4. `[optional]` owner +/// 5. `[optional]` update_authority +/// 6. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 7. `[optional]` log_wrapper +#[derive(Default)] +pub struct CreateV2Builder { + asset: Option, + collection: Option, + authority: Option, + payer: Option, + owner: Option, + update_authority: Option, + system_program: Option, + log_wrapper: Option, + data_state: Option, + name: Option, + uri: Option, + plugins: Option>, + external_plugin_adapters: Option>, + __remaining_accounts: Vec, +} + +impl CreateV2Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the new asset + #[inline(always)] + pub fn asset(&mut self, asset: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// `[optional account]` + /// The authority signing for creation + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner of the new asset. Defaults to the authority if not present. + #[inline(always)] + pub fn owner(&mut self, owner: Option) -> &mut Self { + self.owner = owner; + self + } + /// `[optional account]` + /// The authority on the new asset + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option, + ) -> &mut Self { + self.update_authority = update_authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// `[optional argument, defaults to 'DataState::AccountState']` + #[inline(always)] + pub fn data_state(&mut self, data_state: DataState) -> &mut Self { + self.data_state = Some(data_state); + self + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.uri = Some(uri); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn plugins(&mut self, plugins: Vec) -> &mut Self { + self.plugins = Some(plugins); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn external_plugin_adapters( + &mut self, + external_plugin_adapters: Vec, + ) -> &mut Self { + self.external_plugin_adapters = Some(external_plugin_adapters); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = CreateV2 { + asset: self.asset.expect("asset is not set"), + collection: self.collection, + authority: self.authority, + payer: self.payer.expect("payer is not set"), + owner: self.owner, + update_authority: self.update_authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = CreateV2InstructionArgs { + data_state: self.data_state.clone().unwrap_or(DataState::AccountState), + name: self.name.clone().expect("name is not set"), + uri: self.uri.clone().expect("uri is not set"), + plugins: self.plugins.clone(), + external_plugin_adapters: self.external_plugin_adapters.clone(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `create_v2` CPI accounts. +pub struct CreateV2CpiAccounts<'a, 'b> { + /// The address of the new asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The authority signing for creation + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner of the new asset. Defaults to the authority if not present. + pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The authority on the new asset + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `create_v2` CPI instruction. +pub struct CreateV2Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the new asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The authority signing for creation + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner of the new asset. Defaults to the authority if not present. + pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The authority on the new asset + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: CreateV2InstructionArgs, +} + +impl<'a, 'b> CreateV2Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: CreateV2CpiAccounts<'a, 'b>, + args: CreateV2InstructionArgs, + ) -> Self { + Self { + __program: program, + asset: accounts.asset, + collection: accounts.collection, + authority: accounts.authority, + payer: accounts.payer, + owner: accounts.owner, + update_authority: accounts.update_authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(8 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset.key, + true, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(owner) = self.owner { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *owner.key, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + if let Some(update_authority) = self.update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *update_authority.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = CreateV2InstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(8 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(owner) = self.owner { + account_infos.push(owner.clone()); + } + if let Some(update_authority) = self.update_authority { + account_infos.push(update_authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `CreateV2` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` asset +/// 1. `[writable, optional]` collection +/// 2. `[signer, optional]` authority +/// 3. `[writable, signer]` payer +/// 4. `[optional]` owner +/// 5. `[optional]` update_authority +/// 6. `[]` system_program +/// 7. `[optional]` log_wrapper +pub struct CreateV2CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> CreateV2CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(CreateV2CpiBuilderInstruction { + __program: program, + asset: None, + collection: None, + authority: None, + payer: None, + owner: None, + update_authority: None, + system_program: None, + log_wrapper: None, + data_state: None, + name: None, + uri: None, + plugins: None, + external_plugin_adapters: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the new asset + #[inline(always)] + pub fn asset(&mut self, asset: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// `[optional account]` + /// The authority signing for creation + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner of the new asset. Defaults to the authority if not present. + #[inline(always)] + pub fn owner( + &mut self, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.owner = owner; + self + } + /// `[optional account]` + /// The authority on the new asset + #[inline(always)] + pub fn update_authority( + &mut self, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.update_authority = update_authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// `[optional argument, defaults to 'DataState::AccountState']` + #[inline(always)] + pub fn data_state(&mut self, data_state: DataState) -> &mut Self { + self.instruction.data_state = Some(data_state); + self + } + #[inline(always)] + pub fn name(&mut self, name: String) -> &mut Self { + self.instruction.name = Some(name); + self + } + #[inline(always)] + pub fn uri(&mut self, uri: String) -> &mut Self { + self.instruction.uri = Some(uri); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn plugins(&mut self, plugins: Vec) -> &mut Self { + self.instruction.plugins = Some(plugins); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn external_plugin_adapters( + &mut self, + external_plugin_adapters: Vec, + ) -> &mut Self { + self.instruction.external_plugin_adapters = Some(external_plugin_adapters); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = CreateV2InstructionArgs { + data_state: self + .instruction + .data_state + .clone() + .unwrap_or(DataState::AccountState), + name: self.instruction.name.clone().expect("name is not set"), + uri: self.instruction.uri.clone().expect("uri is not set"), + plugins: self.instruction.plugins.clone(), + external_plugin_adapters: self.instruction.external_plugin_adapters.clone(), + }; + let instruction = CreateV2Cpi { + __program: self.instruction.__program, + + asset: self.instruction.asset.expect("asset is not set"), + + collection: self.instruction.collection, + + authority: self.instruction.authority, + + payer: self.instruction.payer.expect("payer is not set"), + + owner: self.instruction.owner, + + update_authority: self.instruction.update_authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct CreateV2CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + data_state: Option, + name: Option, + uri: Option, + plugins: Option>, + external_plugin_adapters: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index 0f9a14bd..116673db 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -5,7 +5,9 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod r#add_collection_external_plugin_adapter_v1; pub(crate) mod r#add_collection_plugin_v1; +pub(crate) mod r#add_external_plugin_adapter_v1; pub(crate) mod r#add_plugin_v1; pub(crate) mod r#approve_collection_plugin_authority_v1; pub(crate) mod r#approve_plugin_authority_v1; @@ -14,19 +16,29 @@ pub(crate) mod r#burn_v1; pub(crate) mod r#collect; pub(crate) mod r#compress_v1; pub(crate) mod r#create_collection_v1; +pub(crate) mod r#create_collection_v2; pub(crate) mod r#create_v1; +pub(crate) mod r#create_v2; pub(crate) mod r#decompress_v1; +pub(crate) mod r#remove_collection_external_plugin_adapter_v1; pub(crate) mod r#remove_collection_plugin_v1; +pub(crate) mod r#remove_external_plugin_adapter_v1; pub(crate) mod r#remove_plugin_v1; pub(crate) mod r#revoke_collection_plugin_authority_v1; pub(crate) mod r#revoke_plugin_authority_v1; pub(crate) mod r#transfer_v1; +pub(crate) mod r#update_collection_external_plugin_adapter_v1; pub(crate) mod r#update_collection_plugin_v1; pub(crate) mod r#update_collection_v1; +pub(crate) mod r#update_external_plugin_adapter_v1; pub(crate) mod r#update_plugin_v1; pub(crate) mod r#update_v1; +pub(crate) mod r#write_collection_external_plugin_adapter_data_v1; +pub(crate) mod r#write_external_plugin_adapter_data_v1; +pub use self::r#add_collection_external_plugin_adapter_v1::*; pub use self::r#add_collection_plugin_v1::*; +pub use self::r#add_external_plugin_adapter_v1::*; pub use self::r#add_plugin_v1::*; pub use self::r#approve_collection_plugin_authority_v1::*; pub use self::r#approve_plugin_authority_v1::*; @@ -35,14 +47,22 @@ pub use self::r#burn_v1::*; pub use self::r#collect::*; pub use self::r#compress_v1::*; pub use self::r#create_collection_v1::*; +pub use self::r#create_collection_v2::*; pub use self::r#create_v1::*; +pub use self::r#create_v2::*; pub use self::r#decompress_v1::*; +pub use self::r#remove_collection_external_plugin_adapter_v1::*; pub use self::r#remove_collection_plugin_v1::*; +pub use self::r#remove_external_plugin_adapter_v1::*; pub use self::r#remove_plugin_v1::*; pub use self::r#revoke_collection_plugin_authority_v1::*; pub use self::r#revoke_plugin_authority_v1::*; pub use self::r#transfer_v1::*; +pub use self::r#update_collection_external_plugin_adapter_v1::*; pub use self::r#update_collection_plugin_v1::*; pub use self::r#update_collection_v1::*; +pub use self::r#update_external_plugin_adapter_v1::*; pub use self::r#update_plugin_v1::*; pub use self::r#update_v1::*; +pub use self::r#write_collection_external_plugin_adapter_data_v1::*; +pub use self::r#write_external_plugin_adapter_data_v1::*; diff --git a/clients/rust/src/generated/instructions/remove_collection_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/remove_collection_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..5d2fb72d --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_collection_external_plugin_adapter_v1.rs @@ -0,0 +1,528 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct RemoveCollectionExternalPluginAdapterV1 { + /// The address of the asset + pub collection: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl RemoveCollectionExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: RemoveCollectionExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveCollectionExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveCollectionExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl RemoveCollectionExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 25 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveCollectionExternalPluginAdapterV1InstructionArgs { + pub key: ExternalPluginAdapterKey, +} + +/// Instruction builder for `RemoveCollectionExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct RemoveCollectionExternalPluginAdapterV1Builder { + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + __remaining_accounts: Vec, +} + +impl RemoveCollectionExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn collection(&mut self, collection: solana_program::pubkey::Pubkey) -> &mut Self { + self.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = RemoveCollectionExternalPluginAdapterV1 { + collection: self.collection.expect("collection is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = RemoveCollectionExternalPluginAdapterV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_collection_external_plugin_adapter_v1` CPI accounts. +pub struct RemoveCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `remove_collection_external_plugin_adapter_v1` CPI instruction. +pub struct RemoveCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: RemoveCollectionExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> RemoveCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: RemoveCollectionExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = RemoveCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.collection.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `RemoveCollectionExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct RemoveCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new( + RemoveCollectionExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + __remaining_accounts: Vec::new(), + }, + ); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn collection( + &mut self, + collection: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = RemoveCollectionExternalPluginAdapterV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + }; + let instruction = RemoveCollectionExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + collection: self.instruction.collection.expect("collection is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveCollectionExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/remove_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/remove_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..4e027b85 --- /dev/null +++ b/clients/rust/src/generated/instructions/remove_external_plugin_adapter_v1.rs @@ -0,0 +1,578 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct RemoveExternalPluginAdapterV1 { + /// The address of the asset + pub asset: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl RemoveExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: RemoveExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: RemoveExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset, false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = RemoveExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct RemoveExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl RemoveExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 24 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoveExternalPluginAdapterV1InstructionArgs { + pub key: ExternalPluginAdapterKey, +} + +/// Instruction builder for `RemoveExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper +#[derive(Default)] +pub struct RemoveExternalPluginAdapterV1Builder { + asset: Option, + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + __remaining_accounts: Vec, +} + +impl RemoveExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = RemoveExternalPluginAdapterV1 { + asset: self.asset.expect("asset is not set"), + collection: self.collection, + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = RemoveExternalPluginAdapterV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `remove_external_plugin_adapter_v1` CPI accounts. +pub struct RemoveExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `remove_external_plugin_adapter_v1` CPI instruction. +pub struct RemoveExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: RemoveExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> RemoveExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RemoveExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: RemoveExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + asset: accounts.asset, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = RemoveExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `RemoveExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper +pub struct RemoveExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RemoveExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RemoveExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + asset: None, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = RemoveExternalPluginAdapterV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + }; + let instruction = RemoveExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + asset: self.instruction.asset.expect("asset is not set"), + + collection: self.instruction.collection, + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct RemoveExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/update_collection_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/update_collection_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..bc396088 --- /dev/null +++ b/clients/rust/src/generated/instructions/update_collection_external_plugin_adapter_v1.rs @@ -0,0 +1,549 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +use crate::generated::types::ExternalPluginAdapterUpdateInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct UpdateCollectionExternalPluginAdapterV1 { + /// The address of the asset + pub collection: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl UpdateCollectionExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: UpdateCollectionExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: UpdateCollectionExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = UpdateCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct UpdateCollectionExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl UpdateCollectionExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 27 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UpdateCollectionExternalPluginAdapterV1InstructionArgs { + pub key: ExternalPluginAdapterKey, + pub update_info: ExternalPluginAdapterUpdateInfo, +} + +/// Instruction builder for `UpdateCollectionExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct UpdateCollectionExternalPluginAdapterV1Builder { + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + update_info: Option, + __remaining_accounts: Vec, +} + +impl UpdateCollectionExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn collection(&mut self, collection: solana_program::pubkey::Pubkey) -> &mut Self { + self.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + #[inline(always)] + pub fn update_info(&mut self, update_info: ExternalPluginAdapterUpdateInfo) -> &mut Self { + self.update_info = Some(update_info); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = UpdateCollectionExternalPluginAdapterV1 { + collection: self.collection.expect("collection is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = UpdateCollectionExternalPluginAdapterV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + update_info: self.update_info.clone().expect("update_info is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `update_collection_external_plugin_adapter_v1` CPI accounts. +pub struct UpdateCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `update_collection_external_plugin_adapter_v1` CPI instruction. +pub struct UpdateCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: UpdateCollectionExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> UpdateCollectionExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: UpdateCollectionExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: UpdateCollectionExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = UpdateCollectionExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.collection.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `UpdateCollectionExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct UpdateCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> UpdateCollectionExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new( + UpdateCollectionExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + update_info: None, + __remaining_accounts: Vec::new(), + }, + ); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn collection( + &mut self, + collection: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + #[inline(always)] + pub fn update_info(&mut self, update_info: ExternalPluginAdapterUpdateInfo) -> &mut Self { + self.instruction.update_info = Some(update_info); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = UpdateCollectionExternalPluginAdapterV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + update_info: self + .instruction + .update_info + .clone() + .expect("update_info is not set"), + }; + let instruction = UpdateCollectionExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + collection: self.instruction.collection.expect("collection is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct UpdateCollectionExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + update_info: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/update_external_plugin_adapter_v1.rs b/clients/rust/src/generated/instructions/update_external_plugin_adapter_v1.rs new file mode 100644 index 00000000..d55b2934 --- /dev/null +++ b/clients/rust/src/generated/instructions/update_external_plugin_adapter_v1.rs @@ -0,0 +1,599 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +use crate::generated::types::ExternalPluginAdapterUpdateInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct UpdateExternalPluginAdapterV1 { + /// The address of the asset + pub asset: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl UpdateExternalPluginAdapterV1 { + pub fn instruction( + &self, + args: UpdateExternalPluginAdapterV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: UpdateExternalPluginAdapterV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset, false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = UpdateExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct UpdateExternalPluginAdapterV1InstructionData { + discriminator: u8, +} + +impl UpdateExternalPluginAdapterV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 26 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UpdateExternalPluginAdapterV1InstructionArgs { + pub key: ExternalPluginAdapterKey, + pub update_info: ExternalPluginAdapterUpdateInfo, +} + +/// Instruction builder for `UpdateExternalPluginAdapterV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper +#[derive(Default)] +pub struct UpdateExternalPluginAdapterV1Builder { + asset: Option, + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + update_info: Option, + __remaining_accounts: Vec, +} + +impl UpdateExternalPluginAdapterV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + #[inline(always)] + pub fn update_info(&mut self, update_info: ExternalPluginAdapterUpdateInfo) -> &mut Self { + self.update_info = Some(update_info); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = UpdateExternalPluginAdapterV1 { + asset: self.asset.expect("asset is not set"), + collection: self.collection, + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = UpdateExternalPluginAdapterV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + update_info: self.update_info.clone().expect("update_info is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `update_external_plugin_adapter_v1` CPI accounts. +pub struct UpdateExternalPluginAdapterV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `update_external_plugin_adapter_v1` CPI instruction. +pub struct UpdateExternalPluginAdapterV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: UpdateExternalPluginAdapterV1InstructionArgs, +} + +impl<'a, 'b> UpdateExternalPluginAdapterV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: UpdateExternalPluginAdapterV1CpiAccounts<'a, 'b>, + args: UpdateExternalPluginAdapterV1InstructionArgs, + ) -> Self { + Self { + __program: program, + asset: accounts.asset, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = UpdateExternalPluginAdapterV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `UpdateExternalPluginAdapterV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper +pub struct UpdateExternalPluginAdapterV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> UpdateExternalPluginAdapterV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(UpdateExternalPluginAdapterV1CpiBuilderInstruction { + __program: program, + asset: None, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + update_info: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + #[inline(always)] + pub fn update_info(&mut self, update_info: ExternalPluginAdapterUpdateInfo) -> &mut Self { + self.instruction.update_info = Some(update_info); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = UpdateExternalPluginAdapterV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + update_info: self + .instruction + .update_info + .clone() + .expect("update_info is not set"), + }; + let instruction = UpdateExternalPluginAdapterV1Cpi { + __program: self.instruction.__program, + + asset: self.instruction.asset.expect("asset is not set"), + + collection: self.instruction.collection, + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct UpdateExternalPluginAdapterV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + update_info: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs b/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs new file mode 100644 index 00000000..16bf3b74 --- /dev/null +++ b/clients/rust/src/generated/instructions/write_collection_external_plugin_adapter_data_v1.rs @@ -0,0 +1,544 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct WriteCollectionExternalPluginAdapterDataV1 { + /// The address of the asset + pub collection: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl WriteCollectionExternalPluginAdapterDataV1 { + pub fn instruction( + &self, + args: WriteCollectionExternalPluginAdapterDataV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: WriteCollectionExternalPluginAdapterDataV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.collection, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = WriteCollectionExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct WriteCollectionExternalPluginAdapterDataV1InstructionData { + discriminator: u8, +} + +impl WriteCollectionExternalPluginAdapterDataV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 29 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WriteCollectionExternalPluginAdapterDataV1InstructionArgs { + pub key: ExternalPluginAdapterKey, + pub data: Vec, +} + +/// Instruction builder for `WriteCollectionExternalPluginAdapterDataV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 4. `[optional]` log_wrapper +#[derive(Default)] +pub struct WriteCollectionExternalPluginAdapterDataV1Builder { + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + data: Option>, + __remaining_accounts: Vec, +} + +impl WriteCollectionExternalPluginAdapterDataV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn collection(&mut self, collection: solana_program::pubkey::Pubkey) -> &mut Self { + self.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The Data Authority of the External PluginExternalPluginAdapter + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.data = Some(data); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = WriteCollectionExternalPluginAdapterDataV1 { + collection: self.collection.expect("collection is not set"), + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = WriteCollectionExternalPluginAdapterDataV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + data: self.data.clone().expect("data is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `write_collection_external_plugin_adapter_data_v1` CPI accounts. +pub struct WriteCollectionExternalPluginAdapterDataV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `write_collection_external_plugin_adapter_data_v1` CPI instruction. +pub struct WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub collection: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: WriteCollectionExternalPluginAdapterDataV1InstructionArgs, +} + +impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: WriteCollectionExternalPluginAdapterDataV1CpiAccounts<'a, 'b>, + args: WriteCollectionExternalPluginAdapterDataV1InstructionArgs, + ) -> Self { + Self { + __program: program, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.collection.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = WriteCollectionExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.collection.clone()); + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `WriteCollectionExternalPluginAdapterDataV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` collection +/// 1. `[writable, signer]` payer +/// 2. `[signer, optional]` authority +/// 3. `[]` system_program +/// 4. `[optional]` log_wrapper +pub struct WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> WriteCollectionExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new( + WriteCollectionExternalPluginAdapterDataV1CpiBuilderInstruction { + __program: program, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + data: None, + __remaining_accounts: Vec::new(), + }, + ); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn collection( + &mut self, + collection: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.collection = Some(collection); + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The Data Authority of the External PluginExternalPluginAdapter + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.instruction.data = Some(data); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = WriteCollectionExternalPluginAdapterDataV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + data: self.instruction.data.clone().expect("data is not set"), + }; + let instruction = WriteCollectionExternalPluginAdapterDataV1Cpi { + __program: self.instruction.__program, + + collection: self.instruction.collection.expect("collection is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct WriteCollectionExternalPluginAdapterDataV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + data: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs b/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs new file mode 100644 index 00000000..0e0123e0 --- /dev/null +++ b/clients/rust/src/generated/instructions/write_external_plugin_adapter_data_v1.rs @@ -0,0 +1,594 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterKey; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Accounts. +pub struct WriteExternalPluginAdapterDataV1 { + /// The address of the asset + pub asset: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl WriteExternalPluginAdapterDataV1 { + pub fn instruction( + &self, + args: WriteExternalPluginAdapterDataV1InstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: WriteExternalPluginAdapterDataV1InstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset, false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authority, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = WriteExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +pub struct WriteExternalPluginAdapterDataV1InstructionData { + discriminator: u8, +} + +impl WriteExternalPluginAdapterDataV1InstructionData { + pub fn new() -> Self { + Self { discriminator: 28 } + } +} + +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WriteExternalPluginAdapterDataV1InstructionArgs { + pub key: ExternalPluginAdapterKey, + pub data: Vec, +} + +/// Instruction builder for `WriteExternalPluginAdapterDataV1`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +/// 5. `[optional]` log_wrapper +#[derive(Default)] +pub struct WriteExternalPluginAdapterDataV1Builder { + asset: Option, + collection: Option, + payer: Option, + authority: Option, + system_program: Option, + log_wrapper: Option, + key: Option, + data: Option>, + __remaining_accounts: Vec, +} + +impl WriteExternalPluginAdapterDataV1Builder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// The Data Authority of the External PluginExternalPluginAdapter + #[inline(always)] + pub fn authority(&mut self, authority: Option) -> &mut Self { + self.authority = authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.key = Some(key); + self + } + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.data = Some(data); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = WriteExternalPluginAdapterDataV1 { + asset: self.asset.expect("asset is not set"), + collection: self.collection, + payer: self.payer.expect("payer is not set"), + authority: self.authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = WriteExternalPluginAdapterDataV1InstructionArgs { + key: self.key.clone().expect("key is not set"), + data: self.data.clone().expect("data is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `write_external_plugin_adapter_data_v1` CPI accounts. +pub struct WriteExternalPluginAdapterDataV1CpiAccounts<'a, 'b> { + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `write_external_plugin_adapter_data_v1` CPI instruction. +pub struct WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// The Data Authority of the External PluginExternalPluginAdapter + pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: WriteExternalPluginAdapterDataV1InstructionArgs, +} + +impl<'a, 'b> WriteExternalPluginAdapterDataV1Cpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: WriteExternalPluginAdapterDataV1CpiAccounts<'a, 'b>, + args: WriteExternalPluginAdapterDataV1InstructionArgs, + ) -> Self { + Self { + __program: program, + asset: accounts.asset, + collection: accounts.collection, + payer: accounts.payer, + authority: accounts.authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(authority) = self.authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authority.key, + true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = WriteExternalPluginAdapterDataV1InstructionData::new() + .try_to_vec() + .unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_CORE_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(authority) = self.authority { + account_infos.push(authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `WriteExternalPluginAdapterDataV1` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` asset +/// 1. `[writable, optional]` collection +/// 2. `[writable, signer]` payer +/// 3. `[signer, optional]` authority +/// 4. `[]` system_program +/// 5. `[optional]` log_wrapper +pub struct WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> WriteExternalPluginAdapterDataV1CpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(WriteExternalPluginAdapterDataV1CpiBuilderInstruction { + __program: program, + asset: None, + collection: None, + payer: None, + authority: None, + system_program: None, + log_wrapper: None, + key: None, + data: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset(&mut self, asset: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.asset = Some(asset); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// The Data Authority of the External PluginExternalPluginAdapter + #[inline(always)] + pub fn authority( + &mut self, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authority = authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn key(&mut self, key: ExternalPluginAdapterKey) -> &mut Self { + self.instruction.key = Some(key); + self + } + #[inline(always)] + pub fn data(&mut self, data: Vec) -> &mut Self { + self.instruction.data = Some(data); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = WriteExternalPluginAdapterDataV1InstructionArgs { + key: self.instruction.key.clone().expect("key is not set"), + data: self.instruction.data.clone().expect("data is not set"), + }; + let instruction = WriteExternalPluginAdapterDataV1Cpi { + __program: self.instruction.__program, + + asset: self.instruction.asset.expect("asset is not set"), + + collection: self.instruction.collection, + + payer: self.instruction.payer.expect("payer is not set"), + + authority: self.instruction.authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct WriteExternalPluginAdapterDataV1CpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + key: Option, + data: Option>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/types/data_store.rs b/clients/rust/src/generated/types/data_store.rs new file mode 100644 index 00000000..7d7f5577 --- /dev/null +++ b/clients/rust/src/generated/types/data_store.rs @@ -0,0 +1,22 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataStore { + pub data_authority: PluginAuthority, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/data_store_init_info.rs b/clients/rust/src/generated/types/data_store_init_info.rs new file mode 100644 index 00000000..bdcf7213 --- /dev/null +++ b/clients/rust/src/generated/types/data_store_init_info.rs @@ -0,0 +1,23 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataStoreInitInfo { + pub data_authority: PluginAuthority, + pub init_plugin_authority: Option, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/data_store_update_info.rs b/clients/rust/src/generated/types/data_store_update_info.rs new file mode 100644 index 00000000..c9fe1584 --- /dev/null +++ b/clients/rust/src/generated/types/data_store_update_info.rs @@ -0,0 +1,20 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DataStoreUpdateInfo { + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/external_plugin_record.rs b/clients/rust/src/generated/types/external_check_result.rs similarity index 83% rename from clients/rust/src/generated/types/external_plugin_record.rs rename to clients/rust/src/generated/types/external_check_result.rs index cd74fa76..344adc52 100644 --- a/clients/rust/src/generated/types/external_plugin_record.rs +++ b/clients/rust/src/generated/types/external_check_result.rs @@ -5,7 +5,6 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::PluginAuthority; #[cfg(feature = "anchor")] use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; #[cfg(not(feature = "anchor"))] @@ -15,7 +14,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ExternalPluginRecord { - pub authority: PluginAuthority, - pub offset: u64, +pub struct ExternalCheckResult { + pub flags: u32, } diff --git a/clients/rust/src/generated/types/external_plugin_adapter.rs b/clients/rust/src/generated/types/external_plugin_adapter.rs new file mode 100644 index 00000000..00e02d0a --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter.rs @@ -0,0 +1,24 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::DataStore; +use crate::generated::types::LifecycleHook; +use crate::generated::types::Oracle; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExternalPluginAdapter { + LifecycleHook(LifecycleHook), + Oracle(Oracle), + DataStore(DataStore), +} diff --git a/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs b/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs new file mode 100644 index 00000000..2168a069 --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter_init_info.rs @@ -0,0 +1,24 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::DataStoreInitInfo; +use crate::generated::types::LifecycleHookInitInfo; +use crate::generated::types::OracleInitInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExternalPluginAdapterInitInfo { + LifecycleHook(LifecycleHookInitInfo), + Oracle(OracleInitInfo), + DataStore(DataStoreInitInfo), +} diff --git a/clients/rust/src/generated/types/external_plugin_adapter_key.rs b/clients/rust/src/generated/types/external_plugin_adapter_key.rs new file mode 100644 index 00000000..b8d3b68d --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter_key.rs @@ -0,0 +1,31 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExternalPluginAdapterKey { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + LifecycleHook(Pubkey), + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + Oracle(Pubkey), + DataStore(PluginAuthority), +} diff --git a/clients/rust/src/generated/types/external_plugin_adapter_schema.rs b/clients/rust/src/generated/types/external_plugin_adapter_schema.rs new file mode 100644 index 00000000..6502eb7c --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter_schema.rs @@ -0,0 +1,22 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum ExternalPluginAdapterSchema { + Binary, + Json, + MsgPack, +} diff --git a/clients/rust/src/generated/types/external_plugin_adapter_type.rs b/clients/rust/src/generated/types/external_plugin_adapter_type.rs new file mode 100644 index 00000000..0a7c025e --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter_type.rs @@ -0,0 +1,22 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum ExternalPluginAdapterType { + LifecycleHook, + Oracle, + DataStore, +} diff --git a/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs b/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs new file mode 100644 index 00000000..cd709990 --- /dev/null +++ b/clients/rust/src/generated/types/external_plugin_adapter_update_info.rs @@ -0,0 +1,24 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::DataStoreUpdateInfo; +use crate::generated::types::LifecycleHookUpdateInfo; +use crate::generated::types::OracleUpdateInfo; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExternalPluginAdapterUpdateInfo { + LifecycleHook(LifecycleHookUpdateInfo), + Oracle(OracleUpdateInfo), + DataStore(DataStoreUpdateInfo), +} diff --git a/clients/rust/src/generated/types/external_registry_record.rs b/clients/rust/src/generated/types/external_registry_record.rs new file mode 100644 index 00000000..6dbef310 --- /dev/null +++ b/clients/rust/src/generated/types/external_registry_record.rs @@ -0,0 +1,28 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExternalPluginAdapterType; +use crate::generated::types::HookableLifecycleEvent; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExternalRegistryRecord { + pub plugin_type: ExternalPluginAdapterType, + pub authority: PluginAuthority, + pub lifecycle_checks: Option>, + pub offset: u64, + pub data_offset: Option, + pub data_len: Option, +} diff --git a/clients/rust/src/generated/types/external_validation_result.rs b/clients/rust/src/generated/types/external_validation_result.rs new file mode 100644 index 00000000..a97b5912 --- /dev/null +++ b/clients/rust/src/generated/types/external_validation_result.rs @@ -0,0 +1,22 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum ExternalValidationResult { + Approved, + Rejected, + Pass, +} diff --git a/clients/rust/src/generated/types/extra_account.rs b/clients/rust/src/generated/types/extra_account.rs new file mode 100644 index 00000000..55eb3d50 --- /dev/null +++ b/clients/rust/src/generated/types/extra_account.rs @@ -0,0 +1,55 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::Seed; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ExtraAccount { + PreconfiguredProgram { + is_signer: bool, + is_writable: bool, + }, + PreconfiguredCollection { + is_signer: bool, + is_writable: bool, + }, + PreconfiguredOwner { + is_signer: bool, + is_writable: bool, + }, + PreconfiguredRecipient { + is_signer: bool, + is_writable: bool, + }, + PreconfiguredAsset { + is_signer: bool, + is_writable: bool, + }, + CustomPda { + seeds: Vec, + custom_program_id: Option, + is_signer: bool, + is_writable: bool, + }, + Address { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + address: Pubkey, + is_signer: bool, + is_writable: bool, + }, +} diff --git a/clients/rust/src/generated/types/hookable_lifecycle_event.rs b/clients/rust/src/generated/types/hookable_lifecycle_event.rs new file mode 100644 index 00000000..51e2288b --- /dev/null +++ b/clients/rust/src/generated/types/hookable_lifecycle_event.rs @@ -0,0 +1,23 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum HookableLifecycleEvent { + Create, + Transfer, + Burn, + Update, +} diff --git a/clients/rust/src/generated/types/lifecycle_hook.rs b/clients/rust/src/generated/types/lifecycle_hook.rs new file mode 100644 index 00000000..d7ea8373 --- /dev/null +++ b/clients/rust/src/generated/types/lifecycle_hook.rs @@ -0,0 +1,30 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LifecycleHook { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub hooked_program: Pubkey, + pub extra_accounts: Option>, + pub data_authority: Option, + pub schema: ExternalPluginAdapterSchema, +} diff --git a/clients/rust/src/generated/types/lifecycle_hook_init_info.rs b/clients/rust/src/generated/types/lifecycle_hook_init_info.rs new file mode 100644 index 00000000..16b54c70 --- /dev/null +++ b/clients/rust/src/generated/types/lifecycle_hook_init_info.rs @@ -0,0 +1,34 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +use crate::generated::types::PluginAuthority; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LifecycleHookInitInfo { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub hooked_program: Pubkey, + pub init_plugin_authority: Option, + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + pub extra_accounts: Option>, + pub data_authority: Option, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/lifecycle_hook_update_info.rs b/clients/rust/src/generated/types/lifecycle_hook_update_info.rs new file mode 100644 index 00000000..ebefb9b7 --- /dev/null +++ b/clients/rust/src/generated/types/lifecycle_hook_update_info.rs @@ -0,0 +1,25 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExternalPluginAdapterSchema; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LifecycleHookUpdateInfo { + pub lifecycle_checks: Option>, + pub extra_accounts: Option>, + pub schema: Option, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index 6049941c..0b2d39bd 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -12,15 +12,34 @@ pub(crate) mod r#burn_delegate; pub(crate) mod r#compression_proof; pub(crate) mod r#creator; pub(crate) mod r#data_state; +pub(crate) mod r#data_store; +pub(crate) mod r#data_store_init_info; +pub(crate) mod r#data_store_update_info; pub(crate) mod r#edition; -pub(crate) mod r#external_plugin_record; -pub(crate) mod r#extra_accounts; +pub(crate) mod r#external_check_result; +pub(crate) mod r#external_plugin_adapter; +pub(crate) mod r#external_plugin_adapter_init_info; +pub(crate) mod r#external_plugin_adapter_key; +pub(crate) mod r#external_plugin_adapter_schema; +pub(crate) mod r#external_plugin_adapter_type; +pub(crate) mod r#external_plugin_adapter_update_info; +pub(crate) mod r#external_registry_record; +pub(crate) mod r#external_validation_result; +pub(crate) mod r#extra_account; pub(crate) mod r#freeze_delegate; pub(crate) mod r#hashable_plugin_schema; pub(crate) mod r#hashed_asset_schema; +pub(crate) mod r#hookable_lifecycle_event; pub(crate) mod r#immutable_metadata; pub(crate) mod r#key; +pub(crate) mod r#lifecycle_hook; +pub(crate) mod r#lifecycle_hook_init_info; +pub(crate) mod r#lifecycle_hook_update_info; pub(crate) mod r#master_edition; +pub(crate) mod r#oracle; +pub(crate) mod r#oracle_init_info; +pub(crate) mod r#oracle_update_info; +pub(crate) mod r#oracle_validation; pub(crate) mod r#permanent_burn_delegate; pub(crate) mod r#permanent_freeze_delegate; pub(crate) mod r#permanent_transfer_delegate; @@ -31,9 +50,12 @@ pub(crate) mod r#plugin_type; pub(crate) mod r#registry_record; pub(crate) mod r#royalties; pub(crate) mod r#rule_set; +pub(crate) mod r#seed; pub(crate) mod r#transfer_delegate; pub(crate) mod r#update_authority; pub(crate) mod r#update_delegate; +pub(crate) mod r#validation_result; +pub(crate) mod r#validation_results_offset; pub use self::r#add_blocker::*; pub use self::r#attribute::*; @@ -42,15 +64,34 @@ pub use self::r#burn_delegate::*; pub use self::r#compression_proof::*; pub use self::r#creator::*; pub use self::r#data_state::*; +pub use self::r#data_store::*; +pub use self::r#data_store_init_info::*; +pub use self::r#data_store_update_info::*; pub use self::r#edition::*; -pub use self::r#external_plugin_record::*; -pub use self::r#extra_accounts::*; +pub use self::r#external_check_result::*; +pub use self::r#external_plugin_adapter::*; +pub use self::r#external_plugin_adapter_init_info::*; +pub use self::r#external_plugin_adapter_key::*; +pub use self::r#external_plugin_adapter_schema::*; +pub use self::r#external_plugin_adapter_type::*; +pub use self::r#external_plugin_adapter_update_info::*; +pub use self::r#external_registry_record::*; +pub use self::r#external_validation_result::*; +pub use self::r#extra_account::*; pub use self::r#freeze_delegate::*; pub use self::r#hashable_plugin_schema::*; pub use self::r#hashed_asset_schema::*; +pub use self::r#hookable_lifecycle_event::*; pub use self::r#immutable_metadata::*; pub use self::r#key::*; +pub use self::r#lifecycle_hook::*; +pub use self::r#lifecycle_hook_init_info::*; +pub use self::r#lifecycle_hook_update_info::*; pub use self::r#master_edition::*; +pub use self::r#oracle::*; +pub use self::r#oracle_init_info::*; +pub use self::r#oracle_update_info::*; +pub use self::r#oracle_validation::*; pub use self::r#permanent_burn_delegate::*; pub use self::r#permanent_freeze_delegate::*; pub use self::r#permanent_transfer_delegate::*; @@ -61,6 +102,9 @@ pub use self::r#plugin_type::*; pub use self::r#registry_record::*; pub use self::r#royalties::*; pub use self::r#rule_set::*; +pub use self::r#seed::*; pub use self::r#transfer_delegate::*; pub use self::r#update_authority::*; pub use self::r#update_delegate::*; +pub use self::r#validation_result::*; +pub use self::r#validation_results_offset::*; diff --git a/clients/rust/src/generated/types/oracle.rs b/clients/rust/src/generated/types/oracle.rs new file mode 100644 index 00000000..f4b7e829 --- /dev/null +++ b/clients/rust/src/generated/types/oracle.rs @@ -0,0 +1,28 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExtraAccount; +use crate::generated::types::ValidationResultsOffset; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Oracle { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub base_address: Pubkey, + pub base_address_config: Option, + pub results_offset: ValidationResultsOffset, +} diff --git a/clients/rust/src/generated/types/oracle_init_info.rs b/clients/rust/src/generated/types/oracle_init_info.rs new file mode 100644 index 00000000..ed8ea193 --- /dev/null +++ b/clients/rust/src/generated/types/oracle_init_info.rs @@ -0,0 +1,33 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +use crate::generated::types::PluginAuthority; +use crate::generated::types::ValidationResultsOffset; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OracleInitInfo { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub base_address: Pubkey, + pub init_plugin_authority: Option, + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + pub base_address_config: Option, + pub results_offset: Option, +} diff --git a/clients/rust/src/generated/types/oracle_update_info.rs b/clients/rust/src/generated/types/oracle_update_info.rs new file mode 100644 index 00000000..b2d23097 --- /dev/null +++ b/clients/rust/src/generated/types/oracle_update_info.rs @@ -0,0 +1,25 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalCheckResult; +use crate::generated::types::ExtraAccount; +use crate::generated::types::HookableLifecycleEvent; +use crate::generated::types::ValidationResultsOffset; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OracleUpdateInfo { + pub lifecycle_checks: Option>, + pub base_address_config: Option, + pub results_offset: Option, +} diff --git a/clients/rust/src/generated/types/oracle_validation.rs b/clients/rust/src/generated/types/oracle_validation.rs new file mode 100644 index 00000000..deb89deb --- /dev/null +++ b/clients/rust/src/generated/types/oracle_validation.rs @@ -0,0 +1,26 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalValidationResult; +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OracleValidation { + Uninitialized, + V1 { + create: ExternalValidationResult, + transfer: ExternalValidationResult, + burn: ExternalValidationResult, + update: ExternalValidationResult, + }, +} diff --git a/clients/rust/src/generated/types/extra_accounts.rs b/clients/rust/src/generated/types/seed.rs similarity index 66% rename from clients/rust/src/generated/types/extra_accounts.rs rename to clients/rust/src/generated/types/seed.rs index 146d5b4b..3234936f 100644 --- a/clients/rust/src/generated/types/extra_accounts.rs +++ b/clients/rust/src/generated/types/seed.rs @@ -15,18 +15,15 @@ use solana_program::pubkey::Pubkey; #[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] #[derive(Clone, Debug, Eq, PartialEq)] -pub enum ExtraAccounts { - None, - SplHook { - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - extra_account_metas: Pubkey, - }, - MplHook { - mint_pda: Option, - collection_pda: Option, - owner_pda: Option, - }, +pub enum Seed { + Collection, + Owner, + Recipient, + Asset, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + Address(Pubkey), + Bytes(Vec), } diff --git a/clients/rust/src/generated/types/validation_result.rs b/clients/rust/src/generated/types/validation_result.rs new file mode 100644 index 00000000..fe5f499a --- /dev/null +++ b/clients/rust/src/generated/types/validation_result.rs @@ -0,0 +1,23 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::FromPrimitive; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive)] +pub enum ValidationResult { + Approved, + Rejected, + Pass, + ForceApproved, +} diff --git a/clients/rust/src/generated/types/validation_results_offset.rs b/clients/rust/src/generated/types/validation_results_offset.rs new file mode 100644 index 00000000..357a3ae7 --- /dev/null +++ b/clients/rust/src/generated/types/validation_results_offset.rs @@ -0,0 +1,21 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +#[cfg(feature = "anchor")] +use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize}; +#[cfg(not(feature = "anchor"))] +use borsh::{BorshDeserialize, BorshSerialize}; + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(not(feature = "anchor"), derive(BorshSerialize, BorshDeserialize))] +#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ValidationResultsOffset { + NoOffset, + Anchor, + Custom(u64), +} diff --git a/clients/rust/src/hooked/advanced_types.rs b/clients/rust/src/hooked/advanced_types.rs index 9d08c525..e7e6d520 100644 --- a/clients/rust/src/hooked/advanced_types.rs +++ b/clients/rust/src/hooked/advanced_types.rs @@ -8,9 +8,10 @@ use std::{cmp::Ordering, io::ErrorKind}; use crate::{ accounts::{BaseAssetV1, BaseCollectionV1, PluginHeaderV1}, types::{ - AddBlocker, Attributes, BurnDelegate, Edition, FreezeDelegate, ImmutableMetadata, Key, - MasterEdition, PermanentBurnDelegate, PermanentFreezeDelegate, PermanentTransferDelegate, - PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, + AddBlocker, Attributes, BurnDelegate, DataStore, Edition, ExternalCheckResult, + ExternalPluginAdapter, ExternalPluginAdapterKey, FreezeDelegate, ImmutableMetadata, Key, + LifecycleHook, MasterEdition, Oracle, PermanentBurnDelegate, PermanentFreezeDelegate, + PermanentTransferDelegate, PluginAuthority, Royalties, TransferDelegate, UpdateDelegate, }, }; @@ -163,10 +164,18 @@ pub struct PluginsList { pub immutable_metadata: Option, } +#[derive(Debug, Default)] +pub struct ExternalPluginAdaptersList { + pub lifecycle_hooks: Vec, + pub oracles: Vec, + pub data_stores: Vec, +} + #[derive(Debug)] pub struct Asset { pub base: BaseAssetV1, pub plugin_list: PluginsList, + pub external_plugin_adapter_list: ExternalPluginAdaptersList, pub plugin_header: Option, } @@ -192,12 +201,31 @@ impl RegistryRecordSafe { } } +///ExternalPluginAdapter Registry record that can be used when the plugin type is not known (i.e. a `ExternalPluginAdapterType` that +/// is too new for this client to know about). +pub struct ExternalRegistryRecordSafe { + pub plugin_type: u8, + pub authority: PluginAuthority, + pub lifecycle_checks: Option>, + pub offset: u64, + pub data_offset: Option, + pub data_len: Option, +} + +impl ExternalRegistryRecordSafe { + /// Associated function for sorting `RegistryRecordIndexable` by offset. + pub fn compare_offsets(a: &RegistryRecordSafe, b: &RegistryRecordSafe) -> Ordering { + a.offset.cmp(&b.offset) + } +} + /// Plugin registry that an account can safely be deserialized into even if some plugins are -/// not known. Note this skips over external plugins for now, and will be updated when those +/// not known. Note this skips over external plugin adapters for now, and will be updated when those /// are defined. pub struct PluginRegistryV1Safe { pub _key: Key, pub registry: Vec, + pub external_registry: Vec, } impl PluginRegistryV1Safe { @@ -224,9 +252,48 @@ impl PluginRegistryV1Safe { }); } + let external_registry_size = u32::deserialize(&mut data)?; + + let mut external_registry = vec![]; + for _ in 0..external_registry_size { + let plugin_type = u8::deserialize(&mut data)?; + let authority = PluginAuthority::deserialize(&mut data)?; + let lifecycle_checks = + Option::>::deserialize(&mut data)?; + let offset = u64::deserialize(&mut data)?; + let data_offset = Option::::deserialize(&mut data)?; + let data_len = Option::::deserialize(&mut data)?; + + external_registry.push(ExternalRegistryRecordSafe { + plugin_type, + authority, + lifecycle_checks, + offset, + data_offset, + data_len, + }); + } + Ok(Self { _key: key, registry, + external_registry, }) } } + +impl From<&ExternalPluginAdapter> for ExternalPluginAdapterKey { + fn from(plugin: &ExternalPluginAdapter) -> Self { + match plugin { + ExternalPluginAdapter::DataStore(data_store) => { + ExternalPluginAdapterKey::DataStore(data_store.data_authority.clone()) + } + ExternalPluginAdapter::Oracle(oracle) => { + ExternalPluginAdapterKey::Oracle(oracle.base_address) + } + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + ExternalPluginAdapterKey::LifecycleHook(lifecycle_hook.hooked_program) + } + } + } +} diff --git a/clients/rust/src/hooked/asset.rs b/clients/rust/src/hooked/asset.rs index 3b72f45a..8131e986 100644 --- a/clients/rust/src/hooked/asset.rs +++ b/clients/rust/src/hooked/asset.rs @@ -5,29 +5,41 @@ use borsh::BorshSerialize; use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, - registry_records_to_plugin_list, Asset, PluginRegistryV1Safe, + registry_records_to_external_plugin_adapter_list, registry_records_to_plugin_list, Asset, + PluginRegistryV1Safe, }; impl Asset { pub fn deserialize(data: &[u8]) -> Result { let base = BaseAssetV1::from_bytes(data)?; let base_data = base.try_to_vec()?; - let (plugin_header, plugin_list) = if base_data.len() != data.len() { + let (plugin_header, plugin_list, external_plugin_adapter_list) = if base_data.len() + != data.len() + { let plugin_header = PluginHeaderV1::from_bytes(&data[base_data.len()..])?; let plugin_registry = PluginRegistryV1Safe::from_bytes( &data[plugin_header.plugin_registry_offset as usize..], )?; let plugin_list = registry_records_to_plugin_list(&plugin_registry.registry, data)?; + let external_plugin_adapter_list = registry_records_to_external_plugin_adapter_list( + &plugin_registry.external_registry, + data, + )?; - (Some(plugin_header), Some(plugin_list)) + ( + Some(plugin_header), + Some(plugin_list), + Some(external_plugin_adapter_list), + ) } else { - (None, None) + (None, None, None) }; Ok(Self { base, plugin_list: plugin_list.unwrap_or_default(), + external_plugin_adapter_list: external_plugin_adapter_list.unwrap_or_default(), plugin_header, }) } diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index a2ed5bcd..283edb4c 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -8,12 +8,16 @@ use solana_program::account_info::AccountInfo; use crate::{ accounts::{BaseAssetV1, PluginHeaderV1}, errors::MplCoreError, - types::{Plugin, PluginAuthority, PluginType, RegistryRecord}, + types::{ + ExternalPluginAdapter, ExternalPluginAdapterType, Plugin, PluginAuthority, PluginType, + RegistryRecord, + }, AddBlockerPlugin, AttributesPlugin, BaseAuthority, BasePlugin, BurnDelegatePlugin, DataBlob, - EditionPlugin, FreezeDelegatePlugin, ImmutableMetadataPlugin, MasterEditionPlugin, - PermanentBurnDelegatePlugin, PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, - PluginRegistryV1Safe, PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, - TransferDelegatePlugin, UpdateDelegatePlugin, + EditionPlugin, ExternalPluginAdaptersList, ExternalRegistryRecordSafe, FreezeDelegatePlugin, + ImmutableMetadataPlugin, MasterEditionPlugin, PermanentBurnDelegatePlugin, + PermanentFreezeDelegatePlugin, PermanentTransferDelegatePlugin, PluginRegistryV1Safe, + PluginsList, RegistryRecordSafe, RoyaltiesPlugin, SolanaAccount, TransferDelegatePlugin, + UpdateDelegatePlugin, }; /// Fetch the plugin from the registry. @@ -105,7 +109,7 @@ pub fn fetch_plugins(account_data: &[u8]) -> Result, std::io } /// List all plugins in an account, dropping any unknown plugins (i.e. `PluginType`s that are too -/// new for this client to know about). Note this also does not support external plugins for now, +/// new for this client to know about). Note this also does not support external plugin adapters for now, /// and will be updated when those are defined. pub fn list_plugins(account_data: &[u8]) -> Result, std::io::Error> { let asset = BaseAssetV1::from_bytes(account_data)?; @@ -122,8 +126,7 @@ pub fn list_plugins(account_data: &[u8]) -> Result, std::io::Err } // Convert a slice of `RegistryRecordSafe` into the `PluginsList` type, dropping any unknown -// plugins (i.e. `PluginType`s that are too new for this client to know about). Note this also does -// not support external plugins for now, and will be updated when those are defined. +// plugins (i.e. `PluginType`s that are too new for this client to know about). pub(crate) fn registry_records_to_plugin_list( registry_records: &[RegistryRecordSafe], account_data: &[u8], @@ -211,3 +214,34 @@ pub(crate) fn registry_records_to_plugin_list( result } + +// Convert a slice of `AdapterRegistryRecordSafe` into the `ExternalPluginAdaptersList` type, dropping any unknown +// plugins (i.e. `ExternalPluginAdapterType`s that are too new for this client to know about). +pub(crate) fn registry_records_to_external_plugin_adapter_list( + registry_records: &[ExternalRegistryRecordSafe], + account_data: &[u8], +) -> Result { + let result = registry_records.iter().try_fold( + ExternalPluginAdaptersList::default(), + |mut acc, record| { + if ExternalPluginAdapterType::from_u8(record.plugin_type).is_some() { + let plugin = ExternalPluginAdapter::deserialize( + &mut &account_data[record.offset as usize..], + )?; + + match plugin { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + acc.lifecycle_hooks.push(lifecycle_hook) + } + ExternalPluginAdapter::Oracle(oracle) => acc.oracles.push(oracle), + ExternalPluginAdapter::DataStore(data_store) => { + acc.data_stores.push(data_store) + } + } + } + Ok(acc) + }, + ); + + result +} diff --git a/clients/rust/tests/add_external_plugins.rs b/clients/rust/tests/add_external_plugins.rs new file mode 100644 index 00000000..f08935dc --- /dev/null +++ b/clients/rust/tests/add_external_plugins.rs @@ -0,0 +1,851 @@ +#![cfg(feature = "test-sbf")] +pub mod setup; +use mpl_core::{ + errors::MplCoreError, + instructions::{ + AddCollectionExternalPluginAdapterV1Builder, AddExternalPluginAdapterV1Builder, + }, + types::{ + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, ExternalPluginAdapterSchema, HookableLifecycleEvent, + LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, + UpdateAuthority, ValidationResultsOffset, + }, +}; +pub use setup::*; + +use solana_program::pubkey; +use solana_program_test::tokio; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; + +#[tokio::test] +#[ignore] +async fn test_add_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; +} + +#[tokio::test] +#[ignore] +async fn test_cannot_add_lifecycle_hook_with_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_temporarily_cannot_add_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_temporarily_cannot_add_lifecycle_hook_on_collection() { + let mut context = program_test().start_with_context().await; + + let collection = Keypair::new(); + create_collection( + &mut context, + CreateCollectionHelperArgs { + collection: &collection, + update_authority: None, + payer: None, + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let update_authority = context.payer.pubkey(); + assert_collection( + &mut context, + AssertCollectionHelperArgs { + collection: collection.pubkey(), + update_authority, + name: None, + uri: None, + num_minted: 0, + current_size: 0, + plugins: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddCollectionExternalPluginAdapterV1Builder::new() + .collection(collection.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); + + // TODO add collection assert. +} + +#[tokio::test] +async fn test_add_oracle() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::NoOffset, + })], + }, + ) + .await; +} + +#[tokio::test] +async fn test_cannot_add_oracle_with_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ], + base_address_config: None, + results_offset: None, + })) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +#[ignore] +async fn test_add_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; +} + +#[tokio::test] +async fn test_temporarily_cannot_add_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_temporarily_cannot_add_data_store_on_collection() { + let mut context = program_test().start_with_context().await; + + let collection = Keypair::new(); + create_collection( + &mut context, + CreateCollectionHelperArgs { + collection: &collection, + update_authority: None, + payer: None, + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let update_authority = context.payer.pubkey(); + assert_collection( + &mut context, + AssertCollectionHelperArgs { + collection: collection.pubkey(), + update_authority, + name: None, + uri: None, + num_minted: 0, + current_size: 0, + plugins: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix = AddCollectionExternalPluginAdapterV1Builder::new() + .collection(collection.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[add_external_plugin_adapter_ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); + + // TODO: add collection assert. +} + +#[tokio::test] +async fn test_cannot_add_duplicate_external_plugin_adapter() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; + + let add_external_plugin_adapter_ix0 = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })) + .instruction(); + + let add_external_plugin_adapter_ix1 = AddExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .init_info(ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ + add_external_plugin_adapter_ix0, + add_external_plugin_adapter_ix1, + ], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(1, error, MplCoreError::ExternalPluginAdapterAlreadyExists); +} diff --git a/clients/rust/tests/create.rs b/clients/rust/tests/create.rs index 3ad64545..a48f947f 100644 --- a/clients/rust/tests/create.rs +++ b/clients/rust/tests/create.rs @@ -26,6 +26,7 @@ async fn create_asset_in_account_state() { update_authority: None, collection: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -42,6 +43,7 @@ async fn create_asset_in_account_state() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -69,6 +71,7 @@ async fn create_asset_with_different_payer() { update_authority: None, collection: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -83,6 +86,7 @@ async fn create_asset_with_different_payer() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -109,6 +113,7 @@ async fn create_asset_with_plugins() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: None, }], + external_plugin_adapters: vec![], }, ) .await @@ -128,6 +133,7 @@ async fn create_asset_with_plugins() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: Some(PluginAuthority::Owner), }], + external_plugin_adapters: vec![], }, ) .await; @@ -155,6 +161,7 @@ async fn create_asset_with_different_update_authority() { update_authority: Some(update_authority.pubkey()), collection: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -170,6 +177,7 @@ async fn create_asset_with_different_update_authority() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -200,6 +208,7 @@ async fn create_asset_with_plugins_with_different_update_authority() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: None, }], + external_plugin_adapters: vec![], }, ) .await @@ -218,6 +227,7 @@ async fn create_asset_with_plugins_with_different_update_authority() { plugin: Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), authority: Some(PluginAuthority::Owner), }], + external_plugin_adapters: vec![], }, ) .await; diff --git a/clients/rust/tests/create_collection.rs b/clients/rust/tests/create_collection.rs index 755eceba..84ff4ddf 100644 --- a/clients/rust/tests/create_collection.rs +++ b/clients/rust/tests/create_collection.rs @@ -20,6 +20,7 @@ async fn test_create_collection() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -59,6 +60,7 @@ async fn create_collection_with_different_payer() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -104,6 +106,7 @@ async fn create_collection_with_plugins() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await @@ -153,6 +156,7 @@ async fn create_collection_with_different_update_authority() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -201,6 +205,7 @@ async fn create_collection_with_plugins_with_different_plugin_authority() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await diff --git a/clients/rust/tests/create_with_external_plugins.rs b/clients/rust/tests/create_with_external_plugins.rs new file mode 100644 index 00000000..411e48ee --- /dev/null +++ b/clients/rust/tests/create_with_external_plugins.rs @@ -0,0 +1,404 @@ +#![cfg(feature = "test-sbf")] +pub mod setup; +use mpl_core::{ + errors::MplCoreError, + types::{ + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, ExternalPluginAdapterSchema, HookableLifecycleEvent, + LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, PluginAuthority, + UpdateAuthority, ValidationResultsOffset, + }, +}; +pub use setup::*; + +use solana_program::pubkey; +use solana_program_test::tokio; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + +#[tokio::test] +#[ignore] +async fn test_create_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; +} + +#[tokio::test] +#[ignore] +async fn test_cannot_create_lifecycle_hook_with_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + let error = create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); +} + +#[tokio::test] +async fn test_temporarily_cannot_create_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + let error = create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); +} + +#[tokio::test] +async fn test_temporarily_cannot_create_lifecycle_hook_on_collection() { + let mut context = program_test().start_with_context().await; + + let collection = Keypair::new(); + let error = create_collection( + &mut context, + CreateCollectionHelperArgs { + collection: &collection, + update_authority: None, + payer: None, + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); +} + +#[tokio::test] +async fn test_create_oracle() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::NoOffset, + })], + }, + ) + .await; +} + +#[tokio::test] +async fn test_cannot_create_oracle_with_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + let error = create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ], + base_address_config: None, + results_offset: None, + })], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); +} + +#[tokio::test] +#[ignore] +async fn test_create_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; +} + +#[tokio::test] +async fn test_temporarily_cannot_create_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + let error = create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); +} + +#[tokio::test] +async fn test_temporarily_cannot_create_data_store_on_collection() { + let mut context = program_test().start_with_context().await; + + let collection = Keypair::new(); + let error = create_collection( + &mut context, + CreateCollectionHelperArgs { + collection: &collection, + update_authority: None, + payer: None, + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )], + }, + ) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::NotAvailable); +} diff --git a/clients/rust/tests/plugins.rs b/clients/rust/tests/plugins.rs index 65510768..431ddb28 100644 --- a/clients/rust/tests/plugins.rs +++ b/clients/rust/tests/plugins.rs @@ -51,6 +51,7 @@ async fn test_fetch_plugin() { }), }, ], + external_plugin_adapters: vec![], }, ) .await @@ -83,6 +84,7 @@ async fn test_fetch_plugin() { }), }, ], + external_plugin_adapters: vec![], }, ) .await; @@ -159,6 +161,7 @@ async fn test_fetch_plugins() { }), }, ], + external_plugin_adapters: vec![], }, ) .await @@ -191,6 +194,7 @@ async fn test_fetch_plugins() { }), }, ], + external_plugin_adapters: vec![], }, ) .await; @@ -268,6 +272,7 @@ async fn test_list_plugins() { }), }, ], + external_plugin_adapters: vec![], }, ) .await @@ -300,6 +305,7 @@ async fn test_list_plugins() { }), }, ], + external_plugin_adapters: vec![], }, ) .await; diff --git a/clients/rust/tests/remove_external_plugins.rs b/clients/rust/tests/remove_external_plugins.rs new file mode 100644 index 00000000..6e1a99ca --- /dev/null +++ b/clients/rust/tests/remove_external_plugins.rs @@ -0,0 +1,271 @@ +#![cfg(feature = "test-sbf")] +pub mod setup; +use mpl_core::{ + instructions::RemoveExternalPluginAdapterV1Builder, + types::{ + DataStore, DataStoreInitInfo, ExternalCheckResult, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, ExternalPluginAdapterSchema, + HookableLifecycleEvent, LifecycleHook, LifecycleHookInitInfo, Oracle, OracleInitInfo, + PluginAuthority, UpdateAuthority, ValidationResultsOffset, + }, +}; +pub use setup::*; + +use solana_program::pubkey; +use solana_program_test::tokio; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; + +#[tokio::test] +#[ignore] +async fn test_remove_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::LifecycleHook(pubkey!( + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + ))) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +async fn test_remove_oracle() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::NoOffset, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::Oracle(Pubkey::default())) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} + +#[tokio::test] +#[ignore] +async fn test_remove_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; + + let ix = RemoveExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::DataStore( + PluginAuthority::UpdateAuthority, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![], + }, + ) + .await; +} diff --git a/clients/rust/tests/setup/mod.rs b/clients/rust/tests/setup/mod.rs index 40b6fb04..6a7c402e 100644 --- a/clients/rust/tests/setup/mod.rs +++ b/clients/rust/tests/setup/mod.rs @@ -1,6 +1,9 @@ use mpl_core::{ - instructions::{CreateCollectionV1Builder, CreateV1Builder}, - types::{DataState, Key, Plugin, PluginAuthorityPair, UpdateAuthority}, + instructions::{CreateCollectionV2Builder, CreateV2Builder}, + types::{ + DataState, ExternalPluginAdapter, ExternalPluginAdapterInitInfo, Key, Plugin, + PluginAuthorityPair, UpdateAuthority, + }, Asset, Collection, }; use solana_program_test::{BanksClientError, ProgramTest, ProgramTestContext}; @@ -31,6 +34,7 @@ pub struct CreateAssetHelperArgs<'a> { pub collection: Option, // TODO use PluginList type here pub plugins: Vec, + pub external_plugin_adapters: Vec, } pub async fn create_asset<'a>( @@ -38,7 +42,7 @@ pub async fn create_asset<'a>( input: CreateAssetHelperArgs<'a>, ) -> Result<(), BanksClientError> { let payer = input.payer.unwrap_or(&context.payer); - let create_ix = CreateV1Builder::new() + let create_ix = CreateV2Builder::new() .asset(input.asset.pubkey()) .collection(input.collection) .authority(input.authority) @@ -50,6 +54,7 @@ pub async fn create_asset<'a>( .name(input.name.unwrap_or(DEFAULT_ASSET_NAME.to_owned())) .uri(input.uri.unwrap_or(DEFAULT_ASSET_URI.to_owned())) .plugins(input.plugins) + .external_plugin_adapters(input.external_plugin_adapters) .instruction(); let mut signers = vec![input.asset, &context.payer]; @@ -75,6 +80,7 @@ pub struct AssertAssetHelperArgs { pub uri: Option, // TODO use PluginList type here pub plugins: Vec, + pub external_plugin_adapters: Vec, } pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHelperArgs) { @@ -125,6 +131,32 @@ pub async fn assert_asset(context: &mut ProgramTestContext, input: AssertAssetHe _ => panic!("unsupported plugin type"), } } + + assert_eq!( + input.external_plugin_adapters.len(), + asset.external_plugin_adapter_list.lifecycle_hooks.len() + + asset.external_plugin_adapter_list.oracles.len() + + asset.external_plugin_adapter_list.data_stores.len() + ); + for plugin in input.external_plugin_adapters { + match plugin { + ExternalPluginAdapter::LifecycleHook(hook) => { + assert!(asset + .external_plugin_adapter_list + .lifecycle_hooks + .contains(&hook)) + } + ExternalPluginAdapter::Oracle(oracle) => { + assert!(asset.external_plugin_adapter_list.oracles.contains(&oracle)) + } + ExternalPluginAdapter::DataStore(data_store) => { + assert!(asset + .external_plugin_adapter_list + .data_stores + .contains(&data_store)) + } + } + } } #[derive(Debug)] @@ -136,6 +168,7 @@ pub struct CreateCollectionHelperArgs<'a> { pub uri: Option, // TODO use PluginList type here pub plugins: Vec, + pub external_plugin_adapters: Vec, } pub async fn create_collection<'a>( @@ -143,7 +176,7 @@ pub async fn create_collection<'a>( input: CreateCollectionHelperArgs<'a>, ) -> Result<(), BanksClientError> { let payer = input.payer.unwrap_or(&context.payer); - let create_ix = CreateCollectionV1Builder::new() + let create_ix = CreateCollectionV2Builder::new() .collection(input.collection.pubkey()) .update_authority(input.update_authority) .payer(payer.pubkey()) @@ -151,6 +184,7 @@ pub async fn create_collection<'a>( .name(input.name.unwrap_or(DEFAULT_COLLECTION_NAME.to_owned())) .uri(input.uri.unwrap_or(DEFAULT_COLLECTION_URI.to_owned())) .plugins(input.plugins) + .external_plugin_adapters(input.external_plugin_adapters) .instruction(); let mut signers = vec![input.collection, &context.payer]; @@ -229,6 +263,8 @@ pub async fn assert_collection( _ => panic!("unsupported plugin type"), } } + + // TODO validate external plugin adapters here. } pub async fn airdrop( @@ -250,3 +286,32 @@ pub async fn airdrop( context.banks_client.process_transaction(tx).await.unwrap(); Ok(()) } + +#[macro_export] +macro_rules! assert_custom_instruction_error { + ($ix:expr, $error:expr, $matcher:pat) => { + match $error { + solana_program_test::BanksClientError::TransactionError( + solana_sdk::transaction::TransactionError::InstructionError( + $ix, + solana_sdk::instruction::InstructionError::Custom(x), + ), + ) => match num_traits::FromPrimitive::from_i32(x as i32) { + Some($matcher) => assert!(true), + Some(other) => { + assert!( + false, + "Expected another custom instruction error than '{:#?}'", + other + ) + } + None => assert!(false, "Expected custom instruction error"), + }, + err => assert!( + false, + "Expected custom instruction error but got '{:#?}'", + err + ), + }; + }; +} diff --git a/clients/rust/tests/transfer.rs b/clients/rust/tests/transfer.rs index 68141165..bc4beafd 100644 --- a/clients/rust/tests/transfer.rs +++ b/clients/rust/tests/transfer.rs @@ -27,6 +27,7 @@ async fn transfer_asset_as_owner() { update_authority: None, collection: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await @@ -58,6 +59,7 @@ async fn transfer_asset_as_owner() { name: None, uri: None, plugins: vec![], + external_plugin_adapters: vec![], }, ) .await; @@ -92,6 +94,7 @@ async fn transfer_asset_with_royalties() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await @@ -132,6 +135,7 @@ async fn transfer_asset_with_royalties() { rule_set: RuleSet::ProgramDenyList(vec![]), }), }], + external_plugin_adapters: vec![], }, ) .await; diff --git a/clients/rust/tests/update_external_plugins.rs b/clients/rust/tests/update_external_plugins.rs new file mode 100644 index 00000000..22972f9e --- /dev/null +++ b/clients/rust/tests/update_external_plugins.rs @@ -0,0 +1,493 @@ +#![cfg(feature = "test-sbf")] +pub mod setup; +use mpl_core::{ + errors::MplCoreError, + instructions::UpdateExternalPluginAdapterV1Builder, + types::{ + DataStore, DataStoreInitInfo, DataStoreUpdateInfo, ExternalCheckResult, + ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, + ExternalPluginAdapterSchema, ExternalPluginAdapterUpdateInfo, HookableLifecycleEvent, + LifecycleHook, LifecycleHookInitInfo, LifecycleHookUpdateInfo, Oracle, OracleInitInfo, + OracleUpdateInfo, PluginAuthority, UpdateAuthority, ValidationResultsOffset, + }, +}; +pub use setup::*; + +use solana_program::pubkey; +use solana_program_test::tokio; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction}; + +#[tokio::test] +#[ignore] +async fn test_update_lifecycle_hook() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; + + let ix = UpdateExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::LifecycleHook(pubkey!( + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + ))) + .update_info(ExternalPluginAdapterUpdateInfo::LifecycleHook( + LifecycleHookUpdateInfo { + lifecycle_checks: None, + extra_accounts: None, + schema: Some(ExternalPluginAdapterSchema::Json), + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Json, + })], + }, + ) + .await; +} + +#[tokio::test] +#[ignore] +async fn test_cannot_update_lifecycle_hook_to_have_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::LifecycleHook( + LifecycleHookInitInfo { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + )], + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::LifecycleHook(LifecycleHook { + hooked_program: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + extra_accounts: None, + data_authority: Some(PluginAuthority::UpdateAuthority), + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; + + let ix = UpdateExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::LifecycleHook(pubkey!( + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + ))) + .update_info(ExternalPluginAdapterUpdateInfo::LifecycleHook( + LifecycleHookUpdateInfo { + lifecycle_checks: Some(vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 1 }, + ), + ]), + extra_accounts: None, + schema: Some(ExternalPluginAdapterSchema::Json), + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); + + // TODO: Check that registry record did not get updated. +} + +#[tokio::test] +async fn test_update_oracle() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::NoOffset, + })], + }, + ) + .await; + + let ix = UpdateExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::Oracle(Pubkey::default())) + .update_info(ExternalPluginAdapterUpdateInfo::Oracle(OracleUpdateInfo { + lifecycle_checks: None, + base_address_config: None, + results_offset: Some(ValidationResultsOffset::Custom(10)), + })) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::Custom(10), + })], + }, + ) + .await; +} + +#[tokio::test] +async fn test_cannot_update_oracle_to_have_duplicate_lifecycle_checks() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::Oracle(OracleInitInfo { + base_address: Pubkey::default(), + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + lifecycle_checks: vec![( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + )], + base_address_config: None, + results_offset: None, + })], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::Oracle(Oracle { + base_address: Pubkey::default(), + base_address_config: None, + results_offset: ValidationResultsOffset::NoOffset, + })], + }, + ) + .await; + + let ix = UpdateExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::Oracle(Pubkey::default())) + .update_info(ExternalPluginAdapterUpdateInfo::Oracle(OracleUpdateInfo { + lifecycle_checks: Some(vec![ + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ( + HookableLifecycleEvent::Transfer, + ExternalCheckResult { flags: 4 }, + ), + ]), + base_address_config: None, + results_offset: Some(ValidationResultsOffset::Custom(10)), + })) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + let error = context + .banks_client + .process_transaction(tx) + .await + .unwrap_err(); + + assert_custom_instruction_error!(0, error, MplCoreError::DuplicateLifecycleChecks); + + // TODO: Check that registry record did not get updated. +} + +#[tokio::test] +#[ignore] +async fn test_update_data_store() { + let mut context = program_test().start_with_context().await; + + let asset = Keypair::new(); + create_asset( + &mut context, + CreateAssetHelperArgs { + owner: None, + payer: None, + asset: &asset, + data_state: None, + name: None, + uri: None, + authority: None, + update_authority: None, + collection: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapterInitInfo::DataStore( + DataStoreInitInfo { + init_plugin_authority: Some(PluginAuthority::UpdateAuthority), + data_authority: PluginAuthority::UpdateAuthority, + schema: None, + }, + )], + }, + ) + .await + .unwrap(); + + let owner = context.payer.pubkey(); + let update_authority = context.payer.pubkey(); + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Binary, + })], + }, + ) + .await; + + let ix = UpdateExternalPluginAdapterV1Builder::new() + .asset(asset.pubkey()) + .payer(context.payer.pubkey()) + .key(ExternalPluginAdapterKey::DataStore( + PluginAuthority::UpdateAuthority, + )) + .update_info(ExternalPluginAdapterUpdateInfo::DataStore( + DataStoreUpdateInfo { + schema: Some(ExternalPluginAdapterSchema::Json), + }, + )) + .instruction(); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + assert_asset( + &mut context, + AssertAssetHelperArgs { + asset: asset.pubkey(), + owner, + update_authority: Some(UpdateAuthority::Address(update_authority)), + name: None, + uri: None, + plugins: vec![], + external_plugin_adapters: vec![ExternalPluginAdapter::DataStore(DataStore { + data_authority: PluginAuthority::UpdateAuthority, + schema: ExternalPluginAdapterSchema::Json, + })], + }, + ) + .await; +} diff --git a/configs/kinobi.cjs b/configs/kinobi.cjs index 930fd8de..c6aaf750 100755 --- a/configs/kinobi.cjs +++ b/configs/kinobi.cjs @@ -77,14 +77,36 @@ kinobi.update( } } }, + createV2: { + arguments: { + plugins: { + defaultValue: k.arrayValueNode([]) + }, + externalPluginAdapters: { + defaultValue: k.arrayValueNode([]) + }, + dataState: { + defaultValue: k.enumValueNode('DataState', 'AccountState') + } + } + }, createCollectionV1: { arguments: { plugins: { defaultValue: k.noneValueNode() - } } }, + createCollectionV2: { + arguments: { + plugins: { + defaultValue: k.noneValueNode() + }, + externalPluginAdapters: { + defaultValue: k.arrayValueNode([]) + }, + } + }, collect: { accounts: { recipient1: { @@ -99,7 +121,23 @@ kinobi.update( arguments: { newUpdateAuthority: { defaultValue: k.noneValueNode() - } + }, + newName: { + defaultValue: k.noneValueNode() + }, + newUri: { + defaultValue: k.noneValueNode() + }, + } + }, + updateCollectionV1: { + arguments: { + newName: { + defaultValue: k.noneValueNode() + }, + newUri: { + defaultValue: k.noneValueNode() + }, } } }) @@ -137,6 +175,71 @@ kinobi.update( }) ); +kinobi.update( + new k.updateDefinedTypesVisitor({ + ruleSet: { + name: "baseRuleSet" + }, + royalties: { + name: "baseRoyalties" + }, + pluginAuthority: { + name: "basePluginAuthority" + }, + updateAuthority: { + name: "baseUpdateAuthority" + }, + seed: { + name: "baseSeed" + }, + extraAccount: { + name: "baseExtraAccount" + }, + externalPluginAdapterKey: { + name: "baseExternalPluginAdapterKey" + }, + externalPluginAdapterInitInfo: { + name: "baseExternalPluginAdapterInitInfo" + }, + externalPluginAdapterUpdateInfo: { + name: "baseExternalPluginAdapterUpdateInfo" + }, + oracle: { + name: "baseOracle" + }, + oracleInitInfo: { + name: "baseOracleInitInfo" + }, + oracleUpdateInfo: { + name: "baseOracleUpdateInfo" + }, + lifecycleHook: { + name: "baseLifecycleHook" + }, + lifecycleHookInitInfo: { + name: "baseLifecycleHookInitInfo" + }, + lifecycleHookUpdateInfo: { + name: "baseLifecycleHookUpdateInfo" + }, + dataStore: { + name: "baseDataStore" + }, + dataStoreInitInfo: { + name: "baseDataStoreInitInfo" + }, + dataStoreUpdateInfo: { + name: "baseDataStoreUpdateInfo" + }, + validationResultsOffset: { + name: "baseValidationResultsOffset" + }, + masterEdition: { + name: "baseMasterEdition" + } + }) +) + // Render JavaScript. const jsDir = path.join(clientDir, "js", "src", "generated"); const prettier = require(path.join(clientDir, "js", ".prettierrc.json")); diff --git a/configs/scripts/program/build.sh b/configs/scripts/program/build.sh index c8708bc7..0b324b55 100755 --- a/configs/scripts/program/build.sh +++ b/configs/scripts/program/build.sh @@ -4,6 +4,10 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) OUTPUT="./programs/.bin" # saves external programs binaries to the output directory source ${SCRIPT_DIR}/dump.sh ${OUTPUT} + +# Oracle program used for tests and is only deployed to devnet +source ${SCRIPT_DIR}/dump_oracle_example.sh ${OUTPUT} + # go to parent folder cd $(dirname $(dirname $(dirname ${SCRIPT_DIR}))) diff --git a/configs/scripts/program/dump_oracle_example.sh b/configs/scripts/program/dump_oracle_example.sh new file mode 100755 index 00000000..b815a0b7 --- /dev/null +++ b/configs/scripts/program/dump_oracle_example.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +EXTERNAL_ID=("4RZ7RhXeL4oz4kVX5fpRfkNQ3nz1n4eruqBn2AGPQepo") +EXTERNAL_SO=("mpl_core_oracle_example.so") + +# output colours +RED() { echo $'\e[1;31m'$1$'\e[0m'; } +GRN() { echo $'\e[1;32m'$1$'\e[0m'; } +YLW() { echo $'\e[1;33m'$1$'\e[0m'; } + +CURRENT_DIR=$(pwd) +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +# go to parent folder +cd $(dirname $(dirname $(dirname $SCRIPT_DIR))) + +OUTPUT=$1 + +# oracle example program is only deployed to devnet +RPC="https://api.devnet.solana.com" + +if [ -z "$OUTPUT" ]; then + echo "missing output directory" + exit 1 +fi + +# creates the output directory if it doesn't exist +if [ ! -d ${OUTPUT} ]; then + mkdir ${OUTPUT} +fi + +# only prints this if we have external programs +if [ ${#EXTERNAL_ID[@]} -gt 0 ]; then + echo "Dumping external accounts to '${OUTPUT}':" +fi + +# copy external programs or accounts binaries from the chain +copy_from_chain() { + ACCOUNT_TYPE=`echo $1 | cut -d. -f2` + PREFIX=$2 + + case "$ACCOUNT_TYPE" in + "bin") + solana account -u $RPC ${EXTERNAL_ID[$i]} -o ${OUTPUT}/$2$1 > /dev/null + ;; + "so") + solana program dump -u $RPC ${EXTERNAL_ID[$i]} ${OUTPUT}/$2$1 > /dev/null + ;; + *) + echo $(RED "[ ERROR ] unknown account type for '$1'") + exit 1 + ;; + esac + + if [ -z "$PREFIX" ]; then + echo "Wrote account data to ${OUTPUT}/$2$1" + fi +} + +# dump external programs binaries if needed +for i in ${!EXTERNAL_ID[@]}; do + if [ ! -f "${OUTPUT}/${EXTERNAL_SO[$i]}" ]; then + copy_from_chain "${EXTERNAL_SO[$i]}" + else + copy_from_chain "${EXTERNAL_SO[$i]}" "onchain-" + + ON_CHAIN=`sha256sum -b ${OUTPUT}/onchain-${EXTERNAL_SO[$i]} | cut -d ' ' -f 1` + LOCAL=`sha256sum -b ${OUTPUT}/${EXTERNAL_SO[$i]} | cut -d ' ' -f 1` + + if [ "$ON_CHAIN" != "$LOCAL" ]; then + echo $(YLW "[ WARNING ] on-chain and local binaries are different for '${EXTERNAL_SO[$i]}'") + else + echo "$(GRN "[ SKIPPED ]") on-chain and local binaries are the same for '${EXTERNAL_SO[$i]}'" + fi + + rm ${OUTPUT}/onchain-${EXTERNAL_SO[$i]} + fi +done + +# only prints this if we have external programs +if [ ${#EXTERNAL_ID[@]} -gt 0 ]; then + echo "" +fi + +cd ${CURRENT_DIR} \ No newline at end of file diff --git a/configs/validator.cjs b/configs/validator.cjs index 10be4405..58043348 100755 --- a/configs/validator.cjs +++ b/configs/validator.cjs @@ -15,6 +15,11 @@ module.exports = { programId: "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d", deployPath: getProgram("mpl_core_program.so"), }, + { + label: "Mpl Core Oracle Example", + programId: "4RZ7RhXeL4oz4kVX5fpRfkNQ3nz1n4eruqBn2AGPQepo", + deployPath: getProgram("mpl_core_oracle_example.so"), + }, // Below are external programs that should be included in the local validator. // You may configure which ones to fetch from the cluster when building // programs within the `configs/program-scripts/dump.sh` script. diff --git a/idls/mpl_core.json b/idls/mpl_core.json index 2088bd58..743d25dc 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -803,7 +803,7 @@ }, { "name": "authority", - "isMut": false, + "isMut": true, "isSigner": true, "isOptional": true, "docs": [ @@ -863,7 +863,7 @@ }, { "name": "authority", - "isMut": false, + "isMut": true, "isSigner": true, "isOptional": true, "docs": [ @@ -1267,150 +1267,794 @@ "type": "u8", "value": 19 } - } - ], - "accounts": [ - { - "name": "PluginHeaderV1", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "pluginRegistryOffset", - "type": "u64" - } - ] - } - }, - { - "name": "PluginRegistryV1", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "registry", - "type": { - "vec": { - "defined": "RegistryRecord" - } - } - }, - { - "name": "externalPlugins", - "type": { - "vec": { - "defined": "ExternalPluginRecord" - } - } - } - ] - } }, { - "name": "AssetV1", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "owner", - "type": "publicKey" - }, - { - "name": "updateAuthority", - "type": { - "defined": "UpdateAuthority" - } - }, - { - "name": "name", - "type": "string" - }, - { - "name": "uri", - "type": "string" - }, - { - "name": "seq", - "type": { - "option": "u64" - } + "name": "CreateV2", + "accounts": [ + { + "name": "asset", + "isMut": true, + "isSigner": true, + "docs": [ + "The address of the new asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The authority signing for creation" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "owner", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The owner of the new asset. Defaults to the authority if not present." + ] + }, + { + "name": "updateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The authority on the new asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "createV2Args", + "type": { + "defined": "CreateV2Args" } - ] + } + ], + "discriminant": { + "type": "u8", + "value": 20 } }, { - "name": "CollectionV1", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "updateAuthority", - "type": "publicKey" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "uri", - "type": "string" - }, - { - "name": "numMinted", - "type": "u32" - }, - { - "name": "currentSize", - "type": "u32" + "name": "CreateCollectionV2", + "accounts": [ + { + "name": "collection", + "isMut": true, + "isSigner": true, + "docs": [ + "The address of the new asset" + ] + }, + { + "name": "updateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The authority of the new asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + } + ], + "args": [ + { + "name": "createCollectionV2Args", + "type": { + "defined": "CreateCollectionV2Args" } - ] + } + ], + "discriminant": { + "type": "u8", + "value": 21 } }, { - "name": "HashedAssetV1", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "hash", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + "name": "AddExternalPluginAdapterV1", + "accounts": [ + { + "name": "asset", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "addExternalPluginAdapterV1Args", + "type": { + "defined": "AddExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 22 + } + }, + { + "name": "AddCollectionExternalPluginAdapterV1", + "accounts": [ + { + "name": "collection", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "addCollectionExternalPluginAdapterV1Args", + "type": { + "defined": "AddCollectionExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 23 + } + }, + { + "name": "RemoveExternalPluginAdapterV1", + "accounts": [ + { + "name": "asset", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "removeExternalPluginAdapterV1Args", + "type": { + "defined": "RemoveExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 24 + } + }, + { + "name": "RemoveCollectionExternalPluginAdapterV1", + "accounts": [ + { + "name": "collection", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "removeCollectionExternalPluginAdapterV1Args", + "type": { + "defined": "RemoveCollectionExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 25 + } + }, + { + "name": "UpdateExternalPluginAdapterV1", + "accounts": [ + { + "name": "asset", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "updateExternalPluginAdapterV1Args", + "type": { + "defined": "UpdateExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 26 + } + }, + { + "name": "UpdateCollectionExternalPluginAdapterV1", + "accounts": [ + { + "name": "collection", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "updateCollectionExternalPluginAdapterV1Args", + "type": { + "defined": "UpdateCollectionExternalPluginAdapterV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 27 + } + }, + { + "name": "WriteExternalPluginAdapterDataV1", + "accounts": [ + { + "name": "asset", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The Data Authority of the External PluginExternalPluginAdapter" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "writeExternalPluginAdapterDataV1Args", + "type": { + "defined": "WriteExternalPluginAdapterDataV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 28 + } + }, + { + "name": "WriteCollectionExternalPluginAdapterDataV1", + "accounts": [ + { + "name": "collection", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The Data Authority of the External PluginExternalPluginAdapter" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "writeCollectionExternalPluginAdapterDataV1Args", + "type": { + "defined": "WriteCollectionExternalPluginAdapterDataV1Args" + } + } + ], + "discriminant": { + "type": "u8", + "value": 29 + } + } + ], + "accounts": [ + { + "name": "PluginHeaderV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "pluginRegistryOffset", + "type": "u64" + } + ] + } + }, + { + "name": "PluginRegistryV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "registry", + "type": { + "vec": { + "defined": "RegistryRecord" + } + } + }, + { + "name": "externalRegistry", + "type": { + "vec": { + "defined": "ExternalRegistryRecord" + } + } + } + ] + } + }, + { + "name": "AssetV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "owner", + "type": "publicKey" + }, + { + "name": "updateAuthority", + "type": { + "defined": "UpdateAuthority" + } + }, + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "seq", + "type": { + "option": "u64" + } + } + ] + } + }, + { + "name": "CollectionV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "updateAuthority", + "type": "publicKey" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "numMinted", + "type": "u32" + }, + { + "name": "currentSize", + "type": "u32" + } + ] + } + }, + { + "name": "HashedAssetV1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "hash", + "type": { + "array": [ + "u8", + 32 + ] + } + } + ] } } ], @@ -1461,80 +2105,416 @@ } }, { - "name": "Attributes", + "name": "Attributes", + "type": { + "kind": "struct", + "fields": [ + { + "name": "attributeList", + "type": { + "vec": { + "defined": "Attribute" + } + } + } + ] + } + }, + { + "name": "BurnDelegate", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "DataStore", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } + } + ] + } + }, + { + "name": "DataStoreInitInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dataAuthority", + "type": { + "defined": "Authority" + } + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, + { + "name": "DataStoreUpdateInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, + { + "name": "Edition", + "type": { + "kind": "struct", + "fields": [ + { + "name": "number", + "type": "u32" + } + ] + } + }, + { + "name": "FreezeDelegate", + "type": { + "kind": "struct", + "fields": [ + { + "name": "frozen", + "type": "bool" + } + ] + } + }, + { + "name": "ImmutableMetadata", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "ExternalCheckResult", + "type": { + "kind": "struct", + "fields": [ + { + "name": "flags", + "type": "u32" + } + ] + } + }, + { + "name": "LifecycleHook", + "type": { + "kind": "struct", + "fields": [ + { + "name": "hookedProgram", + "type": "publicKey" + }, + { + "name": "extraAccounts", + "type": { + "option": { + "vec": { + "defined": "ExtraAccount" + } + } + } + }, + { + "name": "dataAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "schema", + "type": { + "defined": "ExternalPluginAdapterSchema" + } + } + ] + } + }, + { + "name": "LifecycleHookInitInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "hookedProgram", + "type": "publicKey" + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "lifecycleChecks", + "type": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + }, + { + "name": "extraAccounts", + "type": { + "option": { + "vec": { + "defined": "ExtraAccount" + } + } + } + }, + { + "name": "dataAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, + { + "name": "LifecycleHookUpdateInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "lifecycleChecks", + "type": { + "option": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + } + }, + { + "name": "extraAccounts", + "type": { + "option": { + "vec": { + "defined": "ExtraAccount" + } + } + } + }, + { + "name": "schema", + "type": { + "option": { + "defined": "ExternalPluginAdapterSchema" + } + } + } + ] + } + }, + { + "name": "MasterEdition", + "type": { + "kind": "struct", + "fields": [ + { + "name": "maxSupply", + "type": { + "option": "u32" + } + }, + { + "name": "name", + "type": { + "option": "string" + } + }, + { + "name": "uri", + "type": { + "option": "string" + } + } + ] + } + }, + { + "name": "Oracle", "type": { "kind": "struct", "fields": [ { - "name": "attributeList", + "name": "baseAddress", + "type": "publicKey" + }, + { + "name": "baseAddressConfig", "type": { - "vec": { - "defined": "Attribute" + "option": { + "defined": "ExtraAccount" } } - } - ] - } - }, - { - "name": "BurnDelegate", - "type": { - "kind": "struct", - "fields": [] - } - }, - { - "name": "Edition", - "type": { - "kind": "struct", - "fields": [ + }, { - "name": "number", - "type": "u32" + "name": "resultsOffset", + "type": { + "defined": "ValidationResultsOffset" + } } ] } }, { - "name": "FreezeDelegate", + "name": "OracleInitInfo", "type": { "kind": "struct", "fields": [ { - "name": "frozen", - "type": "bool" + "name": "baseAddress", + "type": "publicKey" + }, + { + "name": "initPluginAuthority", + "type": { + "option": { + "defined": "Authority" + } + } + }, + { + "name": "lifecycleChecks", + "type": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + }, + { + "name": "baseAddressConfig", + "type": { + "option": { + "defined": "ExtraAccount" + } + } + }, + { + "name": "resultsOffset", + "type": { + "option": { + "defined": "ValidationResultsOffset" + } + } } ] } }, { - "name": "ImmutableMetadata", - "type": { - "kind": "struct", - "fields": [] - } - }, - { - "name": "MasterEdition", + "name": "OracleUpdateInfo", "type": { "kind": "struct", "fields": [ { - "name": "maxSupply", + "name": "lifecycleChecks", "type": { - "option": "u32" + "option": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } } }, { - "name": "name", + "name": "baseAddressConfig", "type": { - "option": "string" + "option": { + "defined": "ExtraAccount" + } } }, { - "name": "uri", + "name": "resultsOffset", "type": { - "option": "string" + "option": { + "defined": "ValidationResultsOffset" + } } } ] @@ -1591,19 +2571,54 @@ } }, { - "name": "ExternalPluginRecord", + "name": "ExternalRegistryRecord", "type": { "kind": "struct", "fields": [ + { + "name": "pluginType", + "type": { + "defined": "ExternalPluginAdapterType" + } + }, { "name": "authority", "type": { "defined": "Authority" } }, + { + "name": "lifecycleChecks", + "type": { + "option": { + "vec": { + "tuple": [ + { + "defined": "HookableLifecycleEvent" + }, + { + "defined": "ExternalCheckResult" + } + ] + } + } + } + }, { "name": "offset", "type": "u64" + }, + { + "name": "dataOffset", + "type": { + "option": "u64" + } + }, + { + "name": "dataLen", + "type": { + "option": "u64" + } } ] } @@ -1671,6 +2686,34 @@ ] } }, + { + "name": "AddExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initInfo", + "type": { + "defined": "ExternalPluginAdapterInitInfo" + } + } + ] + } + }, + { + "name": "AddCollectionExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initInfo", + "type": { + "defined": "ExternalPluginAdapterInitInfo" + } + } + ] + } + }, { "name": "AddPluginV1Args", "type": { @@ -1826,6 +2869,48 @@ ] } }, + { + "name": "CreateV2Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dataState", + "type": { + "defined": "DataState" + } + }, + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "plugins", + "type": { + "option": { + "vec": { + "defined": "PluginAuthorityPair" + } + } + } + }, + { + "name": "externalPluginAdapters", + "type": { + "option": { + "vec": { + "defined": "ExternalPluginAdapterInitInfo" + } + } + } + } + ] + } + }, { "name": "CreateCollectionV1Args", "type": { @@ -1852,6 +2937,42 @@ ] } }, + { + "name": "CreateCollectionV2Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "plugins", + "type": { + "option": { + "vec": { + "defined": "PluginAuthorityPair" + } + } + } + }, + { + "name": "externalPluginAdapters", + "type": { + "option": { + "vec": { + "defined": "ExternalPluginAdapterInitInfo" + } + } + } + } + ] + } + }, { "name": "DecompressV1Args", "type": { @@ -1866,6 +2987,34 @@ ] } }, + { + "name": "RemoveExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + } + ] + } + }, + { + "name": "RemoveCollectionExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + } + ] + } + }, { "name": "RemovePluginV1Args", "type": { @@ -1967,20 +3116,60 @@ } }, { - "name": "UpdateCollectionV1Args", + "name": "UpdateCollectionV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "newName", + "type": { + "option": "string" + } + }, + { + "name": "newUri", + "type": { + "option": "string" + } + } + ] + } + }, + { + "name": "UpdateExternalPluginAdapterV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + }, + { + "name": "updateInfo", + "type": { + "defined": "ExternalPluginAdapterUpdateInfo" + } + } + ] + } + }, + { + "name": "UpdateCollectionExternalPluginAdapterV1Args", "type": { "kind": "struct", "fields": [ { - "name": "newName", + "name": "key", "type": { - "option": "string" + "defined": "ExternalPluginAdapterKey" } }, { - "name": "newUri", + "name": "updateInfo", "type": { - "option": "string" + "defined": "ExternalPluginAdapterUpdateInfo" } } ] @@ -2014,6 +3203,42 @@ ] } }, + { + "name": "WriteExternalPluginAdapterDataV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + }, + { + "name": "data", + "type": "bytes" + } + ] + } + }, + { + "name": "WriteCollectionExternalPluginAdapterDataV1Args", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "ExternalPluginAdapterKey" + } + }, + { + "name": "data", + "type": "bytes" + } + ] + } + }, { "name": "CompressionProof", "type": { @@ -2091,125 +3316,471 @@ } }, { - "name": "pluginHashes", - "type": { - "vec": { - "array": [ - "u8", - 32 - ] - } - } + "name": "pluginHashes", + "type": { + "vec": { + "array": [ + "u8", + 32 + ] + } + } + } + ] + } + }, + { + "name": "Plugin", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Royalties", + "fields": [ + { + "defined": "Royalties" + } + ] + }, + { + "name": "FreezeDelegate", + "fields": [ + { + "defined": "FreezeDelegate" + } + ] + }, + { + "name": "BurnDelegate", + "fields": [ + { + "defined": "BurnDelegate" + } + ] + }, + { + "name": "TransferDelegate", + "fields": [ + { + "defined": "TransferDelegate" + } + ] + }, + { + "name": "UpdateDelegate", + "fields": [ + { + "defined": "UpdateDelegate" + } + ] + }, + { + "name": "PermanentFreezeDelegate", + "fields": [ + { + "defined": "PermanentFreezeDelegate" + } + ] + }, + { + "name": "Attributes", + "fields": [ + { + "defined": "Attributes" + } + ] + }, + { + "name": "PermanentTransferDelegate", + "fields": [ + { + "defined": "PermanentTransferDelegate" + } + ] + }, + { + "name": "PermanentBurnDelegate", + "fields": [ + { + "defined": "PermanentBurnDelegate" + } + ] + }, + { + "name": "Edition", + "fields": [ + { + "defined": "Edition" + } + ] + }, + { + "name": "MasterEdition", + "fields": [ + { + "defined": "MasterEdition" + } + ] + }, + { + "name": "AddBlocker", + "fields": [ + { + "defined": "AddBlocker" + } + ] + }, + { + "name": "ImmutableMetadata", + "fields": [ + { + "defined": "ImmutableMetadata" + } + ] + } + ] + } + }, + { + "name": "PluginType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Royalties" + }, + { + "name": "FreezeDelegate" + }, + { + "name": "BurnDelegate" + }, + { + "name": "TransferDelegate" + }, + { + "name": "UpdateDelegate" + }, + { + "name": "PermanentFreezeDelegate" + }, + { + "name": "Attributes" + }, + { + "name": "PermanentTransferDelegate" + }, + { + "name": "PermanentBurnDelegate" + }, + { + "name": "Edition" + }, + { + "name": "MasterEdition" + }, + { + "name": "AddBlocker" + }, + { + "name": "ImmutableMetadata" + } + ] + } + }, + { + "name": "ExternalPluginAdapterType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "LifecycleHook" + }, + { + "name": "Oracle" + }, + { + "name": "DataStore" + } + ] + } + }, + { + "name": "ExternalPluginAdapter", + "type": { + "kind": "enum", + "variants": [ + { + "name": "LifecycleHook", + "fields": [ + { + "defined": "LifecycleHook" + } + ] + }, + { + "name": "Oracle", + "fields": [ + { + "defined": "Oracle" + } + ] + }, + { + "name": "DataStore", + "fields": [ + { + "defined": "DataStore" + } + ] + } + ] + } + }, + { + "name": "HookableLifecycleEvent", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Create" + }, + { + "name": "Transfer" + }, + { + "name": "Burn" + }, + { + "name": "Update" } ] } }, { - "name": "Plugin", + "name": "ExtraAccount", "type": { "kind": "enum", "variants": [ { - "name": "Royalties", + "name": "PreconfiguredProgram", "fields": [ { - "defined": "Royalties" + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "FreezeDelegate", + "name": "PreconfiguredCollection", "fields": [ { - "defined": "FreezeDelegate" + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "BurnDelegate", + "name": "PreconfiguredOwner", "fields": [ { - "defined": "BurnDelegate" + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "TransferDelegate", + "name": "PreconfiguredRecipient", "fields": [ { - "defined": "TransferDelegate" + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "UpdateDelegate", + "name": "PreconfiguredAsset", "fields": [ { - "defined": "UpdateDelegate" + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "PermanentFreezeDelegate", + "name": "CustomPda", "fields": [ { - "defined": "PermanentFreezeDelegate" + "name": "seeds", + "type": { + "vec": { + "defined": "Seed" + } + } + }, + { + "name": "custom_program_id", + "type": { + "option": "publicKey" + } + }, + { + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] }, { - "name": "Attributes", + "name": "Address", "fields": [ { - "defined": "Attributes" + "name": "address", + "type": "publicKey" + }, + { + "name": "is_signer", + "type": "bool" + }, + { + "name": "is_writable", + "type": "bool" } ] + } + ] + } + }, + { + "name": "Seed", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Collection" }, { - "name": "PermanentTransferDelegate", + "name": "Owner" + }, + { + "name": "Recipient" + }, + { + "name": "Asset" + }, + { + "name": "Address", + "fields": [ + "publicKey" + ] + }, + { + "name": "Bytes", + "fields": [ + "bytes" + ] + } + ] + } + }, + { + "name": "ExternalPluginAdapterSchema", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Binary" + }, + { + "name": "Json" + }, + { + "name": "MsgPack" + } + ] + } + }, + { + "name": "ExternalPluginAdapterInitInfo", + "type": { + "kind": "enum", + "variants": [ + { + "name": "LifecycleHook", "fields": [ { - "defined": "PermanentTransferDelegate" + "defined": "LifecycleHookInitInfo" } ] }, { - "name": "PermanentBurnDelegate", + "name": "Oracle", "fields": [ { - "defined": "PermanentBurnDelegate" + "defined": "OracleInitInfo" } ] }, { - "name": "Edition", + "name": "DataStore", "fields": [ { - "defined": "Edition" + "defined": "DataStoreInitInfo" } ] - }, + } + ] + } + }, + { + "name": "ExternalPluginAdapterUpdateInfo", + "type": { + "kind": "enum", + "variants": [ { - "name": "MasterEdition", + "name": "LifecycleHook", "fields": [ { - "defined": "MasterEdition" + "defined": "LifecycleHookUpdateInfo" } ] }, { - "name": "AddBlocker", + "name": "Oracle", "fields": [ { - "defined": "AddBlocker" + "defined": "OracleUpdateInfo" } ] }, { - "name": "ImmutableMetadata", + "name": "DataStore", "fields": [ { - "defined": "ImmutableMetadata" + "defined": "DataStoreUpdateInfo" } ] } @@ -2217,48 +3788,126 @@ } }, { - "name": "PluginType", + "name": "ExternalPluginAdapterKey", "type": { "kind": "enum", "variants": [ { - "name": "Royalties" + "name": "LifecycleHook", + "fields": [ + "publicKey" + ] }, { - "name": "FreezeDelegate" + "name": "Oracle", + "fields": [ + "publicKey" + ] }, { - "name": "BurnDelegate" - }, + "name": "DataStore", + "fields": [ + { + "defined": "Authority" + } + ] + } + ] + } + }, + { + "name": "ValidationResult", + "type": { + "kind": "enum", + "variants": [ { - "name": "TransferDelegate" + "name": "Approved" }, { - "name": "UpdateDelegate" + "name": "Rejected" }, { - "name": "PermanentFreezeDelegate" + "name": "Pass" }, { - "name": "Attributes" - }, + "name": "ForceApproved" + } + ] + } + }, + { + "name": "ExternalValidationResult", + "type": { + "kind": "enum", + "variants": [ { - "name": "PermanentTransferDelegate" + "name": "Approved" }, { - "name": "PermanentBurnDelegate" + "name": "Rejected" }, { - "name": "Edition" + "name": "Pass" + } + ] + } + }, + { + "name": "ValidationResultsOffset", + "type": { + "kind": "enum", + "variants": [ + { + "name": "NoOffset" }, { - "name": "MasterEdition" + "name": "Anchor" }, { - "name": "AddBlocker" + "name": "Custom", + "fields": [ + "u64" + ] + } + ] + } + }, + { + "name": "OracleValidation", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Uninitialized" }, { - "name": "ImmutableMetadata" + "name": "V1", + "fields": [ + { + "name": "create", + "type": { + "defined": "ExternalValidationResult" + } + }, + { + "name": "transfer", + "type": { + "defined": "ExternalValidationResult" + } + }, + { + "name": "burn", + "type": { + "defined": "ExternalValidationResult" + } + }, + { + "name": "update", + "type": { + "defined": "ExternalValidationResult" + } + } + ] } ] } @@ -2330,49 +3979,6 @@ ] } }, - { - "name": "ExtraAccounts", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "SplHook", - "fields": [ - { - "name": "extra_account_metas", - "type": "publicKey" - } - ] - }, - { - "name": "MplHook", - "fields": [ - { - "name": "mint_pda", - "type": { - "option": "publicKey" - } - }, - { - "name": "collection_pda", - "type": { - "option": "publicKey" - } - }, - { - "name": "owner_pda", - "type": { - "option": "publicKey" - } - } - ] - } - ] - } - }, { "name": "Key", "type": { @@ -2578,6 +4184,51 @@ "code": 30, "name": "InvalidLogWrapperProgram", "msg": "Invalid Log Wrapper Program" + }, + { + "code": 31, + "name": "ExternalPluginAdapterNotFound", + "msg": "External PluginExternalPluginAdapter not found" + }, + { + "code": 32, + "name": "ExternalPluginAdapterAlreadyExists", + "msg": "External PluginExternalPluginAdapter already exists" + }, + { + "code": 33, + "name": "MissingAsset", + "msg": "Missing asset needed for extra account PDA derivation" + }, + { + "code": 34, + "name": "MissingExternalPluginAdapterAccount", + "msg": "Missing account needed for external plugin adapter" + }, + { + "code": 35, + "name": "OracleCanRejectOnly", + "msg": "Oracle external plugin adapter can only be configured to reject" + }, + { + "code": 36, + "name": "RequiresLifecycleCheck", + "msg": "External plugin adapter must have at least one lifecycle check" + }, + { + "code": 37, + "name": "DuplicateLifecycleChecks", + "msg": "Duplicate lifecycle checks were provided for external plugin adapter " + }, + { + "code": 38, + "name": "InvalidOracleAccountData", + "msg": "Could not read from oracle account" + }, + { + "code": 39, + "name": "UninitializedOracleAccount", + "msg": "Oracle account is uninitialized" } ], "metadata": { diff --git a/programs/mpl-core/Cargo.toml b/programs/mpl-core/Cargo.toml index 7a2d168c..f9e89769 100644 --- a/programs/mpl-core/Cargo.toml +++ b/programs/mpl-core/Cargo.toml @@ -12,6 +12,7 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "^0.10" shank = "0.4.2" +modular-bitfield = "0.11.2" num-derive = "^0.3" num-traits = "^0.2" solana-program = "^1.17" diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index 66c783a1..9c02b853 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -132,6 +132,42 @@ pub enum MplCoreError { /// 30 - Invalid Log Wrapper Program #[error("Invalid Log Wrapper Program")] InvalidLogWrapperProgram, + + /// 31 - External PluginExternalPluginAdapter not found + #[error("External PluginExternalPluginAdapter not found")] + ExternalPluginAdapterNotFound, + + /// 32 - External PluginExternalPluginAdapter already exists + #[error("External PluginExternalPluginAdapter already exists")] + ExternalPluginAdapterAlreadyExists, + + /// 33 - Missing asset needed for extra account PDA derivation + #[error("Missing asset needed for extra account PDA derivation")] + MissingAsset, + + /// 34 - Missing account needed for external plugin adapter + #[error("Missing account needed for external plugin adapter")] + MissingExternalPluginAdapterAccount, + + /// 35 - Oracle external plugin adapter can only be configured to reject + #[error("Oracle external plugin adapter can only be configured to reject")] + OracleCanRejectOnly, + + /// 36 - External plugin adapter must have at least one lifecycle check + #[error("External plugin adapter must have at least one lifecycle check")] + RequiresLifecycleCheck, + + /// 37 - Duplicate lifecycle checks were provided for external plugin adapter + #[error("Duplicate lifecycle checks were provided for external plugin adapter ")] + DuplicateLifecycleChecks, + + /// 38 - Could not read from oracle account + #[error("Could not read from oracle account")] + InvalidOracleAccountData, + + /// 39 - Oracle account is uninitialized + #[error("Oracle account is uninitialized")] + UninitializedOracleAccount, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/instruction.rs b/programs/mpl-core/src/instruction.rs index c72987aa..5cb980f5 100644 --- a/programs/mpl-core/src/instruction.rs +++ b/programs/mpl-core/src/instruction.rs @@ -3,12 +3,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::{ShankContext, ShankInstruction}; use crate::processor::{ - AddCollectionPluginV1Args, AddPluginV1Args, ApproveCollectionPluginAuthorityV1Args, + AddCollectionExternalPluginAdapterV1Args, AddCollectionPluginV1Args, + AddExternalPluginAdapterV1Args, AddPluginV1Args, ApproveCollectionPluginAuthorityV1Args, ApprovePluginAuthorityV1Args, BurnCollectionV1Args, BurnV1Args, CompressV1Args, - CreateCollectionV1Args, CreateV1Args, DecompressV1Args, RemoveCollectionPluginV1Args, - RemovePluginV1Args, RevokeCollectionPluginAuthorityV1Args, RevokePluginAuthorityV1Args, - TransferV1Args, UpdateCollectionPluginV1Args, UpdateCollectionV1Args, UpdatePluginV1Args, - UpdateV1Args, + CreateCollectionV1Args, CreateCollectionV2Args, CreateV1Args, CreateV2Args, DecompressV1Args, + RemoveCollectionExternalPluginAdapterV1Args, RemoveCollectionPluginV1Args, + RemoveExternalPluginAdapterV1Args, RemovePluginV1Args, RevokeCollectionPluginAuthorityV1Args, + RevokePluginAuthorityV1Args, TransferV1Args, UpdateCollectionExternalPluginAdapterV1Args, + UpdateCollectionPluginV1Args, UpdateCollectionV1Args, UpdateExternalPluginAdapterV1Args, + UpdatePluginV1Args, UpdateV1Args, WriteCollectionExternalPluginAdapterDataV1Args, + WriteExternalPluginAdapterDataV1Args, }; /// Instructions supported by the mpl-core program. @@ -124,7 +128,7 @@ pub(crate) enum MplAssetInstruction { #[account(0, writable, name="asset", desc = "The address of the asset")] #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(3, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(3, optional, writable, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(4, optional, name="system_program", desc = "The system program")] #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] BurnV1(BurnV1Args), @@ -132,7 +136,7 @@ pub(crate) enum MplAssetInstruction { /// Burn an mpl-core. #[account(0, writable, name="collection", desc = "The address of the asset")] #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(2, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(2, optional, writable, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(3, optional, name="log_wrapper", desc = "The SPL Noop Program")] BurnCollectionV1(BurnCollectionV1Args), @@ -188,4 +192,92 @@ pub(crate) enum MplAssetInstruction { #[account(0, writable, name="recipient1", desc = "The address of the recipient 1")] #[account(1, writable, name="recipient2", desc = "The address of the recipient 2")] Collect, + + /// Create a new mpl-core Asset V2. + /// This function creates the initial Asset, with or without internal/external plugin adapters. + #[account(0, writable, signer, name="asset", desc = "The address of the new asset")] + #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] + #[account(2, optional, signer, name="authority", desc = "The authority signing for creation")] + #[account(3, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(4, optional, name="owner", desc = "The owner of the new asset. Defaults to the authority if not present.")] + #[account(5, optional, name="update_authority", desc = "The authority on the new asset")] + #[account(6, name="system_program", desc = "The system program")] + #[account(7, optional, name="log_wrapper", desc = "The SPL Noop Program")] + CreateV2(CreateV2Args), + + /// Create a new mpl-core Collection V2. + /// This function creates the initial Collection, with or without internal/external plugin adapters. + #[account(0, writable, signer, name="collection", desc = "The address of the new asset")] + #[account(1, optional, name="update_authority", desc = "The authority of the new asset")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, name="system_program", desc = "The system program")] + CreateCollectionV2(CreateCollectionV2Args), + + /// Add an external plugin adapter to an mpl-core. + #[account(0, writable, name="asset", desc = "The address of the asset")] + #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + AddExternalPluginAdapterV1(AddExternalPluginAdapterV1Args), + + /// Add an external plugin adapter to an mpl-core Collection. + #[account(0, writable, name="collection", desc = "The address of the asset")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + AddCollectionExternalPluginAdapterV1(AddCollectionExternalPluginAdapterV1Args), + + /// Remove an external plugin adapter from an mpl-core. + #[account(0, writable, name="asset", desc = "The address of the asset")] + #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + RemoveExternalPluginAdapterV1(RemoveExternalPluginAdapterV1Args), + + /// Remove an external plugin adapter from an mpl-core Collection. + #[account(0, writable, name="collection", desc = "The address of the asset")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + RemoveCollectionExternalPluginAdapterV1(RemoveCollectionExternalPluginAdapterV1Args), + + /// Update the data for an external plugin adapter of an mpl-core. + #[account(0, writable, name="asset", desc = "The address of the asset")] + #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + UpdateExternalPluginAdapterV1(UpdateExternalPluginAdapterV1Args), + + /// Update the data for an external plugin adapter of an mpl-core Collection. + #[account(0, writable, name="collection", desc = "The address of the asset")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The owner or delegate of the asset")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + UpdateCollectionExternalPluginAdapterV1(UpdateCollectionExternalPluginAdapterV1Args), + + /// Add an external plugin adapter to an mpl-core. + #[account(0, writable, name="asset", desc = "The address of the asset")] + #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] + #[account(2, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(3, optional, signer, name="authority", desc = "The Data Authority of the External PluginExternalPluginAdapter")] + #[account(4, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + WriteExternalPluginAdapterDataV1(WriteExternalPluginAdapterDataV1Args), + + /// Add an external plugin adapter to an mpl-core. + #[account(0, writable, name="collection", desc = "The address of the asset")] + #[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")] + #[account(2, optional, signer, name="authority", desc = "The Data Authority of the External PluginExternalPluginAdapter")] + #[account(3, name="system_program", desc = "The system program")] + #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + WriteCollectionExternalPluginAdapterDataV1(WriteCollectionExternalPluginAdapterDataV1Args), } diff --git a/programs/mpl-core/src/plugins/data_store.rs b/programs/mpl-core/src/plugins/data_store.rs new file mode 100644 index 00000000..76e959d7 --- /dev/null +++ b/programs/mpl-core/src/plugins/data_store.rs @@ -0,0 +1,74 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::program_error::ProgramError; + +use super::{ + Authority, ExternalPluginAdapterSchema, PluginValidation, PluginValidationContext, + ValidationResult, +}; + +/// The data store third party plugin contains arbitrary data that can be written to by the +/// `data_authority`. Note this is different then the overall plugin authority stored in the +/// `ExternalRegistryRecord` as it cannot update/revoke authority or change other metadata for the +/// plugin. The data is stored at the plugin's data offset (which in the account is immediately +/// after this header). +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataStore { + /// Data authority who can update the data store. Cannot be changed after plugin is + /// added. + pub data_authority: Authority, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, +} + +impl DataStore { + /// Updates the data store with the new info. + pub fn update(&mut self, info: &DataStoreUpdateInfo) { + if let Some(schema) = &info.schema { + self.schema = *schema; + } + } +} + +impl PluginValidation for DataStore { + fn validate_add_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_transfer( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } +} + +impl From<&DataStoreInitInfo> for DataStore { + fn from(init_info: &DataStoreInitInfo) -> Self { + Self { + data_authority: init_info.data_authority, + schema: init_info.schema.unwrap_or_default(), + } + } +} + +/// Data store initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataStoreInitInfo { + /// Data authority who can update the data store. This field cannot be + /// changed after the plugin is added. + pub data_authority: Authority, + /// Initial plugin authority who can update plugin properties. + pub init_plugin_authority: Option, + /// Schema for the data used by the plugin. + pub schema: Option, +} + +/// Data store update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct DataStoreUpdateInfo { + /// Schema for the data used by the plugin. + pub schema: Option, +} diff --git a/programs/mpl-core/src/plugins/external_plugin_adapters.rs b/programs/mpl-core/src/plugins/external_plugin_adapters.rs new file mode 100644 index 00000000..ceb25fb4 --- /dev/null +++ b/programs/mpl-core/src/plugins/external_plugin_adapters.rs @@ -0,0 +1,545 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, +}; +use strum::EnumCount; + +use crate::{ + error::MplCoreError, + state::{AssetV1, SolanaAccount}, +}; + +use super::{ + Authority, DataStore, DataStoreInitInfo, DataStoreUpdateInfo, ExternalCheckResult, + ExternalRegistryRecord, LifecycleHook, LifecycleHookInitInfo, LifecycleHookUpdateInfo, Oracle, + OracleInitInfo, OracleUpdateInfo, PluginValidation, PluginValidationContext, ValidationResult, +}; + +/// List of third party plugin types. +#[repr(C)] +#[derive( + Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, +)] +pub enum ExternalPluginAdapterType { + /// Lifecycle Hook. + LifecycleHook, + /// Oracle. + Oracle, + /// Data Store. + DataStore, +} + +impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType { + fn from(key: &ExternalPluginAdapterKey) -> Self { + match key { + ExternalPluginAdapterKey::LifecycleHook(_) => ExternalPluginAdapterType::LifecycleHook, + ExternalPluginAdapterKey::Oracle(_) => ExternalPluginAdapterType::Oracle, + ExternalPluginAdapterKey::DataStore(_) => ExternalPluginAdapterType::DataStore, + } + } +} + +impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapterType { + fn from(init_info: &ExternalPluginAdapterInitInfo) -> Self { + match init_info { + ExternalPluginAdapterInitInfo::LifecycleHook(_) => { + ExternalPluginAdapterType::LifecycleHook + } + ExternalPluginAdapterInitInfo::Oracle(_) => ExternalPluginAdapterType::Oracle, + ExternalPluginAdapterInitInfo::DataStore(_) => ExternalPluginAdapterType::DataStore, + } + } +} + +/// Definition of the external plugin adapter variants, each containing a link to the external plugin adapter +/// struct. +#[repr(C)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum ExternalPluginAdapter { + /// Lifecycle Hook. The hooked program and extra accounts are specified in the attached + /// struct. The hooked program is called at specified lifecycle events and will return a + /// validation result and new data to store. + LifecycleHook(LifecycleHook), + /// Oracle. Get a `ValidationResult` result from an account either specified by or derived + /// from a `Pubkey` stored in the attached struct. + Oracle(Oracle), + /// Arbitrary data that can be written to by the data `Authority` stored in the attached + /// struct. Note this data authority is different then the plugin authority. + DataStore(DataStore), +} + +impl ExternalPluginAdapter { + /// Update the plugin from the update info. + pub fn update(&mut self, update_info: &ExternalPluginAdapterUpdateInfo) { + match (self, update_info) { + ( + ExternalPluginAdapter::LifecycleHook(lifecycle_hook), + ExternalPluginAdapterUpdateInfo::LifecycleHook(update_info), + ) => { + lifecycle_hook.update(update_info); + } + ( + ExternalPluginAdapter::Oracle(oracle), + ExternalPluginAdapterUpdateInfo::Oracle(update_info), + ) => { + oracle.update(update_info); + } + ( + ExternalPluginAdapter::DataStore(data_store), + ExternalPluginAdapterUpdateInfo::DataStore(update_info), + ) => { + data_store.update(update_info); + } + _ => unreachable!(), + } + } + + /// Check if a plugin is permitted to approve or deny a create action. + pub fn check_create(plugin: &ExternalPluginAdapterInitInfo) -> ExternalCheckResult { + match plugin { + ExternalPluginAdapterInitInfo::LifecycleHook(init_info) => { + if let Some(checks) = init_info + .lifecycle_checks + .iter() + .find(|event| event.0 == HookableLifecycleEvent::Create) + { + checks.1 + } else { + ExternalCheckResult::none() + } + } + ExternalPluginAdapterInitInfo::Oracle(init_info) => { + if let Some(checks) = init_info + .lifecycle_checks + .iter() + .find(|event| event.0 == HookableLifecycleEvent::Create) + { + checks.1 + } else { + ExternalCheckResult::none() + } + } + ExternalPluginAdapterInitInfo::DataStore(_) => ExternalCheckResult::none(), + } + } + + /// Validate the add external plugin adapter lifecycle event. + pub(crate) fn validate_create( + external_plugin_adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + match external_plugin_adapter { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_create(ctx) + } + ExternalPluginAdapter::Oracle(oracle) => oracle.validate_create(ctx), + ExternalPluginAdapter::DataStore(data_store) => data_store.validate_create(ctx), + } + } + + /// Route the validation of the update action to the appropriate plugin. + pub(crate) fn validate_update( + external_plugin_adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + match external_plugin_adapter { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_update(ctx) + } + ExternalPluginAdapter::Oracle(oracle) => oracle.validate_update(ctx), + ExternalPluginAdapter::DataStore(data_store) => data_store.validate_update(ctx), + } + } + + /// Route the validation of the burn action to the appropriate plugin. + pub(crate) fn validate_burn( + external_plugin_adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + match external_plugin_adapter { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_burn(ctx) + } + ExternalPluginAdapter::Oracle(oracle) => oracle.validate_burn(ctx), + ExternalPluginAdapter::DataStore(data_store) => data_store.validate_burn(ctx), + } + } + + /// Route the validation of the transfer action to the appropriate external plugin adapter. + pub(crate) fn validate_transfer( + external_plugin_adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + match external_plugin_adapter { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_transfer(ctx) + } + ExternalPluginAdapter::Oracle(oracle) => oracle.validate_transfer(ctx), + ExternalPluginAdapter::DataStore(data_store) => data_store.validate_transfer(ctx), + } + } + + /// Validate the add external plugin adapter lifecycle event. + pub(crate) fn validate_add_external_plugin_adapter( + external_plugin_adapter: &ExternalPluginAdapter, + ctx: &PluginValidationContext, + ) -> Result { + match external_plugin_adapter { + ExternalPluginAdapter::LifecycleHook(lifecycle_hook) => { + lifecycle_hook.validate_add_external_plugin_adapter(ctx) + } + ExternalPluginAdapter::Oracle(oracle) => { + oracle.validate_add_external_plugin_adapter(ctx) + } + ExternalPluginAdapter::DataStore(data_store) => { + data_store.validate_add_external_plugin_adapter(ctx) + } + } + } + + /// Load and deserialize a plugin from an offset in the account. + pub fn load(account: &AccountInfo, offset: usize) -> Result { + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; + Self::deserialize(&mut bytes).map_err(|error| { + msg!("Error: {}", error); + MplCoreError::DeserializationError.into() + }) + } + + /// Save and serialize a plugin to an offset in the account. + pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { + msg!("Error: {}", error); + MplCoreError::SerializationError.into() + }) + } +} + +impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapter { + fn from(init_info: &ExternalPluginAdapterInitInfo) -> Self { + match init_info { + ExternalPluginAdapterInitInfo::LifecycleHook(init_info) => { + ExternalPluginAdapter::LifecycleHook(LifecycleHook::from(init_info)) + } + ExternalPluginAdapterInitInfo::Oracle(init_info) => { + ExternalPluginAdapter::Oracle(Oracle::from(init_info)) + } + ExternalPluginAdapterInitInfo::DataStore(init_info) => { + ExternalPluginAdapter::DataStore(DataStore::from(init_info)) + } + } + } +} + +#[repr(C)] +#[derive(Eq, PartialEq, Clone, BorshSerialize, BorshDeserialize, Debug, PartialOrd, Ord, Hash)] +/// An enum listing all the lifecyle events available for external plugin adapter hooks. Note that some +/// lifecycle events such as adding and removing plugins will be checked by default as they are +/// inherently part of the external plugin adapter system. +pub enum HookableLifecycleEvent { + /// Add a plugin. + Create, + /// Transfer an Asset. + Transfer, + /// Burn an Asset or a Collection. + Burn, + /// Update an Asset or a Collection. + Update, +} + +/// Prefix used with some of the `ExtraAccounts` that are PDAs. +pub const MPL_CORE_PREFIX: &str = "mpl-core"; + +/// Type used to specify extra accounts for external plugin adapters. +#[repr(C)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum ExtraAccount { + /// Program-based PDA with seeds \["mpl-core"\] + PreconfiguredProgram { + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// Collection-based PDA with seeds \["mpl-core", collection_pubkey\] + PreconfiguredCollection { + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// Owner-based PDA with seeds \["mpl-core", owner_pubkey\] + PreconfiguredOwner { + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// Recipient-based PDA with seeds \["mpl-core", recipient_pubkey\] + /// If the lifecycle event has no recipient the derivation will fail. + PreconfiguredRecipient { + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// Asset-based PDA with seeds \["mpl-core", asset_pubkey\] + PreconfiguredAsset { + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// PDA based on user-specified seeds. + CustomPda { + /// Seeds used to derive the PDA. + seeds: Vec, + /// Program ID if not the base address/program ID for the external plugin. + custom_program_id: Option, + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, + /// Directly-specified address. + Address { + /// Address. + address: Pubkey, + /// Account is a signer + is_signer: bool, + /// Account is writable. + is_writable: bool, + }, +} + +impl ExtraAccount { + pub(crate) fn derive( + &self, + program_id: &Pubkey, + ctx: &PluginValidationContext, + ) -> Result { + match self { + ExtraAccount::PreconfiguredProgram { .. } => { + let seeds = &[MPL_CORE_PREFIX.as_bytes()]; + let (pubkey, _bump) = Pubkey::find_program_address(seeds, program_id); + Ok(pubkey) + } + ExtraAccount::PreconfiguredCollection { .. } => { + let collection = ctx + .collection_info + .ok_or(MplCoreError::MissingCollection)? + .key; + let seeds = &[MPL_CORE_PREFIX.as_bytes(), collection.as_ref()]; + let (pubkey, _bump) = Pubkey::find_program_address(seeds, program_id); + Ok(pubkey) + } + ExtraAccount::PreconfiguredOwner { .. } => { + let asset_info = ctx.asset_info.ok_or(MplCoreError::MissingAsset)?; + let owner = AssetV1::load(asset_info, 0)?.owner; + let seeds = &[MPL_CORE_PREFIX.as_bytes(), owner.as_ref()]; + let (pubkey, _bump) = Pubkey::find_program_address(seeds, program_id); + Ok(pubkey) + } + ExtraAccount::PreconfiguredRecipient { .. } => { + let recipient = ctx.new_owner.ok_or(MplCoreError::MissingNewOwner)?.key; + let seeds = &[MPL_CORE_PREFIX.as_bytes(), recipient.as_ref()]; + let (pubkey, _bump) = Pubkey::find_program_address(seeds, program_id); + Ok(pubkey) + } + ExtraAccount::PreconfiguredAsset { .. } => { + let asset = ctx.asset_info.ok_or(MplCoreError::MissingAsset)?.key; + let seeds = &[MPL_CORE_PREFIX.as_bytes(), asset.as_ref()]; + let (pubkey, _bump) = Pubkey::find_program_address(seeds, program_id); + Ok(pubkey) + } + ExtraAccount::CustomPda { + seeds, + custom_program_id, + .. + } => { + let seeds = transform_seeds(seeds, ctx)?; + + // Convert the Vec of Vec into Vec of u8 slices. + let vec_of_slices: Vec<&[u8]> = seeds.iter().map(Vec::as_slice).collect(); + + let (pubkey, _bump) = Pubkey::find_program_address( + &vec_of_slices, + custom_program_id.as_ref().unwrap_or(program_id), + ); + Ok(pubkey) + } + ExtraAccount::Address { address, .. } => Ok(*address), + } + } +} + +// Transform seeds from their tokens into actual seeds based on passed-in context values. +fn transform_seeds( + seeds: &Vec, + ctx: &PluginValidationContext, +) -> Result>, ProgramError> { + let mut transformed_seeds = Vec::>::new(); + + for seed in seeds { + match seed { + Seed::Collection => { + let collection = ctx + .collection_info + .ok_or(MplCoreError::MissingCollection)? + .key + .as_ref() + .to_vec(); + transformed_seeds.push(collection); + } + Seed::Owner => { + let asset_info = ctx.asset_info.ok_or(MplCoreError::MissingAsset)?; + let owner = AssetV1::load(asset_info, 0)?.owner.as_ref().to_vec(); + transformed_seeds.push(owner); + } + Seed::Recipient => { + let recipient = ctx + .new_owner + .ok_or(MplCoreError::MissingNewOwner)? + .key + .as_ref() + .to_vec(); + transformed_seeds.push(recipient); + } + Seed::Asset => { + let asset = ctx + .asset_info + .ok_or(MplCoreError::MissingAsset)? + .key + .as_ref() + .to_vec(); + transformed_seeds.push(asset); + } + Seed::Address(pubkey) => { + transformed_seeds.push(pubkey.as_ref().to_vec()); + } + Seed::Bytes(val) => { + transformed_seeds.push(val.clone()); + } + } + } + + Ok(transformed_seeds) +} + +/// Seeds to be used for extra account custom PDA derivations. +#[repr(C)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum Seed { + /// Insert the collection `Pubkey`. If the asset has no collection the lifecycle action will + /// fail. + Collection, + /// Insert the owner `Pubkey`. + Owner, + /// Insert the recipient `Pubkey`. If the lifecycle event has no recipient the action will fail. + Recipient, + /// Insert the asset `Pubkey`. + Asset, + /// Insert the specified `Pubkey`. + Address(Pubkey), + /// Insert the specified bytes. + Bytes(Vec), +} + +/// Schema used for third party plugin data. +#[repr(C)] +#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, Default)] +pub enum ExternalPluginAdapterSchema { + /// Raw binary data. + #[default] + Binary, + /// JSON. + Json, + /// MessagePack serialized data. + MsgPack, +} + +/// Information needed to initialize an external plugin adapter. +#[repr(C)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum ExternalPluginAdapterInitInfo { + /// Lifecycle Hook. + LifecycleHook(LifecycleHookInitInfo), + /// Oracle. + Oracle(OracleInitInfo), + /// Data Store. + DataStore(DataStoreInitInfo), +} + +/// Information needed to update an external plugin adapter. +#[repr(C)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum ExternalPluginAdapterUpdateInfo { + /// Lifecycle Hook. + LifecycleHook(LifecycleHookUpdateInfo), + /// Oracle. + Oracle(OracleUpdateInfo), + /// Data Store. + DataStore(DataStoreUpdateInfo), +} + +/// Key used to uniquely specify an external plugin adapter after it is created. +#[repr(C)] +#[derive( + Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, +)] +pub enum ExternalPluginAdapterKey { + /// Lifecycle Hook. + LifecycleHook(Pubkey), + /// Oracle. + Oracle(Pubkey), + /// Data Store. + DataStore(Authority), +} + +impl ExternalPluginAdapterKey { + pub(crate) fn from_record( + account: &AccountInfo, + external_registry_record: &ExternalRegistryRecord, + ) -> Result { + let pubkey_or_authority_offset = external_registry_record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + + match external_registry_record.plugin_type { + ExternalPluginAdapterType::LifecycleHook => { + let pubkey = + Pubkey::deserialize(&mut &account.data.borrow()[pubkey_or_authority_offset..])?; + Ok(Self::LifecycleHook(pubkey)) + } + ExternalPluginAdapterType::Oracle => { + let pubkey = + Pubkey::deserialize(&mut &account.data.borrow()[pubkey_or_authority_offset..])?; + Ok(Self::Oracle(pubkey)) + } + ExternalPluginAdapterType::DataStore => { + let authority = Authority::deserialize( + &mut &account.data.borrow()[pubkey_or_authority_offset..], + )?; + Ok(Self::DataStore(authority)) + } + } + } +} + +impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapterKey { + fn from(init_info: &ExternalPluginAdapterInitInfo) -> Self { + match init_info { + ExternalPluginAdapterInitInfo::LifecycleHook(init_info) => { + ExternalPluginAdapterKey::LifecycleHook(init_info.hooked_program) + } + ExternalPluginAdapterInitInfo::Oracle(init_info) => { + ExternalPluginAdapterKey::Oracle(init_info.base_address) + } + ExternalPluginAdapterInitInfo::DataStore(init_info) => { + ExternalPluginAdapterKey::DataStore(init_info.data_authority) + } + } + } +} diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 4a2feca4..8871f1e1 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -1,13 +1,17 @@ -use std::collections::BTreeMap; - +use borsh::{BorshDeserialize, BorshSerialize}; +use modular_bitfield::{bitfield, specifiers::B29}; use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use std::collections::BTreeMap; use crate::{ error::MplCoreError, state::{Authority, Key}, }; -use super::{Plugin, PluginType, RegistryRecord}; +use super::{ + ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, Plugin, PluginType, + RegistryRecord, +}; /// Lifecycle permissions /// Plugins use this field to indicate their permission to approve or deny @@ -24,6 +28,49 @@ pub enum CheckResult { CanForceApprove, } +/// Lifecycle permissions for adapter, third party plugins. +/// Third party plugins use this field to indicate their permission to listen, approve, and/or +/// deny a lifecycle event. +#[derive(BorshDeserialize, BorshSerialize, Eq, PartialEq, Copy, Clone, Debug)] +pub struct ExternalCheckResult { + /// Bitfield for external plugin adapter check results. + pub flags: u32, +} + +impl ExternalCheckResult { + pub(crate) fn none() -> Self { + Self { flags: 0 } + } + + pub(crate) fn can_reject_only() -> Self { + Self { flags: 0x4 } + } +} + +/// Bitfield representation of lifecycle permissions for external plugin adapter, third party plugins. +#[bitfield(bits = 32)] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct ExternalCheckResultBits { + pub can_listen: bool, + pub can_approve: bool, + pub can_reject: bool, + pub empty_bits: B29, +} + +impl From for ExternalCheckResultBits { + fn from(check_result: ExternalCheckResult) -> Self { + ExternalCheckResultBits::from_bytes(check_result.flags.to_le_bytes()) + } +} + +impl From for ExternalCheckResult { + fn from(bits: ExternalCheckResultBits) -> Self { + ExternalCheckResult { + flags: u32::from_le_bytes(bits.into_bytes()), + } + } +} + impl PluginType { /// Check permissions for the add plugin lifecycle event. pub fn check_add_plugin(plugin_type: &PluginType) -> CheckResult { @@ -136,6 +183,30 @@ impl PluginType { _ => CheckResult::None, } } + + /// Check permissions for the add external plugin adapter lifecycle event. + pub fn check_add_external_plugin_adapter(plugin_type: &PluginType) -> CheckResult { + #[allow(clippy::match_single_binding)] + match plugin_type { + _ => CheckResult::None, + } + } + + /// Check permissions for the remove external plugin adapter lifecycle event. + pub fn check_remove_external_plugin_adapter(plugin_type: &PluginType) -> CheckResult { + #[allow(clippy::match_single_binding)] + match plugin_type { + _ => CheckResult::None, + } + } + + /// Check permissions for the update external plugin adapter lifecycle event. + pub fn check_update_external_plugin_adapter(plugin_type: &PluginType) -> CheckResult { + #[allow(clippy::match_single_binding)] + match plugin_type { + _ => CheckResult::None, + } + } } impl Plugin { @@ -521,11 +592,159 @@ impl Plugin { } } } + + /// Validate the add external plugin adapter lifecycle event. + pub(crate) fn validate_add_external_plugin_adapter( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result { + match plugin { + Plugin::Royalties(royalties) => royalties.validate_add_external_plugin_adapter(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_add_external_plugin_adapter(ctx), + Plugin::BurnDelegate(burn) => burn.validate_add_external_plugin_adapter(ctx), + Plugin::TransferDelegate(transfer) => { + transfer.validate_add_external_plugin_adapter(ctx) + } + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_add_external_plugin_adapter(ctx) + } + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_add_external_plugin_adapter(ctx) + } + Plugin::Attributes(attributes) => attributes.validate_add_external_plugin_adapter(ctx), + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_add_external_plugin_adapter(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_add_external_plugin_adapter(ctx) + } + Plugin::Edition(edition) => edition.validate_add_external_plugin_adapter(ctx), + Plugin::MasterEdition(master_edition) => { + master_edition.validate_add_external_plugin_adapter(ctx) + } + Plugin::AddBlocker(add_blocker) => { + add_blocker.validate_add_external_plugin_adapter(ctx) + } + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_add_external_plugin_adapter(ctx) + } + } + } + + /// Validate the remove plugin lifecycle event. + pub(crate) fn validate_remove_external_plugin_adapter( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result { + match plugin { + Plugin::Royalties(royalties) => royalties.validate_remove_external_plugin_adapter(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_remove_external_plugin_adapter(ctx), + Plugin::BurnDelegate(burn) => burn.validate_remove_external_plugin_adapter(ctx), + Plugin::TransferDelegate(transfer) => { + transfer.validate_remove_external_plugin_adapter(ctx) + } + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_remove_external_plugin_adapter(ctx) + } + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_remove_external_plugin_adapter(ctx) + } + Plugin::Attributes(attributes) => { + attributes.validate_remove_external_plugin_adapter(ctx) + } + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_remove_external_plugin_adapter(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_remove_external_plugin_adapter(ctx) + } + Plugin::Edition(edition) => edition.validate_remove_external_plugin_adapter(ctx), + Plugin::MasterEdition(master_edition) => { + master_edition.validate_remove_external_plugin_adapter(ctx) + } + Plugin::AddBlocker(add_blocker) => { + add_blocker.validate_remove_external_plugin_adapter(ctx) + } + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_remove_external_plugin_adapter(ctx) + } + } + } + + /// Route the validation of the update_plugin action to the appropriate plugin. + /// There is no check for updating a plugin because the plugin itself MUST validate the change. + pub(crate) fn validate_update_external_plugin_adapter( + plugin: &Plugin, + ctx: &PluginValidationContext, + ) -> Result { + let resolved_authorities = ctx + .resolved_authorities + .ok_or(MplCoreError::InvalidAuthority)?; + let base_result = if resolved_authorities.contains(ctx.self_authority) { + solana_program::msg!("Base: Approved"); + ValidationResult::Approved + } else { + ValidationResult::Pass + }; + + let result = match plugin { + Plugin::Royalties(royalties) => royalties.validate_update_external_plugin_adapter(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_update_external_plugin_adapter(ctx), + Plugin::BurnDelegate(burn) => burn.validate_update_external_plugin_adapter(ctx), + Plugin::TransferDelegate(transfer) => { + transfer.validate_update_external_plugin_adapter(ctx) + } + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_update_external_plugin_adapter(ctx) + } + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_update_external_plugin_adapter(ctx) + } + Plugin::Attributes(attributes) => { + attributes.validate_update_external_plugin_adapter(ctx) + } + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_update_external_plugin_adapter(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_update_external_plugin_adapter(ctx) + } + Plugin::Edition(edition) => edition.validate_update_external_plugin_adapter(ctx), + Plugin::MasterEdition(master_edition) => { + master_edition.validate_update_external_plugin_adapter(ctx) + } + Plugin::AddBlocker(add_blocker) => { + add_blocker.validate_update_external_plugin_adapter(ctx) + } + Plugin::ImmutableMetadata(immutable_metadata) => { + immutable_metadata.validate_update_external_plugin_adapter(ctx) + } + }?; + + match (&base_result, &result) { + (ValidationResult::Approved, ValidationResult::Approved) => { + Ok(ValidationResult::Approved) + } + (ValidationResult::Approved, ValidationResult::Rejected) => { + Ok(ValidationResult::Rejected) + } + (ValidationResult::Rejected, ValidationResult::Approved) => { + Ok(ValidationResult::Rejected) + } + (ValidationResult::Rejected, ValidationResult::Rejected) => { + Ok(ValidationResult::Rejected) + } + (ValidationResult::Pass, _) => Ok(result), + (ValidationResult::ForceApproved, _) => Ok(ValidationResult::ForceApproved), + (_, ValidationResult::Pass) => Ok(base_result), + (_, ValidationResult::ForceApproved) => Ok(ValidationResult::ForceApproved), + } + } } /// Lifecycle validations /// Plugins utilize this to indicate whether they approve or reject a lifecycle action. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Clone, BorshDeserialize, BorshSerialize)] pub enum ValidationResult { /// The plugin approves the lifecycle action. Approved, @@ -537,9 +756,37 @@ pub enum ValidationResult { ForceApproved, } +/// External plugin adapters lifecycle validations +/// External plugin adapters utilize this to indicate whether they approve or reject a lifecycle action. +#[derive(Eq, PartialEq, Debug, Clone, BorshDeserialize, BorshSerialize)] +pub enum ExternalValidationResult { + /// The plugin approves the lifecycle action. + Approved, + /// The plugin rejects the lifecycle action. + Rejected, + /// The plugin abstains from approving or rejecting the lifecycle action. + Pass, +} + +impl From for ValidationResult { + fn from(result: ExternalValidationResult) -> Self { + match result { + ExternalValidationResult::Approved => Self::Approved, + ExternalValidationResult::Rejected => Self::Rejected, + ExternalValidationResult::Pass => Self::Pass, + } + } +} + /// The required context for a plugin validation. #[allow(dead_code)] pub(crate) struct PluginValidationContext<'a, 'b> { + /// This list of all the accounts passed into the instruction. + pub accounts: &'a [AccountInfo<'a>], + /// The asset account. + pub asset_info: Option<&'a AccountInfo<'a>>, + /// The collection account. + pub collection_info: Option<&'a AccountInfo<'a>>, /// The authority. pub self_authority: &'b Authority, /// The authority account. @@ -570,6 +817,22 @@ pub(crate) trait PluginValidation { Ok(ValidationResult::Pass) } + /// Validate the add plugin lifecycle action. + fn validate_add_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + + /// Validate the remove plugin lifecycle action. + fn validate_remove_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + /// Validate the approve plugin authority lifecycle action. fn validate_approve_plugin_authority( &self, @@ -657,6 +920,14 @@ pub(crate) trait PluginValidation { ) -> Result { Ok(ValidationResult::Pass) } + + /// Validate the update_plugin lifecycle action. + fn validate_update_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } } /// This function iterates through all plugin checks passed in and performs the validation @@ -665,14 +936,18 @@ pub(crate) trait PluginValidation { #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn validate_plugin_checks<'a>( key: Key, + accounts: &'a [AccountInfo<'a>], checks: &BTreeMap, authority: &'a AccountInfo<'a>, new_owner: Option<&'a AccountInfo<'a>>, new_plugin: Option<&Plugin>, - asset: Option<&AccountInfo<'a>>, - collection: Option<&AccountInfo<'a>>, + asset: Option<&'a AccountInfo<'a>>, + collection: Option<&'a AccountInfo<'a>>, resolved_authorities: &[Authority], - validate_fp: fn(&Plugin, &PluginValidationContext) -> Result, + plugin_validate_fp: fn( + &Plugin, + &PluginValidationContext, + ) -> Result, ) -> Result { let mut approved = false; let mut rejected = false; @@ -689,7 +964,10 @@ pub(crate) fn validate_plugin_checks<'a>( _ => unreachable!(), }; - let ctx = PluginValidationContext { + let validation_ctx = PluginValidationContext { + accounts, + asset_info: asset, + collection_info: collection, self_authority: ®istry_record.authority, authority_info: authority, resolved_authorities: Some(resolved_authorities), @@ -697,7 +975,10 @@ pub(crate) fn validate_plugin_checks<'a>( target_plugin: new_plugin, }; - let result = validate_fp(&Plugin::load(account, registry_record.offset)?, &ctx)?; + let result = plugin_validate_fp( + &Plugin::load(account, registry_record.offset)?, + &validation_ctx, + )?; match result { ValidationResult::Rejected => rejected = true, ValidationResult::Approved => approved = true, @@ -715,3 +996,78 @@ pub(crate) fn validate_plugin_checks<'a>( Ok(ValidationResult::Pass) } } + +/// This function iterates through all external plugin adapter checks passed in and performs the validation +/// by deserializing and calling validate on the plugin. +/// The STRONGEST result is returned. +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +pub(crate) fn validate_external_plugin_adapter_checks<'a>( + key: Key, + accounts: &'a [AccountInfo<'a>], + external_checks: &BTreeMap< + ExternalPluginAdapterKey, + (Key, ExternalCheckResultBits, ExternalRegistryRecord), + >, + authority: &'a AccountInfo<'a>, + new_owner: Option<&'a AccountInfo<'a>>, + new_plugin: Option<&Plugin>, + asset: Option<&'a AccountInfo<'a>>, + collection: Option<&'a AccountInfo<'a>>, + resolved_authorities: &[Authority], + external_plugin_adapter_validate_fp: fn( + &ExternalPluginAdapter, + &PluginValidationContext, + ) -> Result, +) -> Result { + let mut approved = false; + for (check_key, check_result, external_registry_record) in external_checks.values() { + if *check_key == key + && (check_result.can_listen() + || check_result.can_approve() + || check_result.can_reject()) + { + let account = match key { + Key::CollectionV1 => collection.ok_or(MplCoreError::InvalidCollection)?, + Key::AssetV1 => asset.ok_or(MplCoreError::InvalidAsset)?, + _ => unreachable!(), + }; + + let validation_ctx = PluginValidationContext { + accounts, + asset_info: asset, + collection_info: collection, + self_authority: &external_registry_record.authority, + authority_info: authority, + resolved_authorities: Some(resolved_authorities), + new_owner, + target_plugin: new_plugin, + }; + + let result = external_plugin_adapter_validate_fp( + &ExternalPluginAdapter::load(account, external_registry_record.offset)?, + &validation_ctx, + )?; + match result { + ValidationResult::Rejected => { + if check_result.can_reject() { + return Ok(ValidationResult::Rejected); + } + } + ValidationResult::Approved => { + if check_result.can_approve() { + approved = true; + } + } + ValidationResult::Pass => continue, + // Force approved will not be possible from external plugin adapters. + ValidationResult::ForceApproved => unreachable!(), + } + } + } + + if approved { + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } +} diff --git a/programs/mpl-core/src/plugins/lifecycle_hook.rs b/programs/mpl-core/src/plugins/lifecycle_hook.rs new file mode 100644 index 00000000..81938d5c --- /dev/null +++ b/programs/mpl-core/src/plugins/lifecycle_hook.rs @@ -0,0 +1,96 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; + +use super::{ + Authority, ExternalCheckResult, ExternalPluginAdapterSchema, ExtraAccount, + HookableLifecycleEvent, PluginValidation, PluginValidationContext, ValidationResult, +}; + +/// Lifecycle hook that CPIs into the `hooked_program`. This hook is used for any lifecycle events +/// that were selected in the `ExternalRegistryRecord` for the plugin. If any extra accounts are +/// present in the `extra_accounts` optional `Vec`, then these accounts are added to the CPI call +/// in the order in which they are in the Vec. Any PDAs in the `Vec` are derived using the hooked +/// program. The hooked program will return a validation result and new data to store at the +/// plugin's data offset (which in the account is immediately after this header). +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LifecycleHook { + /// The `Pubkey` for the hooked program. + pub hooked_program: Pubkey, // 32 + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// The authority of who can update the Lifecycle Hook data. This can be for the purposes + /// of initialization of data, or schema migration. This field cannot be changed after + /// the plugin is added. + pub data_authority: Option, + /// Schema for the data used by the plugin. + pub schema: ExternalPluginAdapterSchema, // 1 +} + +impl LifecycleHook { + /// Updates the lifecycle hook with the new info. + pub fn update(&mut self, info: &LifecycleHookUpdateInfo) { + if let Some(extra_accounts) = &info.extra_accounts { + self.extra_accounts = Some(extra_accounts.clone()); + } + if let Some(schema) = &info.schema { + self.schema = *schema; + } + } +} + +impl PluginValidation for LifecycleHook { + fn validate_add_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_transfer( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } +} + +impl From<&LifecycleHookInitInfo> for LifecycleHook { + fn from(init_info: &LifecycleHookInitInfo) -> Self { + Self { + hooked_program: init_info.hooked_program, + extra_accounts: init_info.extra_accounts.clone(), + data_authority: init_info.data_authority, + schema: init_info.schema.unwrap_or_default(), + } + } +} + +/// Lifecycle hook initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LifecycleHookInitInfo { + /// The `Pubkey` for the hooked program. + pub hooked_program: Pubkey, + /// Initial plugin authority. + pub init_plugin_authority: Option, + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// The authority of who can update the Lifecycle Hook data. This can be for the purposes + /// of initialization of data, or schema migration. This field cannot be changed after + /// the plugin is added. + pub data_authority: Option, + /// Schema for the data used by the plugin. + pub schema: Option, +} + +/// Lifecycle hook update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct LifecycleHookUpdateInfo { + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Option>, + /// The extra accounts to use for the lifecycle hook. + pub extra_accounts: Option>, + /// Schema for the data used by the plugin. + pub schema: Option, +} diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index ba778d3b..93ef8da7 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -1,11 +1,18 @@ mod add_blocker; mod attributes; mod burn_delegate; +mod data_store; mod edition; +mod external_plugin_adapters; mod freeze_delegate; mod immutable_metadata; mod lifecycle; + +mod lifecycle_hook; +mod oracle; + mod master_edition; + mod permanent_burn_delegate; mod permanent_freeze_delegate; mod permanent_transfer_delegate; @@ -19,12 +26,15 @@ mod utils; pub use add_blocker::*; pub use attributes::*; pub use burn_delegate::*; +pub use data_store::*; pub use edition::*; +pub use external_plugin_adapters::*; pub use freeze_delegate::*; pub use immutable_metadata::*; pub use lifecycle::*; +pub use lifecycle_hook::*; pub use master_edition::*; -use num_derive::ToPrimitive; +pub use oracle::*; pub use permanent_burn_delegate::*; pub use permanent_freeze_delegate::*; pub use permanent_transfer_delegate::*; @@ -35,11 +45,11 @@ pub use transfer::*; pub use update_delegate::*; pub use utils::*; +use borsh::{BorshDeserialize, BorshSerialize}; +use num_derive::ToPrimitive; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, }; - -use borsh::{BorshDeserialize, BorshSerialize}; use strum::EnumCount; use crate::{ @@ -105,7 +115,7 @@ impl Plugin { impl Compressible for Plugin {} -/// List of First Party Plugin types. +/// List of first party plugin types. #[repr(C)] #[derive( Clone, diff --git a/programs/mpl-core/src/plugins/oracle.rs b/programs/mpl-core/src/plugins/oracle.rs new file mode 100644 index 00000000..a0a0c1c7 --- /dev/null +++ b/programs/mpl-core/src/plugins/oracle.rs @@ -0,0 +1,215 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; + +use crate::error::MplCoreError; + +use super::{ + Authority, ExternalCheckResult, ExternalValidationResult, ExtraAccount, HookableLifecycleEvent, + PluginValidation, PluginValidationContext, ValidationResult, +}; + +/// Oracle plugin that allows getting a `ValidationResult` for a lifecycle event from an arbitrary +/// account either specified by or derived from the `base_address`. This hook is used for any +/// lifecycle events that were selected in the `ExternalRegistryRecord` for the plugin. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct Oracle { + /// The address of the oracle, or if using the `pda` option, a program ID from which + /// to derive a PDA. + pub base_address: Pubkey, + /// Optional account specification (PDA derived from `base_address` or other available account + /// specifications). Note that even when this configuration is used there is still only one + /// Oracle account specified by the adapter. + pub base_address_config: Option, + /// Validation results offset in the Oracle account. Default is `ValidationResultsOffset::NoOffset`. + pub results_offset: ValidationResultsOffset, +} + +impl Oracle { + /// Updates the oracle with the new info. + pub fn update(&mut self, info: &OracleUpdateInfo) { + if let Some(base_address_config) = &info.base_address_config { + self.base_address_config = Some(base_address_config.clone()); + } + if let Some(results_offset) = &info.results_offset { + self.results_offset = *results_offset; + } + } +} + +impl PluginValidation for Oracle { + fn validate_add_external_plugin_adapter( + &self, + _ctx: &PluginValidationContext, + ) -> Result { + Ok(ValidationResult::Pass) + } + + fn validate_create( + &self, + ctx: &PluginValidationContext, + ) -> Result { + self.validate_helper(ctx, HookableLifecycleEvent::Create) + } + + fn validate_transfer( + &self, + ctx: &PluginValidationContext, + ) -> Result { + self.validate_helper(ctx, HookableLifecycleEvent::Transfer) + } + + fn validate_burn( + &self, + ctx: &PluginValidationContext, + ) -> Result { + self.validate_helper(ctx, HookableLifecycleEvent::Burn) + } + + fn validate_update( + &self, + ctx: &PluginValidationContext, + ) -> Result { + self.validate_helper(ctx, HookableLifecycleEvent::Update) + } +} + +impl Oracle { + fn validate_helper( + &self, + ctx: &PluginValidationContext, + event: HookableLifecycleEvent, + ) -> Result { + let oracle_account = match &self.base_address_config { + None => self.base_address, + Some(extra_account) => extra_account.derive(&self.base_address, ctx)?, + }; + + let oracle_account = ctx + .accounts + .iter() + .find(|account| *account.key == oracle_account) + .ok_or(MplCoreError::MissingExternalPluginAdapterAccount)?; + + let offset = self.results_offset.to_offset_usize(); + + let oracle_data = (*oracle_account.data).borrow(); + let mut oracle_data_slice = oracle_data + .get(offset..) + .ok_or(MplCoreError::InvalidOracleAccountData)?; + + if oracle_data_slice.len() < OracleValidation::serialized_size() { + return Err(MplCoreError::InvalidOracleAccountData.into()); + } + + let validation_result = OracleValidation::deserialize(&mut oracle_data_slice) + .map_err(|_| MplCoreError::InvalidOracleAccountData)?; + + match validation_result { + OracleValidation::Uninitialized => Err(MplCoreError::UninitializedOracleAccount.into()), + OracleValidation::V1 { + create, + transfer, + burn, + update, + } => match event { + HookableLifecycleEvent::Create => Ok(ValidationResult::from(create)), + HookableLifecycleEvent::Transfer => Ok(ValidationResult::from(transfer)), + HookableLifecycleEvent::Burn => Ok(ValidationResult::from(burn)), + HookableLifecycleEvent::Update => Ok(ValidationResult::from(update)), + }, + } + } +} + +impl From<&OracleInitInfo> for Oracle { + fn from(init_info: &OracleInitInfo) -> Self { + Self { + base_address: init_info.base_address, + base_address_config: init_info.base_address_config.clone(), + results_offset: init_info + .results_offset + .unwrap_or(ValidationResultsOffset::NoOffset), + } + } +} + +/// Oracle initialization info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct OracleInitInfo { + /// The address of the oracle, or if using the `pda` option, a program ID from which + /// to derive a PDA. + pub base_address: Pubkey, + /// Initial plugin authority. + pub init_plugin_authority: Option, + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + /// Optional account specification (PDA derived from `base_address` or other available account + /// specifications). Note that even when this configuration is used there is still only one + /// Oracle account specified by the adapter. + pub base_address_config: Option, + /// Optional offset for validation results struct used in Oracle account. Default + /// is `ValidationResultsOffset::NoOffset`. + pub results_offset: Option, +} + +/// Oracle update info. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub struct OracleUpdateInfo { + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Option>, + /// Optional account specification (PDA derived from `base_address` or other available account + /// specifications). Note that even when this configuration is used there is still only one + /// Oracle account specified by the adapter. + pub base_address_config: Option, + /// Optional offset for validation results struct used in Oracle account. Default + /// is `ValidationResultsOffset::NoOffset`. + pub results_offset: Option, +} + +/// Offset to where the validation results struct is located in an Oracle account. +#[derive(Copy, Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum ValidationResultsOffset { + /// The validation struct is located at the beginning of the account. + NoOffset, + /// The Oracle is an Anchor account so the validation struct is located after an 8-byte + /// account discriminator. + Anchor, + /// The validation struct is located at the specified offset within the account. + Custom(usize), +} + +impl ValidationResultsOffset { + /// Convert the `ValidationResultsOffset` to the correct offset value as a `usize`. + pub fn to_offset_usize(&self) -> usize { + match self { + Self::NoOffset => 0, + Self::Anchor => 8, + Self::Custom(offset) => *offset, + } + } +} + +/// Validation results struct for an Oracle account. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum OracleValidation { + /// Uninitialized data. This is intended to prevent leaving an account zeroed out by mistake. + Uninitialized, + /// Version 1 of the format. + V1 { + /// Validation for the the create lifecycle action. + create: ExternalValidationResult, + /// Validation for the transfer lifecycle action. + transfer: ExternalValidationResult, + /// Validation for the burn lifecycle action. + burn: ExternalValidationResult, + /// Validation for the update lifecycle action. + update: ExternalValidationResult, + }, +} + +impl OracleValidation { + /// Borsh- and Anchor-serialized size of the `OracleValidation` struct. + pub fn serialized_size() -> usize { + 5 + } +} diff --git a/programs/mpl-core/src/plugins/plugin_registry.rs b/programs/mpl-core/src/plugins/plugin_registry.rs index 9ab915dc..e2c9f6cd 100644 --- a/programs/mpl-core/src/plugins/plugin_registry.rs +++ b/programs/mpl-core/src/plugins/plugin_registry.rs @@ -1,21 +1,28 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use std::{cmp::Ordering, collections::BTreeMap}; -use crate::state::{Authority, DataBlob, Key, SolanaAccount}; +use crate::{ + plugins::validate_lifecycle_checks, + state::{Authority, DataBlob, Key, SolanaAccount}, +}; -use super::{CheckResult, PluginType}; +use super::{ + CheckResult, ExternalCheckResult, ExternalCheckResultBits, ExternalPluginAdapterKey, + ExternalPluginAdapterType, ExternalPluginAdapterUpdateInfo, HookableLifecycleEvent, PluginType, +}; /// The Plugin Registry stores a record of all plugins, their location, and their authorities. #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct PluginRegistryV1 { - /// The Discriminator of the header which doubles as a Plugin metadata version. + /// The Discriminator of the header which doubles as a plugin metadata version. pub key: Key, // 1 /// The registry of all plugins. pub registry: Vec, // 4 - /// The registry of all external, third party, plugins. - pub external_plugins: Vec, // 4 + /// The registry of all adapter, third party, plugins. + pub external_registry: Vec, // 4 } impl PluginRegistryV1 { @@ -33,6 +40,38 @@ impl PluginRegistryV1 { ); } } + + pub(crate) fn check_adapter_registry( + &self, + account: &AccountInfo, + key: Key, + lifecycle_event: &HookableLifecycleEvent, + result: &mut BTreeMap< + ExternalPluginAdapterKey, + (Key, ExternalCheckResultBits, ExternalRegistryRecord), + >, + ) -> ProgramResult { + for record in &self.external_registry { + if let Some(lifecycle_checks) = &record.lifecycle_checks { + for (event, check_result) in lifecycle_checks { + if event == lifecycle_event { + let plugin_key = ExternalPluginAdapterKey::from_record(account, record)?; + + result.insert( + plugin_key, + ( + key, + ExternalCheckResultBits::from(*check_result), + record.clone(), + ), + ); + } + } + } + } + + Ok(()) + } } impl DataBlob for PluginRegistryV1 { @@ -51,7 +90,7 @@ impl SolanaAccount for PluginRegistryV1 { } } -/// A simple type to store the mapping of Plugin type to Plugin data. +/// A simple type to store the mapping of plugin type to plugin data. #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryRecord { @@ -70,12 +109,43 @@ impl RegistryRecord { } } -/// A simple type to store the mapping of external Plugin authority to Plugin data. +/// A type to store the mapping of third party plugin type to third party plugin header and data. #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] -pub struct ExternalPluginRecord { - /// The authority of the external plugin. +pub struct ExternalRegistryRecord { + /// The adapter, third party plugin type. + pub plugin_type: ExternalPluginAdapterType, + /// The authority of the external plugin adapter. pub authority: Authority, + /// The lifecyle events for which the the external plugin adapter is active. + pub lifecycle_checks: Option>, /// The offset to the plugin in the account. - pub offset: usize, + pub offset: usize, // 8 + /// For plugins with data, the offset to the data in the account. + pub data_offset: Option, + /// For plugins with data, the length of the data in the account. + pub data_len: Option, +} + +impl ExternalRegistryRecord { + /// Update the adapter registry record with the new info, if relevant. + pub fn update(&mut self, update_info: &ExternalPluginAdapterUpdateInfo) -> ProgramResult { + match update_info { + ExternalPluginAdapterUpdateInfo::LifecycleHook(update_info) => { + if let Some(checks) = &update_info.lifecycle_checks { + validate_lifecycle_checks(checks, false)?; + self.lifecycle_checks = update_info.lifecycle_checks.clone() + } + } + ExternalPluginAdapterUpdateInfo::Oracle(update_info) => { + if let Some(checks) = &update_info.lifecycle_checks { + validate_lifecycle_checks(checks, true)?; + self.lifecycle_checks = update_info.lifecycle_checks.clone() + } + } + _ => (), + } + + Ok(()) + } } diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index 3c47252a..d124862f 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -1,16 +1,22 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::sol_memcpy, + program_memory::sol_memcpy, pubkey::Pubkey, }; +use std::collections::HashSet; use crate::{ error::MplCoreError, + plugins::{ExternalCheckResult, HookableLifecycleEvent}, state::{AssetV1, Authority, CoreAsset, DataBlob, Key, SolanaAccount}, utils::resize_or_reallocate_account, }; -use super::{Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, RegistryRecord}; +use super::{ + ExternalPluginAdapter, ExternalPluginAdapterInitInfo, ExternalPluginAdapterKey, + ExternalPluginAdapterType, ExternalRegistryRecord, Plugin, PluginHeaderV1, PluginRegistryV1, + PluginType, RegistryRecord, +}; /// Create plugin header and registry if it doesn't exist pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( @@ -31,7 +37,7 @@ pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( let registry = PluginRegistryV1 { key: Key::PluginRegistryV1, registry: vec![], - external_plugins: vec![], + external_registry: vec![], }; resize_or_reallocate_account( @@ -56,7 +62,7 @@ pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( /// Create plugin header and registry pub fn create_plugin_meta<'a, T: SolanaAccount + DataBlob>( - asset: T, + asset: &T, account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, @@ -71,7 +77,7 @@ pub fn create_plugin_meta<'a, T: SolanaAccount + DataBlob>( let registry = PluginRegistryV1 { key: Key::PluginRegistryV1, registry: vec![], - external_plugins: vec![], + external_registry: vec![], }; resize_or_reallocate_account( @@ -174,6 +180,43 @@ pub fn fetch_wrapped_plugin( Ok((registry_record.authority, plugin)) } +/// Fetch the external plugin adapter from the registry. +pub fn fetch_wrapped_external_plugin_adapter( + account: &AccountInfo, + core: Option<&T>, + plugin_key: &ExternalPluginAdapterKey, +) -> Result<(Authority, ExternalPluginAdapter), ProgramError> { + let size = match core { + Some(core) => core.get_size(), + None => { + let asset = T::load(account, 0)?; + + if asset.get_size() == account.data_len() { + return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); + } + + asset.get_size() + } + }; + + let header = PluginHeaderV1::load(account, size)?; + let plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; + + // Find the plugin in the registry. + let result = find_external_plugin_adapter(&plugin_registry, plugin_key, account)?; + + if let (_, Some(record)) = result { + // Deserialize the plugin. + let plugin = + ExternalPluginAdapter::deserialize(&mut &(*account.data).borrow()[record.offset..])?; + + // Return the plugin and its authority. + Ok((record.authority, plugin)) + } else { + Err(MplCoreError::ExternalPluginAdapterNotFound.into()) + } +} + /// Fetch the plugin registry. pub fn fetch_plugins(account: &AccountInfo) -> Result, ProgramError> { let asset = AssetV1::load(account, 0)?; @@ -226,7 +269,7 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( // You cannot add a duplicate plugin. if plugin_registry .registry - .iter_mut() + .iter() .any(|record| record.plugin_type == plugin_type) { return Err(MplCoreError::PluginAlreadyExists.into()); @@ -266,6 +309,139 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( Ok(()) } +/// Add an external plugin adapter to the registry and initialize it. +pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( + init_info: &ExternalPluginAdapterInitInfo, + plugin_header: &mut PluginHeaderV1, + plugin_registry: &mut PluginRegistryV1, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + let core = T::load(account, 0)?; + let header_offset = core.get_size(); + let plugin_type = init_info.into(); + + // Note currently we are blocking adding LifecycleHook and DataStore external plugin adapters as they + // are still in development. + match init_info { + ExternalPluginAdapterInitInfo::LifecycleHook(_) + | ExternalPluginAdapterInitInfo::DataStore(_) => { + return Err(MplCoreError::NotAvailable.into()); + } + ExternalPluginAdapterInitInfo::Oracle(_) => (), + } + + // You cannot add a duplicate plugin. + for record in plugin_registry.external_registry.iter() { + if ExternalPluginAdapterKey::from_record(account, record)? + == ExternalPluginAdapterKey::from(init_info) + { + return Err(MplCoreError::ExternalPluginAdapterAlreadyExists.into()); + } + } + + let (authority, lifecycle_checks) = match init_info { + ExternalPluginAdapterInitInfo::LifecycleHook(init_info) => { + validate_lifecycle_checks(&init_info.lifecycle_checks, false)?; + ( + init_info.init_plugin_authority, + Some(init_info.lifecycle_checks.clone()), + ) + } + ExternalPluginAdapterInitInfo::Oracle(init_info) => { + validate_lifecycle_checks(&init_info.lifecycle_checks, true)?; + ( + init_info.init_plugin_authority, + Some(init_info.lifecycle_checks.clone()), + ) + } + ExternalPluginAdapterInitInfo::DataStore(init_info) => { + (init_info.init_plugin_authority, None) + } + }; + + let old_registry_offset = plugin_header.plugin_registry_offset; + + let mut new_registry_record = ExternalRegistryRecord { + plugin_type, + authority: authority.unwrap_or(Authority::UpdateAuthority), + lifecycle_checks: lifecycle_checks.clone(), + offset: old_registry_offset, + data_offset: None, + data_len: None, + }; + + let mut plugin = ExternalPluginAdapter::from(init_info); + + // If the plugin is a LifecycleHook or DataStore, then we need to set the data offset and length. + match &mut plugin { + ExternalPluginAdapter::LifecycleHook(_) | ExternalPluginAdapter::DataStore(_) => { + new_registry_record.data_offset = Some(old_registry_offset); + new_registry_record.data_len = Some(0); + } + _ => {} + }; + + let plugin_metadata = plugin.try_to_vec()?; + let plugin_size = plugin_metadata.len(); + + let size_increase = plugin_size + .checked_add(new_registry_record.try_to_vec()?.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + let new_registry_offset = plugin_header + .plugin_registry_offset + .checked_add(plugin_size) + .ok_or(MplCoreError::NumericalOverflow)?; + + plugin_header.plugin_registry_offset = new_registry_offset; + + plugin_registry.external_registry.push(new_registry_record); + + let new_size = account + .data_len() + .checked_add(size_increase) + .ok_or(MplCoreError::NumericalOverflow)?; + + resize_or_reallocate_account(account, payer, system_program, new_size)?; + plugin_header.save(account, header_offset)?; + plugin.save(account, old_registry_offset)?; + plugin_registry.save(account, new_registry_offset)?; + + Ok(()) +} + +pub(crate) fn validate_lifecycle_checks( + lifecycle_checks: &Vec<(HookableLifecycleEvent, ExternalCheckResult)>, + can_reject_only: bool, +) -> ProgramResult { + if lifecycle_checks.is_empty() { + return Err(MplCoreError::RequiresLifecycleCheck.into()); + } + + let mut seen_events = HashSet::new(); + if !lifecycle_checks + .iter() + .all(|(event, _)| seen_events.insert(event)) + { + // If `insert` returns false, it means the event was already in the set, indicating a duplicate + return Err(MplCoreError::DuplicateLifecycleChecks.into()); + } + + if can_reject_only { + let reject_only = ExternalCheckResult::can_reject_only(); + if lifecycle_checks + .iter() + .any(|(_, result)| *result != reject_only) + { + return Err(MplCoreError::OracleCanRejectOnly.into()); + } + } + + Ok(()) +} + /// Remove a plugin from the registry and delete it. pub fn delete_plugin<'a, T: DataBlob>( plugin_type: &PluginType, @@ -336,6 +512,12 @@ pub fn delete_plugin<'a, T: DataBlob>( } } + for record in &mut plugin_registry.external_registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + plugin_registry.save(account, new_registry_offset)?; resize_or_reallocate_account(account, payer, system_program, new_size)?; @@ -346,6 +528,90 @@ pub fn delete_plugin<'a, T: DataBlob>( Ok(()) } +/// Remove a plugin from the registry and delete it. +pub fn delete_external_plugin_adapter<'a, T: DataBlob>( + plugin_key: &ExternalPluginAdapterKey, + asset: &T, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + if asset.get_size() == account.data_len() { + return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); + } + + //TODO: Bytemuck this. + let mut header = PluginHeaderV1::load(account, asset.get_size())?; + let mut plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; + + let result = find_external_plugin_adapter(&plugin_registry, plugin_key, account)?; + + if let (Some(index), _) = result { + let registry_record = plugin_registry.external_registry.remove(index); + let serialized_registry_record = registry_record.try_to_vec()?; + + // Fetch the offset of the plugin to be removed. + let plugin_offset = registry_record.offset; + let plugin = ExternalPluginAdapter::load(account, plugin_offset)?; + let serialized_plugin = plugin.try_to_vec()?; + + // Get the offset of the plugin after the one being removed. + let next_plugin_offset = plugin_offset + .checked_add(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + // Calculate the new size of the account. + let new_size = account + .data_len() + .checked_sub(serialized_registry_record.len()) + .ok_or(MplCoreError::NumericalOverflow)? + .checked_sub(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + let new_registry_offset = header + .plugin_registry_offset + .checked_sub(serialized_plugin.len()) + .ok_or(MplCoreError::NumericalOverflow)?; + + let data_to_move = header + .plugin_registry_offset + .checked_sub(next_plugin_offset) + .ok_or(MplCoreError::NumericalOverflow)?; + + //TODO: This is memory intensive, we should use memmove instead probably. + let src = account.data.borrow()[next_plugin_offset..].to_vec(); + sol_memcpy( + &mut account.data.borrow_mut()[plugin_offset..], + &src, + data_to_move, + ); + + header.plugin_registry_offset = new_registry_offset; + header.save(account, asset.get_size())?; + + // Move offsets for existing registry records. + for record in &mut plugin_registry.external_registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + + for record in &mut plugin_registry.registry { + if plugin_offset < record.offset { + record.offset -= serialized_plugin.len() + } + } + + plugin_registry.save(account, new_registry_offset)?; + + resize_or_reallocate_account(account, payer, system_program, new_size)?; + } else { + return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); + } + + Ok(()) +} + /// Add an authority to a plugin. #[allow(clippy::too_many_arguments)] pub fn approve_authority_on_plugin<'a, T: CoreAsset>( @@ -411,3 +677,48 @@ pub fn revoke_authority_on_plugin<'a>( Ok(()) } + +pub(crate) fn find_external_plugin_adapter<'b>( + plugin_registry: &'b PluginRegistryV1, + plugin_key: &ExternalPluginAdapterKey, + account: &AccountInfo<'_>, +) -> Result<(Option, Option<&'b ExternalRegistryRecord>), ProgramError> { + let mut result = (None, None); + for (i, record) in plugin_registry.external_registry.iter().enumerate() { + if record.plugin_type == ExternalPluginAdapterType::from(plugin_key) + && (match plugin_key { + ExternalPluginAdapterKey::LifecycleHook(address) + | ExternalPluginAdapterKey::Oracle(address) => { + let pubkey_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + address + == &match Pubkey::deserialize(&mut &account.data.borrow()[pubkey_offset..]) + { + Ok(address) => address, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } + ExternalPluginAdapterKey::DataStore(authority) => { + let authority_offset = record + .offset + .checked_add(1) + .ok_or(MplCoreError::NumericalOverflow)?; + authority + == &match Authority::deserialize( + &mut &account.data.borrow()[authority_offset..], + ) { + Ok(authority) => authority, + Err(_) => return Err(MplCoreError::DeserializationError.into()), + } + } + }) + { + result = (Some(i), Some(record)); + break; + } + } + + Ok(result) +} diff --git a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs new file mode 100644 index 00000000..85cd0733 --- /dev/null +++ b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs @@ -0,0 +1,192 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; + +use crate::{ + error::MplCoreError, + instruction::accounts::{ + AddCollectionExternalPluginAdapterV1Accounts, AddExternalPluginAdapterV1Accounts, + }, + plugins::{ + create_meta_idempotent, initialize_external_plugin_adapter, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, Plugin, PluginType, PluginValidationContext, + ValidationResult, + }, + state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, + utils::{ + load_key, resolve_authority, validate_asset_permissions, validate_collection_permissions, + }, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddExternalPluginAdapterV1Args { + /// External plugin adapter initialization info. + pub init_info: ExternalPluginAdapterInitInfo, +} + +pub(crate) fn add_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: AddExternalPluginAdapterV1Args, +) -> ProgramResult { + let ctx = AddExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + if let Key::HashedAssetV1 = load_key(ctx.accounts.asset, 0)? { + msg!("Error: Adding plugin to compressed is not available"); + return Err(MplCoreError::NotAvailable.into()); + } + + let validation_ctx = PluginValidationContext { + accounts, + asset_info: Some(ctx.accounts.asset), + collection_info: ctx.accounts.collection, + self_authority: &Authority::UpdateAuthority, + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + + if ExternalPluginAdapter::validate_add_external_plugin_adapter( + &ExternalPluginAdapter::from(&args.init_info), + &validation_ctx, + )? == ValidationResult::Rejected + { + return Err(MplCoreError::InvalidAuthority.into()); + } + + let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); + + // Validate asset permissions. + let (mut asset, _, _) = validate_asset_permissions( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + None, + Some(&external_plugin_adapter), + AssetV1::check_add_external_plugin_adapter, + CollectionV1::check_add_external_plugin_adapter, + PluginType::check_add_external_plugin_adapter, + AssetV1::validate_add_external_plugin_adapter, + CollectionV1::validate_add_external_plugin_adapter, + Plugin::validate_add_external_plugin_adapter, + None, + None, + )?; + + // Increment sequence number and save only if it is `Some(_)`. + asset.increment_seq_and_save(ctx.accounts.asset)?; + + process_add_external_plugin_adapter::( + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + &args.init_info, + ) +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct AddCollectionExternalPluginAdapterV1Args { + /// External plugin adapter initialization info. + pub init_info: ExternalPluginAdapterInitInfo, +} + +pub(crate) fn add_collection_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: AddCollectionExternalPluginAdapterV1Args, +) -> ProgramResult { + let ctx = AddCollectionExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: Some(ctx.accounts.collection), + self_authority: &Authority::UpdateAuthority, + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + + if ExternalPluginAdapter::validate_add_external_plugin_adapter( + &ExternalPluginAdapter::from(&args.init_info), + &validation_ctx, + )? == ValidationResult::Rejected + { + return Err(MplCoreError::InvalidAuthority.into()); + } + + let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); + + // Validate collection permissions. + let _ = validate_collection_permissions( + accounts, + authority, + ctx.accounts.collection, + None, + Some(&external_plugin_adapter), + CollectionV1::check_add_external_plugin_adapter, + PluginType::check_add_external_plugin_adapter, + CollectionV1::validate_add_external_plugin_adapter, + Plugin::validate_add_external_plugin_adapter, + None, + None, + )?; + + process_add_external_plugin_adapter::( + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + &args.init_info, + ) +} + +fn process_add_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, + init_info: &ExternalPluginAdapterInitInfo, +) -> ProgramResult { + let (_, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::(account, payer, system_program)?; + initialize_external_plugin_adapter::( + init_info, + &mut plugin_header, + &mut plugin_registry, + account, + payer, + system_program, + )?; + Ok(()) +} diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index 61704ba1..e2b75c96 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -55,6 +55,9 @@ pub(crate) fn add_plugin<'a>( //TODO: Seed with Rejected let validation_ctx = PluginValidationContext { + accounts, + asset_info: Some(ctx.accounts.asset), + collection_info: ctx.accounts.collection, self_authority: &args.init_authority.unwrap_or(args.plugin.manager()), authority_info: authority, resolved_authorities: None, @@ -67,17 +70,21 @@ pub(crate) fn add_plugin<'a>( // Validate asset permissions. let (mut asset, _, _) = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, Some(&args.plugin), + None, AssetV1::check_add_plugin, CollectionV1::check_add_plugin, PluginType::check_add_plugin, AssetV1::validate_add_plugin, CollectionV1::validate_add_plugin, Plugin::validate_add_plugin, + None, + None, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -119,15 +126,17 @@ pub(crate) fn add_collection_plugin<'a>( } } - let validation_context = PluginValidationContext { + let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: Some(ctx.accounts.collection), self_authority: &args.init_authority.unwrap_or(args.plugin.manager()), authority_info: authority, resolved_authorities: None, new_owner: None, target_plugin: Some(&args.plugin), }; - if Plugin::validate_add_plugin(&args.plugin, &validation_context)? == ValidationResult::Rejected - { + if Plugin::validate_add_plugin(&args.plugin, &validation_ctx)? == ValidationResult::Rejected { return Err(MplCoreError::InvalidAuthority.into()); } @@ -138,13 +147,17 @@ pub(crate) fn add_collection_plugin<'a>( // Validate collection permissions. let _ = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, Some(&args.plugin), + None, CollectionV1::check_add_plugin, PluginType::check_add_plugin, CollectionV1::validate_add_plugin, Plugin::validate_add_plugin, + None, + None, )?; process_add_plugin::( diff --git a/programs/mpl-core/src/processor/approve_plugin_authority.rs b/programs/mpl-core/src/processor/approve_plugin_authority.rs index 9ddf456f..c115c67b 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -51,17 +51,21 @@ pub(crate) fn approve_plugin_authority<'a>( // Validate asset permissions. let (mut asset, _, _) = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, Some(&plugin), + None, AssetV1::check_approve_plugin_authority, CollectionV1::check_approve_plugin_authority, PluginType::check_approve_plugin_authority, AssetV1::validate_approve_plugin_authority, CollectionV1::validate_approve_plugin_authority, Plugin::validate_approve_plugin_authority, + None, + None, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -108,13 +112,17 @@ pub(crate) fn approve_collection_plugin_authority<'a>( // Validate collection permissions. let _ = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, Some(&plugin), + None, CollectionV1::check_approve_plugin_authority, PluginType::check_approve_plugin_authority, CollectionV1::validate_approve_plugin_authority, Plugin::validate_approve_plugin_authority, + None, + None, )?; process_approve_plugin_authority::( diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index 31297e73..2e78c4cb 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -5,7 +5,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::{BurnCollectionV1Accounts, BurnV1Accounts}, - plugins::{Plugin, PluginType}, + plugins::{ExternalPluginAdapter, HookableLifecycleEvent, Plugin, PluginType}, state::{AssetV1, CollectionV1, CompressionProof, Key, SolanaAccount, Wrappable}, utils::{ close_program_account, load_key, rebuild_account_state_from_proof_data, resolve_authority, @@ -84,17 +84,21 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnV1Args) -> Pro // Validate asset permissions. let _ = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, None, + None, AssetV1::check_burn, CollectionV1::check_burn, PluginType::check_burn, AssetV1::validate_burn, CollectionV1::validate_burn, Plugin::validate_burn, + Some(ExternalPluginAdapter::validate_burn), + Some(HookableLifecycleEvent::Burn), )?; process_burn(ctx.accounts.asset, authority)?; @@ -129,13 +133,17 @@ pub(crate) fn burn_collection<'a>( // Validate collection permissions. let _ = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, None, + None, CollectionV1::check_burn, PluginType::check_burn, CollectionV1::validate_burn, Plugin::validate_burn, + Some(ExternalPluginAdapter::validate_burn), + Some(HookableLifecycleEvent::Burn), )?; process_burn(ctx.accounts.collection, authority) diff --git a/programs/mpl-core/src/processor/compress.rs b/programs/mpl-core/src/processor/compress.rs index 3207f142..47a40b4f 100644 --- a/programs/mpl-core/src/processor/compress.rs +++ b/programs/mpl-core/src/processor/compress.rs @@ -44,17 +44,21 @@ pub(crate) fn compress<'a>( // Validate asset permissions. let _ = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, None, + None, AssetV1::check_compress, CollectionV1::check_compress, PluginType::check_compress, AssetV1::validate_compress, CollectionV1::validate_compress, Plugin::validate_compress, + None, + None, )?; // Compress the asset and plugin registry into account space. diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 04776856..5ecea2f2 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -7,12 +7,16 @@ use solana_program::{ use crate::{ error::MplCoreError, - instruction::accounts::CreateV1Accounts, + instruction::accounts::CreateV2Accounts, plugins::{ - create_plugin_meta, initialize_plugin, CheckResult, Plugin, PluginAuthorityPair, - PluginType, PluginValidationContext, ValidationResult, + create_meta_idempotent, create_plugin_meta, initialize_external_plugin_adapter, + initialize_plugin, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair, PluginType, + PluginValidationContext, ValidationResult, + }, + state::{ + AssetV1, Authority, CollectionV1, DataState, SolanaAccount, UpdateAuthority, COLLECT_AMOUNT, }, - state::{AssetV1, CollectionV1, DataState, SolanaAccount, UpdateAuthority, COLLECT_AMOUNT}, utils::resolve_authority, }; @@ -25,9 +29,41 @@ pub(crate) struct CreateV1Args { pub(crate) plugins: Option>, } -pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateV1Args) -> ProgramResult { +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct CreateV2Args { + pub(crate) data_state: DataState, + pub(crate) name: String, + pub(crate) uri: String, + pub(crate) plugins: Option>, + pub(crate) external_plugin_adapters: Option>, +} + +impl From for CreateV2Args { + fn from(item: CreateV1Args) -> Self { + CreateV2Args { + data_state: item.data_state, + name: item.name, + uri: item.uri, + plugins: item.plugins, + external_plugin_adapters: None, + } + } +} + +pub(crate) fn create_v1<'a>(accounts: &'a [AccountInfo<'a>], args: CreateV1Args) -> ProgramResult { + process_create(accounts, CreateV2Args::from(args)) +} +pub(crate) fn create_v2<'a>(accounts: &'a [AccountInfo<'a>], args: CreateV2Args) -> ProgramResult { + process_create(accounts, args) +} + +pub(crate) fn process_create<'a>( + accounts: &'a [AccountInfo<'a>], + args: CreateV2Args, +) -> ProgramResult { // Accounts. - let ctx = CreateV1Accounts::context(accounts)?; + let ctx = CreateV2Accounts::context(accounts)?; let rent = Rent::get()?; // Guards. @@ -114,52 +150,103 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateV1Args) -> serialized_data.len(), ); - if let (Some(plugins), DataState::AccountState) = (args.plugins, args.data_state) { - if !plugins.is_empty() { - let (mut plugin_header, mut plugin_registry) = create_plugin_meta::( - new_asset, - ctx.accounts.asset, - ctx.accounts.payer, - ctx.accounts.system_program, - )?; - let mut approved = true; - let mut force_approved = false; - for plugin in &plugins { - // TODO move into plugin validation when asset/collection is part of validation context - let plugin_type = PluginType::from(&plugin.plugin); - if plugin_type == PluginType::MasterEdition { - return Err(MplCoreError::InvalidPlugin.into()); - } - if PluginType::check_create(&PluginType::from(&plugin.plugin)) != CheckResult::None - { - let validation_ctx = PluginValidationContext { - self_authority: &plugin.authority.unwrap_or(plugin.plugin.manager()), - authority_info: authority, - resolved_authorities: None, - new_owner: None, - target_plugin: None, - }; - match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { - ValidationResult::Rejected => approved = false, - ValidationResult::ForceApproved => force_approved = true, - _ => (), - }; - } - initialize_plugin::( - &plugin.plugin, - &plugin.authority.unwrap_or(plugin.plugin.manager()), - &mut plugin_header, - &mut plugin_registry, + if args.data_state == DataState::AccountState { + let mut approved = true; + let mut force_approved = false; + + if let Some(plugins) = args.plugins { + if !plugins.is_empty() { + let (mut plugin_header, mut plugin_registry) = create_plugin_meta::( + &new_asset, ctx.accounts.asset, ctx.accounts.payer, ctx.accounts.system_program, )?; + for plugin in &plugins { + // TODO move into plugin validation when asset/collection is part of validation context + let plugin_type = PluginType::from(&plugin.plugin); + if plugin_type == PluginType::MasterEdition { + return Err(MplCoreError::InvalidPlugin.into()); + } + if PluginType::check_create(&PluginType::from(&plugin.plugin)) + != CheckResult::None + { + let validation_ctx = PluginValidationContext { + accounts, + asset_info: Some(ctx.accounts.asset), + collection_info: ctx.accounts.collection, + self_authority: &plugin.authority.unwrap_or(plugin.plugin.manager()), + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { + ValidationResult::Rejected => approved = false, + ValidationResult::ForceApproved => force_approved = true, + _ => (), + }; + } + initialize_plugin::( + &plugin.plugin, + &plugin.authority.unwrap_or(plugin.plugin.manager()), + &mut plugin_header, + &mut plugin_registry, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + } } + } + + if let Some(plugins) = args.external_plugin_adapters { + if !plugins.is_empty() { + let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::( + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + for plugin_init_info in &plugins { + let external_check_result_bits = ExternalCheckResultBits::from( + ExternalPluginAdapter::check_create(plugin_init_info), + ); - if !(approved || force_approved) { - return Err(MplCoreError::InvalidAuthority.into()); + if external_check_result_bits.can_reject() { + let validation_ctx = PluginValidationContext { + accounts, + asset_info: Some(ctx.accounts.asset), + collection_info: ctx.accounts.collection, + // External plugin adapters are always managed by the update authority. + self_authority: &Authority::UpdateAuthority, + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + if ExternalPluginAdapter::validate_create( + &ExternalPluginAdapter::from(plugin_init_info), + &validation_ctx, + )? == ValidationResult::Rejected + { + approved = false; + } + } + initialize_external_plugin_adapter::( + plugin_init_info, + &mut plugin_header, + &mut plugin_registry, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + } } } + + if !(approved || force_approved) { + return Err(MplCoreError::InvalidAuthority.into()); + } } if let Some(mut collection) = collection { diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index a5b5be9a..dd907718 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -7,10 +7,12 @@ use solana_program::{ use crate::{ error::MplCoreError, - instruction::accounts::CreateCollectionV1Accounts, + instruction::accounts::CreateCollectionV2Accounts, plugins::{ - create_plugin_meta, initialize_plugin, CheckResult, Plugin, PluginAuthorityPair, - PluginType, PluginValidationContext, ValidationResult, + create_meta_idempotent, create_plugin_meta, initialize_external_plugin_adapter, + initialize_plugin, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, + ExternalPluginAdapterInitInfo, Plugin, PluginAuthorityPair, PluginType, + PluginValidationContext, ValidationResult, }, state::{Authority, CollectionV1, Key}, }; @@ -23,17 +25,51 @@ pub(crate) struct CreateCollectionV1Args { pub(crate) plugins: Option>, } -pub(crate) fn create_collection<'a>( +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct CreateCollectionV2Args { + pub(crate) name: String, + pub(crate) uri: String, + pub(crate) plugins: Option>, + pub(crate) external_plugin_adapters: Option>, +} + +impl From for CreateCollectionV2Args { + fn from(item: CreateCollectionV1Args) -> Self { + CreateCollectionV2Args { + name: item.name, + uri: item.uri, + plugins: item.plugins, + external_plugin_adapters: None, + } + } +} + +pub(crate) fn create_collection_v1<'a>( accounts: &'a [AccountInfo<'a>], args: CreateCollectionV1Args, +) -> ProgramResult { + process_create_collection(accounts, CreateCollectionV2Args::from(args)) +} +pub(crate) fn create_collection_v2<'a>( + accounts: &'a [AccountInfo<'a>], + args: CreateCollectionV2Args, +) -> ProgramResult { + process_create_collection(accounts, args) +} + +pub(crate) fn process_create_collection<'a>( + accounts: &'a [AccountInfo<'a>], + args: CreateCollectionV2Args, ) -> ProgramResult { // Accounts. - let ctx = CreateCollectionV1Accounts::context(accounts)?; + let ctx = CreateCollectionV2Accounts::context(accounts)?; let rent = Rent::get()?; // Guards. assert_signer(ctx.accounts.collection)?; assert_signer(ctx.accounts.payer)?; + let authority = ctx.accounts.update_authority.unwrap_or(ctx.accounts.payer); if *ctx.accounts.system_program.key != system_program::ID { return Err(MplCoreError::InvalidSystemProgram.into()); @@ -41,11 +77,7 @@ pub(crate) fn create_collection<'a>( let new_collection = CollectionV1 { key: Key::CollectionV1, - update_authority: *ctx - .accounts - .update_authority - .unwrap_or(ctx.accounts.payer) - .key, + update_authority: *authority.key, name: args.name, uri: args.uri, num_minted: 0, @@ -78,17 +110,16 @@ pub(crate) fn create_collection<'a>( serialized_data.len(), ); + let mut approved = true; + let mut force_approved = false; if let Some(plugins) = args.plugins { if !plugins.is_empty() { let (mut plugin_header, mut plugin_registry) = create_plugin_meta::( - new_collection, + &new_collection, ctx.accounts.collection, ctx.accounts.payer, ctx.accounts.system_program, )?; - - let mut approved = true; - let mut force_approved = false; for plugin in &plugins { // Cannot have owner-managed plugins on collection. if plugin.plugin.manager() == Authority::Owner { @@ -103,6 +134,9 @@ pub(crate) fn create_collection<'a>( if PluginType::check_create(&plugin_type) != CheckResult::None { let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: Some(ctx.accounts.collection), self_authority: &plugin.authority.unwrap_or(plugin.plugin.manager()), authority_info: ctx.accounts.payer, resolved_authorities: None, @@ -125,12 +159,56 @@ pub(crate) fn create_collection<'a>( ctx.accounts.system_program, )?; } + } + } - if !(approved || force_approved) { - return Err(MplCoreError::InvalidAuthority.into()); + if let Some(plugins) = args.external_plugin_adapters { + if !plugins.is_empty() { + let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::( + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; + for plugin_init_info in &plugins { + let external_check_result_bits = ExternalCheckResultBits::from( + ExternalPluginAdapter::check_create(plugin_init_info), + ); + + if external_check_result_bits.can_reject() { + let validation_ctx = PluginValidationContext { + accounts, + asset_info: None, + collection_info: Some(ctx.accounts.collection), + // External plugin adapters are always managed by the update authority. + self_authority: &Authority::UpdateAuthority, + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + if ExternalPluginAdapter::validate_create( + &ExternalPluginAdapter::from(plugin_init_info), + &validation_ctx, + )? == ValidationResult::Rejected + { + approved = false; + }; + } + initialize_external_plugin_adapter::( + plugin_init_info, + &mut plugin_header, + &mut plugin_registry, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; } } } + if !(approved || force_approved) { + return Err(MplCoreError::InvalidAuthority.into()); + } + Ok(()) } diff --git a/programs/mpl-core/src/processor/decompress.rs b/programs/mpl-core/src/processor/decompress.rs index a4c5953c..c4d11d77 100644 --- a/programs/mpl-core/src/processor/decompress.rs +++ b/programs/mpl-core/src/processor/decompress.rs @@ -60,17 +60,21 @@ pub(crate) fn decompress<'a>( // Validate asset permissions. let _ = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, None, + None, AssetV1::check_decompress, CollectionV1::check_decompress, PluginType::check_decompress, AssetV1::validate_decompress, CollectionV1::validate_decompress, Plugin::validate_decompress, + None, + None, )?; // TODO Enable compression. diff --git a/programs/mpl-core/src/processor/mod.rs b/programs/mpl-core/src/processor/mod.rs index 2774022d..476d2629 100644 --- a/programs/mpl-core/src/processor/mod.rs +++ b/programs/mpl-core/src/processor/mod.rs @@ -1,45 +1,43 @@ -use crate::instruction::MplAssetInstruction; -use borsh::BorshDeserialize; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; - +mod add_external_plugin_adapter; +mod add_plugin; +mod approve_plugin_authority; +mod burn; +mod collect; +mod compress; mod create; -pub(crate) use create::*; - mod create_collection; -pub(crate) use create_collection::*; - -mod add_plugin; -pub(crate) use add_plugin::*; - +mod decompress; +mod remove_external_plugin_adapter; mod remove_plugin; -pub(crate) use remove_plugin::*; - -mod approve_plugin_authority; -pub(crate) use approve_plugin_authority::*; - mod revoke_plugin_authority; -pub(crate) use revoke_plugin_authority::*; - -mod burn; -pub(crate) use burn::*; - mod transfer; -pub(crate) use transfer::*; - mod update; -pub(crate) use update::*; +mod update_external_plugin_adapter; +mod update_plugin; +mod write_external_plugin_adapter_data; -mod compress; +pub(crate) use add_external_plugin_adapter::*; +pub(crate) use add_plugin::*; +pub(crate) use approve_plugin_authority::*; +pub(crate) use burn::*; +pub(crate) use collect::*; pub(crate) use compress::*; - -mod decompress; +pub(crate) use create::*; +pub(crate) use create_collection::*; pub(crate) use decompress::*; - -mod update_plugin; +pub(crate) use remove_external_plugin_adapter::*; +pub(crate) use remove_plugin::*; +pub(crate) use revoke_plugin_authority::*; +pub(crate) use transfer::*; +pub(crate) use update::*; +pub(crate) use update_external_plugin_adapter::*; pub(crate) use update_plugin::*; +pub(crate) use write_external_plugin_adapter_data::*; -mod collect; -pub(crate) use collect::*; +use borsh::BorshDeserialize; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; + +use crate::instruction::MplAssetInstruction; /// Standard processor that deserializes and instruction and routes it to the appropriate handler. pub fn process_instruction<'a>( @@ -51,11 +49,11 @@ pub fn process_instruction<'a>( match instruction { MplAssetInstruction::CreateV1(args) => { msg!("Instruction: Create"); - create(accounts, args) + create_v1(accounts, args) } MplAssetInstruction::CreateCollectionV1(args) => { msg!("Instruction: CreateCollection"); - create_collection(accounts, args) + create_collection_v1(accounts, args) } MplAssetInstruction::AddPluginV1(args) => { msg!("Instruction: AddPlugin"); @@ -125,7 +123,46 @@ pub fn process_instruction<'a>( msg!("Instruction: Decompress"); decompress(accounts, args) } - MplAssetInstruction::Collect => collect(accounts), + MplAssetInstruction::CreateV2(args) => { + msg!("Instruction: CreateV2"); + create_v2(accounts, args) + } + MplAssetInstruction::CreateCollectionV2(args) => { + msg!("Instruction: CreateCollectionV2"); + create_collection_v2(accounts, args) + } + MplAssetInstruction::AddExternalPluginAdapterV1(args) => { + msg!("Instruction: AddExternalPluginAdapter"); + add_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::AddCollectionExternalPluginAdapterV1(args) => { + msg!("Instruction: AddCollectionExternalPluginAdapter"); + add_collection_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::RemoveExternalPluginAdapterV1(args) => { + msg!("Instruction: RemoveExternalPluginAdapter"); + remove_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::RemoveCollectionExternalPluginAdapterV1(args) => { + msg!("Instruction: RemoveCollectionExternalPluginAdapter"); + remove_collection_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::UpdateExternalPluginAdapterV1(args) => { + msg!("Instruction: UpdateExternalPluginAdapter"); + update_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::UpdateCollectionExternalPluginAdapterV1(args) => { + msg!("Instruction: UpdateCollectionExternalPluginAdapter"); + update_collection_external_plugin_adapter(accounts, args) + } + MplAssetInstruction::WriteExternalPluginAdapterDataV1(args) => { + msg!("Instruction: WriteExternalPluginAdapterDataV1"); + write_external_plugin_adapter_data(accounts, args) + } + MplAssetInstruction::WriteCollectionExternalPluginAdapterDataV1(args) => { + msg!("Instruction: WriteCollectionExternalPluginAdapterDataV1"); + write_collection_external_plugin_adapter_data(accounts, args) + } } } diff --git a/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs b/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs new file mode 100644 index 00000000..a6e6d716 --- /dev/null +++ b/programs/mpl-core/src/processor/remove_external_plugin_adapter.rs @@ -0,0 +1,167 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; + +use crate::{ + error::MplCoreError, + instruction::accounts::{ + RemoveCollectionExternalPluginAdapterV1Accounts, RemoveExternalPluginAdapterV1Accounts, + }, + plugins::{ + delete_external_plugin_adapter, fetch_wrapped_external_plugin_adapter, + ExternalPluginAdapterKey, Plugin, PluginType, + }, + state::{AssetV1, CollectionV1, DataBlob, Key}, + utils::{ + fetch_core_data, load_key, resolve_authority, validate_asset_permissions, + validate_collection_permissions, + }, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveExternalPluginAdapterV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, +} + +pub(crate) fn remove_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveExternalPluginAdapterV1Args, +) -> ProgramResult { + let ctx = RemoveExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + if let Key::HashedAssetV1 = load_key(ctx.accounts.asset, 0)? { + msg!("Error: Remove plugin for compressed is not available"); + return Err(MplCoreError::NotAvailable.into()); + } + + let (asset, plugin_header, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; + + // We don't have anything to delete if there's no plugin meta. + if plugin_header.is_none() || plugin_registry.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + let (_, plugin_to_remove) = fetch_wrapped_external_plugin_adapter::( + ctx.accounts.asset, + Some(&asset), + &args.key, + )?; + + // Validate asset permissions. + let _ = validate_asset_permissions( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + None, + Some(&plugin_to_remove), + AssetV1::check_remove_external_plugin_adapter, + CollectionV1::check_remove_external_plugin_adapter, + PluginType::check_remove_external_plugin_adapter, + AssetV1::validate_remove_external_plugin_adapter, + CollectionV1::validate_remove_external_plugin_adapter, + Plugin::validate_remove_external_plugin_adapter, + None, + None, + )?; + + process_remove_external_plugin_adapter( + &args.key, + &asset, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct RemoveCollectionExternalPluginAdapterV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, +} + +pub(crate) fn remove_collection_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: RemoveCollectionExternalPluginAdapterV1Args, +) -> ProgramResult { + let ctx = RemoveCollectionExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + let (collection, plugin_header, plugin_registry) = + fetch_core_data::(ctx.accounts.collection)?; + + // We don't have anything to delete if there's no plugin meta. + if plugin_header.is_none() || plugin_registry.is_none() { + return Err(MplCoreError::PluginNotFound.into()); + } + + let (_, plugin_to_remove) = fetch_wrapped_external_plugin_adapter::( + ctx.accounts.collection, + Some(&collection), + &args.key, + )?; + + // Validate asset permissions. + let _ = validate_collection_permissions( + accounts, + authority, + ctx.accounts.collection, + None, + Some(&plugin_to_remove), + CollectionV1::check_remove_external_plugin_adapter, + PluginType::check_remove_external_plugin_adapter, + CollectionV1::validate_remove_external_plugin_adapter, + Plugin::validate_remove_external_plugin_adapter, + None, + None, + )?; + + process_remove_external_plugin_adapter( + &args.key, + &collection, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +fn process_remove_external_plugin_adapter<'a, T: DataBlob>( + plugin_key: &ExternalPluginAdapterKey, + core: &T, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + delete_external_plugin_adapter(plugin_key, core, account, payer, system_program) +} diff --git a/programs/mpl-core/src/processor/remove_plugin.rs b/programs/mpl-core/src/processor/remove_plugin.rs index caa2bbea..9818651b 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -57,17 +57,21 @@ pub(crate) fn remove_plugin<'a>( // Validate asset permissions. let _ = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, Some(&plugin_to_remove), + None, AssetV1::check_remove_plugin, CollectionV1::check_remove_plugin, PluginType::check_remove_plugin, AssetV1::validate_remove_plugin, CollectionV1::validate_remove_plugin, Plugin::validate_remove_plugin, + None, + None, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -124,13 +128,17 @@ pub(crate) fn remove_collection_plugin<'a>( // Validate collection permissions. let _ = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, Some(&plugin_to_remove), + None, CollectionV1::check_remove_plugin, PluginType::check_remove_plugin, CollectionV1::validate_remove_plugin, Plugin::validate_remove_plugin, + None, + None, )?; process_remove_plugin( diff --git a/programs/mpl-core/src/processor/revoke_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_plugin_authority.rs index fbee760e..aa60a7f6 100644 --- a/programs/mpl-core/src/processor/revoke_plugin_authority.rs +++ b/programs/mpl-core/src/processor/revoke_plugin_authority.rs @@ -58,17 +58,21 @@ pub(crate) fn revoke_plugin_authority<'a>( // Validate asset permissions. let _ = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, Some(&plugin), + None, AssetV1::check_revoke_plugin_authority, CollectionV1::check_revoke_plugin_authority, PluginType::check_revoke_plugin_authority, AssetV1::validate_revoke_plugin_authority, CollectionV1::validate_revoke_plugin_authority, Plugin::validate_revoke_plugin_authority, + None, + None, )?; // Increment sequence number and save only if it is `Some(_)`. @@ -129,13 +133,17 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( // Validate collection permissions. let _ = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, Some(&plugin), + None, CollectionV1::check_revoke_plugin_authority, PluginType::check_revoke_plugin_authority, CollectionV1::validate_revoke_plugin_authority, Plugin::validate_revoke_plugin_authority, + None, + None, )?; let resolved_authorities = diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index b84d47f8..fac7c48b 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -5,7 +5,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::TransferV1Accounts, - plugins::{Plugin, PluginType}, + plugins::{ExternalPluginAdapter, HookableLifecycleEvent, Plugin, PluginType}, state::{AssetV1, Authority, CollectionV1, CompressionProof, Key, SolanaAccount, Wrappable}, utils::{ compress_into_account_space, load_key, rebuild_account_state_from_proof_data, @@ -77,17 +77,21 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferV1Args // Validate asset permissions. let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, Some(ctx.accounts.new_owner), None, + None, AssetV1::check_transfer, CollectionV1::check_transfer, PluginType::check_transfer, AssetV1::validate_transfer, CollectionV1::validate_transfer, Plugin::validate_transfer, + Some(ExternalPluginAdapter::validate_transfer), + Some(HookableLifecycleEvent::Transfer), )?; // Reset every owner-managed plugin in the registry. diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index d5cbb44f..eac48df4 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -7,7 +7,10 @@ use solana_program::{ use crate::{ error::MplCoreError, instruction::accounts::{UpdateCollectionV1Accounts, UpdateV1Accounts}, - plugins::{Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, RegistryRecord}, + plugins::{ + ExternalPluginAdapter, HookableLifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, + PluginType, + }, state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount, UpdateAuthority}, utils::{ load_key, resize_or_reallocate_account, resolve_authority, validate_asset_permissions, @@ -47,17 +50,21 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateV1Args) -> } let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, None, + None, AssetV1::check_update, CollectionV1::check_update, PluginType::check_update, AssetV1::validate_update, CollectionV1::validate_update, Plugin::validate_update, + Some(ExternalPluginAdapter::validate_update), + Some(HookableLifecycleEvent::Update), )?; // Increment sequence number and save only if it is `Some(_)`. @@ -133,13 +140,17 @@ pub(crate) fn update_collection<'a>( } let (mut collection, plugin_header, plugin_registry) = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, None, + None, CollectionV1::check_update, PluginType::check_update, CollectionV1::validate_update, Plugin::validate_update, + Some(ExternalPluginAdapter::validate_update), + Some(HookableLifecycleEvent::Update), )?; let collection_size = collection.get_size() as isize; @@ -225,20 +236,24 @@ fn process_update<'a, T: DataBlob + SolanaAccount>( ); plugin_header.save(account, new_core_size as usize)?; - plugin_registry.registry = plugin_registry - .registry - .iter_mut() - .map(|record| { - let new_offset = (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - Ok(RegistryRecord { - plugin_type: record.plugin_type, - offset: new_offset as usize, - authority: record.authority, - }) - }) - .collect::, MplCoreError>>()?; + + // Move offsets for existing registry records. + for record in &mut plugin_registry.external_registry { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + + for record in &mut plugin_registry.registry { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + plugin_registry.save(account, new_registry_offset as usize)?; } else { resize_or_reallocate_account(account, payer, system_program, core.get_size())?; diff --git a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs new file mode 100644 index 00000000..67a6ee1e --- /dev/null +++ b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs @@ -0,0 +1,251 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_memory::sol_memcpy, +}; + +use crate::{ + error::MplCoreError, + instruction::accounts::{ + UpdateCollectionExternalPluginAdapterV1Accounts, UpdateExternalPluginAdapterV1Accounts, + }, + plugins::{ + fetch_wrapped_external_plugin_adapter, find_external_plugin_adapter, ExternalPluginAdapter, + ExternalPluginAdapterKey, ExternalPluginAdapterUpdateInfo, Plugin, PluginHeaderV1, + PluginRegistryV1, PluginType, + }, + state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount}, + utils::{ + load_key, resize_or_reallocate_account, resolve_authority, validate_asset_permissions, + validate_collection_permissions, + }, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct UpdateExternalPluginAdapterV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, + /// Plugin info to update. + pub update_info: ExternalPluginAdapterUpdateInfo, +} + +pub(crate) fn update_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: UpdateExternalPluginAdapterV1Args, +) -> ProgramResult { + // Accounts. + let ctx = UpdateExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + if let Key::HashedAssetV1 = load_key(ctx.accounts.asset, 0)? { + msg!("Error: Update plugin for compressed is not available"); + return Err(MplCoreError::NotAvailable.into()); + } + + let (_, plugin) = + fetch_wrapped_external_plugin_adapter::(ctx.accounts.asset, None, &args.key)?; + + let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( + accounts, + authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + None, + Some(&plugin), + AssetV1::check_update_external_plugin_adapter, + CollectionV1::check_update_external_plugin_adapter, + PluginType::check_update_external_plugin_adapter, + AssetV1::validate_update_external_plugin_adapter, + CollectionV1::validate_update_external_plugin_adapter, + Plugin::validate_update_external_plugin_adapter, + None, + None, + )?; + + // Increment sequence number and save only if it is `Some(_)`. + asset.increment_seq_and_save(ctx.accounts.asset)?; + + process_update_external_plugin_adapter( + asset, + plugin, + args.key, + args.update_info, + plugin_header, + plugin_registry, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct UpdateCollectionExternalPluginAdapterV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, + /// Plugin info to update. + pub update_info: ExternalPluginAdapterUpdateInfo, +} + +pub(crate) fn update_collection_external_plugin_adapter<'a>( + accounts: &'a [AccountInfo<'a>], + args: UpdateCollectionExternalPluginAdapterV1Args, +) -> ProgramResult { + // Accounts. + let ctx = UpdateCollectionExternalPluginAdapterV1Accounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.payer)?; + let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; + + if ctx.accounts.system_program.key != &solana_program::system_program::ID { + return Err(MplCoreError::InvalidSystemProgram.into()); + } + + if let Some(log_wrapper) = ctx.accounts.log_wrapper { + if log_wrapper.key != &spl_noop::ID { + return Err(MplCoreError::InvalidLogWrapperProgram.into()); + } + } + + let (_, plugin) = fetch_wrapped_external_plugin_adapter::( + ctx.accounts.collection, + None, + &args.key, + )?; + + // Validate collection permissions. + let (collection, plugin_header, plugin_registry) = validate_collection_permissions( + accounts, + authority, + ctx.accounts.collection, + None, + Some(&plugin), + CollectionV1::check_update_plugin, + PluginType::check_update_plugin, + CollectionV1::validate_update_plugin, + Plugin::validate_update_plugin, + None, + None, + )?; + + process_update_external_plugin_adapter( + collection, + plugin, + args.key, + args.update_info, + plugin_header, + plugin_registry, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +#[allow(clippy::too_many_arguments)] +fn process_update_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( + core: T, + plugin: ExternalPluginAdapter, + key: ExternalPluginAdapterKey, + update_info: ExternalPluginAdapterUpdateInfo, + plugin_header: Option, + plugin_registry: Option, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + let mut plugin_registry = plugin_registry.ok_or(MplCoreError::PluginsNotInitialized)?; + let mut plugin_header = plugin_header.ok_or(MplCoreError::PluginsNotInitialized)?; + + let plugin_registry_clone = plugin_registry.clone(); + let (_, record) = find_external_plugin_adapter(&plugin_registry_clone, &key, account)?; + let mut registry_record = record.ok_or(MplCoreError::PluginNotFound)?.clone(); + registry_record.update(&update_info)?; + + let mut new_plugin = plugin.clone(); + new_plugin.update(&update_info); + + let plugin_data = plugin.try_to_vec()?; + let new_plugin_data = new_plugin.try_to_vec()?; + + // The difference in size between the new and old account which is used to calculate the new size of the account. + let plugin_size = plugin_data.len() as isize; + let size_diff = (new_plugin_data.len() as isize) + .checked_sub(plugin_size) + .ok_or(MplCoreError::NumericalOverflow)?; + + // The new size of the account. + let new_size = (account.data_len() as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + // The new offset of the plugin registry is the old offset plus the size difference. + let registry_offset = plugin_header.plugin_registry_offset; + let new_registry_offset = (registry_offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + plugin_header.plugin_registry_offset = new_registry_offset as usize; + + // The offset of the first plugin is the plugin offset plus the size of the plugin. + let next_plugin_offset = (registry_record.offset as isize) + .checked_add(plugin_size) + .ok_or(MplCoreError::NumericalOverflow)?; + + let new_next_plugin_offset = next_plugin_offset + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + // //TODO: This is memory intensive, we should use memmove instead probably. + let src = account.data.borrow()[(next_plugin_offset as usize)..registry_offset].to_vec(); + + resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; + + sol_memcpy( + &mut account.data.borrow_mut()[(new_next_plugin_offset as usize)..], + &src, + src.len(), + ); + + plugin_header.save(account, core.get_size())?; + + // Move offsets for existing registry records. + for record in &mut plugin_registry.external_registry { + if registry_record.offset < record.offset { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + } + + for record in &mut plugin_registry.registry { + if registry_record.offset < record.offset { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + } + + plugin_registry.save(account, new_registry_offset as usize)?; + new_plugin.save(account, registry_record.offset)?; + + Ok(()) +} diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index db584b74..b1ed0114 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -7,7 +7,7 @@ use solana_program::{ use crate::{ error::MplCoreError, instruction::accounts::{UpdateCollectionPluginV1Accounts, UpdatePluginV1Accounts}, - plugins::{Plugin, PluginType, RegistryRecord}, + plugins::{Plugin, PluginHeaderV1, PluginRegistryV1, PluginType}, state::{AssetV1, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ load_key, resize_or_reallocate_account, resolve_authority, validate_asset_permissions, @@ -48,106 +48,35 @@ pub(crate) fn update_plugin<'a>( } let (mut asset, plugin_header, plugin_registry) = validate_asset_permissions( + accounts, authority, ctx.accounts.asset, ctx.accounts.collection, None, Some(&args.plugin), + None, AssetV1::check_update_plugin, CollectionV1::check_update_plugin, PluginType::check_update_plugin, AssetV1::validate_update_plugin, CollectionV1::validate_update_plugin, Plugin::validate_update_plugin, + None, + None, )?; - let mut plugin_registry = plugin_registry.ok_or(MplCoreError::PluginsNotInitialized)?; - let mut plugin_header = plugin_header.ok_or(MplCoreError::PluginsNotInitialized)?; - - let plugin_registry_clone = plugin_registry.clone(); - let plugin_type: PluginType = (&args.plugin).into(); - let registry_record = plugin_registry_clone - .registry - .iter() - .find(|record| record.plugin_type == plugin_type) - .ok_or(MplCoreError::PluginNotFound)?; - - let plugin = Plugin::load(ctx.accounts.asset, registry_record.offset)?; - let plugin_data = plugin.try_to_vec()?; - let new_plugin_data = args.plugin.try_to_vec()?; - - // The difference in size between the new and old account which is used to calculate the new size of the account. - let plugin_size = plugin_data.len() as isize; - let size_diff = (new_plugin_data.len() as isize) - .checked_sub(plugin_size) - .ok_or(MplCoreError::NumericalOverflow)?; - - // The new size of the account. - let new_size = (ctx.accounts.asset.data_len() as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - // The new offset of the plugin registry is the old offset plus the size difference. - let registry_offset = plugin_header.plugin_registry_offset; - let new_registry_offset = (registry_offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - plugin_header.plugin_registry_offset = new_registry_offset as usize; - - // The offset of the first plugin is the plugin offset plus the size of the plugin. - let next_plugin_offset = (registry_record.offset as isize) - .checked_add(plugin_size) - .ok_or(MplCoreError::NumericalOverflow)?; - - let new_next_plugin_offset = next_plugin_offset - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)?; - - // //TODO: This is memory intensive, we should use memmove instead probably. - let src = - ctx.accounts.asset.data.borrow()[(next_plugin_offset as usize)..registry_offset].to_vec(); + // Increment sequence number and save only if it is `Some(_)`. + asset.increment_seq_and_save(ctx.accounts.asset)?; - resize_or_reallocate_account( + process_update_plugin( + asset, + args.plugin, + plugin_header, + plugin_registry, ctx.accounts.asset, ctx.accounts.payer, ctx.accounts.system_program, - new_size as usize, - )?; - - sol_memcpy( - &mut ctx.accounts.asset.data.borrow_mut()[(new_next_plugin_offset as usize)..], - &src, - src.len(), - ); - - plugin_header.save(ctx.accounts.asset, asset.get_size())?; - plugin_registry.registry = plugin_registry - .registry - .clone() - .iter_mut() - .map(|record| { - let new_offset = if record.offset > registry_record.offset { - (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)? - } else { - record.offset as isize - }; - Ok(RegistryRecord { - plugin_type: record.plugin_type, - offset: new_offset as usize, - authority: record.authority, - }) - }) - .collect::, MplCoreError>>()?; - plugin_registry.save(ctx.accounts.asset, new_registry_offset as usize)?; - args.plugin - .save(ctx.accounts.asset, registry_record.offset)?; - - // Increment sequence number and save only if it is `Some(_)`. - asset.increment_seq_and_save(ctx.accounts.asset)?; - - process_update_plugin() + ) } #[repr(C)] @@ -179,31 +108,53 @@ pub(crate) fn update_collection_plugin<'a>( // Validate collection permissions. let (collection, plugin_header, plugin_registry) = validate_collection_permissions( + accounts, authority, ctx.accounts.collection, Some(&args.plugin), + None, CollectionV1::check_update_plugin, PluginType::check_update_plugin, CollectionV1::validate_update_plugin, Plugin::validate_update_plugin, + None, + None, )?; - // let (collection, plugin_header, plugin_registry) = - // fetch_core_data::(ctx.accounts.collection)?; + process_update_plugin( + collection, + args.plugin, + plugin_header, + plugin_registry, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + ) +} + +fn process_update_plugin<'a, T: DataBlob + SolanaAccount>( + core: T, + new_plugin: Plugin, + plugin_header: Option, + plugin_registry: Option, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { let mut plugin_registry = plugin_registry.ok_or(MplCoreError::PluginsNotInitialized)?; let mut plugin_header = plugin_header.ok_or(MplCoreError::PluginsNotInitialized)?; let plugin_registry_clone = plugin_registry.clone(); - let plugin_type: PluginType = (&args.plugin).into(); + let plugin_type: PluginType = (&new_plugin).into(); let registry_record = plugin_registry_clone .registry .iter() .find(|record| record.plugin_type == plugin_type) .ok_or(MplCoreError::PluginNotFound)?; - let plugin = Plugin::load(ctx.accounts.collection, registry_record.offset)?; + let plugin = Plugin::load(account, registry_record.offset)?; let plugin_data = plugin.try_to_vec()?; - let new_plugin_data = args.plugin.try_to_vec()?; + let new_plugin_data = new_plugin.try_to_vec()?; // The difference in size between the new and old account which is used to calculate the new size of the account. let plugin_size = plugin_data.len() as isize; @@ -212,7 +163,7 @@ pub(crate) fn update_collection_plugin<'a>( .ok_or(MplCoreError::NumericalOverflow)?; // The new size of the account. - let new_size = (ctx.accounts.collection.data_len() as isize) + let new_size = (account.data_len() as isize) .checked_add(size_diff) .ok_or(MplCoreError::NumericalOverflow)?; @@ -233,50 +184,41 @@ pub(crate) fn update_collection_plugin<'a>( .ok_or(MplCoreError::NumericalOverflow)?; // //TODO: This is memory intensive, we should use memmove instead probably. - let src = ctx.accounts.collection.data.borrow()[(next_plugin_offset as usize)..registry_offset] - .to_vec(); + let src = account.data.borrow()[(next_plugin_offset as usize)..registry_offset].to_vec(); - resize_or_reallocate_account( - ctx.accounts.collection, - ctx.accounts.payer, - ctx.accounts.system_program, - new_size as usize, - )?; + resize_or_reallocate_account(account, payer, system_program, new_size as usize)?; sol_memcpy( - &mut ctx.accounts.collection.data.borrow_mut()[(new_next_plugin_offset as usize)..], + &mut account.data.borrow_mut()[(new_next_plugin_offset as usize)..], &src, src.len(), ); - plugin_header.save(ctx.accounts.collection, collection.get_size())?; - plugin_registry.registry = plugin_registry - .registry - .clone() - .iter_mut() - .map(|record| { - let new_offset = if record.offset > registry_record.offset { - (record.offset as isize) - .checked_add(size_diff) - .ok_or(MplCoreError::NumericalOverflow)? - } else { - record.offset as isize - }; - Ok(RegistryRecord { - plugin_type: record.plugin_type, - offset: new_offset as usize, - authority: record.authority, - }) - }) - .collect::, MplCoreError>>()?; - plugin_registry.save(ctx.accounts.collection, new_registry_offset as usize)?; - args.plugin - .save(ctx.accounts.collection, registry_record.offset)?; - - process_update_plugin() -} + plugin_header.save(account, core.get_size())?; + + // Move offsets for existing registry records. + for record in &mut plugin_registry.external_registry { + if registry_record.offset < record.offset { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + } + + for record in &mut plugin_registry.registry { + if registry_record.offset < record.offset { + let new_offset = (record.offset as isize) + .checked_add(size_diff) + .ok_or(MplCoreError::NumericalOverflow)?; + + record.offset = new_offset as usize; + } + } + + plugin_registry.save(account, new_registry_offset as usize)?; + new_plugin.save(account, registry_record.offset)?; -//TODO -fn process_update_plugin() -> ProgramResult { Ok(()) } diff --git a/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs new file mode 100644 index 00000000..5c132fcb --- /dev/null +++ b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs @@ -0,0 +1,36 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; + +use crate::{error::MplCoreError, plugins::ExternalPluginAdapterKey}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct WriteExternalPluginAdapterDataV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, + /// The data to write. + pub data: Vec, +} + +pub(crate) fn write_external_plugin_adapter_data<'a>( + _accounts: &'a [AccountInfo<'a>], + _args: WriteExternalPluginAdapterDataV1Args, +) -> ProgramResult { + Err(MplCoreError::NotAvailable.into()) +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct WriteCollectionExternalPluginAdapterDataV1Args { + /// External plugin adapter key. + pub key: ExternalPluginAdapterKey, + /// The data to write. + pub data: Vec, +} + +pub(crate) fn write_collection_external_plugin_adapter_data<'a>( + _accounts: &'a [AccountInfo<'a>], + _args: WriteCollectionExternalPluginAdapterDataV1Args, +) -> ProgramResult { + Err(MplCoreError::NotAvailable.into()) +} diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index 43fd4fd5..6134b857 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -8,7 +8,7 @@ use std::mem::size_of; use crate::{ error::MplCoreError, - plugins::{CheckResult, Plugin, ValidationResult}, + plugins::{CheckResult, ExternalPluginAdapter, Plugin, ValidationResult}, state::{Compressible, CompressionProof, DataBlob, Key, SolanaAccount}, }; @@ -113,11 +113,27 @@ impl AssetV1 { CheckResult::CanApprove } + /// Check permissions for the add external plugin adapter lifecycle event. + pub fn check_add_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + + /// Check permissions for the remove external plugin adapter lifecycle event. + pub fn check_remove_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + + /// Check permissions for the update external plugin adapter lifecycle event. + pub fn check_update_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + /// Validate the add plugin lifecycle event. pub fn validate_add_plugin( &self, authority_info: &AccountInfo, new_plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let new_plugin = match new_plugin { Some(plugin) => plugin, @@ -142,6 +158,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin_to_remove: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let plugin = match plugin_to_remove { Some(plugin) => plugin, @@ -164,6 +181,7 @@ impl AssetV1 { &self, _authority_info: &AccountInfo, _plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } @@ -173,6 +191,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if let Some(plugin) = plugin { if (plugin.manager() == Authority::UpdateAuthority @@ -194,6 +213,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if let Some(plugin) = plugin { if (plugin.manager() == Authority::UpdateAuthority @@ -215,6 +235,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.update_authority.key() { solana_program::msg!("Asset: Approved"); @@ -229,6 +250,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -243,6 +265,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -257,6 +280,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -271,6 +295,7 @@ impl AssetV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.owner { solana_program::msg!("Asset: Approved"); @@ -279,6 +304,52 @@ impl AssetV1 { Ok(ValidationResult::Pass) } } + + /// Validate the add external plugin adapter lifecycle event. + pub fn validate_add_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _new_plugin: Option<&ExternalPluginAdapter>, + ) -> Result { + // If it's not in a collection, then it can be added. + if UpdateAuthority::Address(*authority_info.key) == self.update_authority { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + + /// Validate the remove external plugin adapter lifecycle event. + pub fn validate_remove_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _plugin_to_remove: Option<&ExternalPluginAdapter>, + ) -> Result { + if self.update_authority == UpdateAuthority::Address(*authority_info.key) { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + + /// Validate the update external plugin adapter lifecycle event. + pub fn validate_update_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _plugin: Option<&ExternalPluginAdapter>, + ) -> Result { + if self.update_authority == UpdateAuthority::Address(*authority_info.key) { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } } impl Compressible for AssetV1 {} diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index 8ec0cede..82da9d7f 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -4,7 +4,7 @@ use solana_program::{account_info::AccountInfo, program_error::ProgramError, pub use crate::{ error::MplCoreError, - plugins::{CheckResult, Plugin, ValidationResult}, + plugins::{CheckResult, ExternalPluginAdapter, Plugin, ValidationResult}, }; use super::{Authority, CoreAsset, DataBlob, Key, SolanaAccount, UpdateAuthority}; @@ -98,11 +98,27 @@ impl CollectionV1 { CheckResult::None } + /// Check permissions for the add external plugin adapter lifecycle event. + pub fn check_add_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + + /// Check permissions for the remove external plugin adapter lifecycle event. + pub fn check_remove_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + + /// Check permissions for the update external plugin adapter lifecycle event. + pub fn check_update_external_plugin_adapter() -> CheckResult { + CheckResult::CanApprove + } + /// Validate the add plugin lifecycle event. pub fn validate_add_plugin( &self, authority_info: &AccountInfo, new_plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let new_plugin = match new_plugin { Some(plugin) => plugin, @@ -124,6 +140,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin_to_remove: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let plugin_to_remove = match plugin_to_remove { Some(plugin) => plugin, @@ -145,6 +162,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } @@ -154,6 +172,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let plugin = match plugin { Some(plugin) => plugin, @@ -175,6 +194,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, plugin: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { let plugin = match plugin { Some(plugin) => plugin, @@ -196,6 +216,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } @@ -205,6 +226,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } @@ -214,6 +236,7 @@ impl CollectionV1 { &self, authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { if authority_info.key == &self.update_authority { solana_program::msg!("Collection: Approved"); @@ -228,6 +251,7 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } @@ -237,10 +261,57 @@ impl CollectionV1 { &self, _authority_info: &AccountInfo, _: Option<&Plugin>, + _: Option<&ExternalPluginAdapter>, ) -> Result { Ok(ValidationResult::Pass) } + /// Validate the add external plugin adapter lifecycle event. + pub fn validate_add_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _new_plugin: Option<&ExternalPluginAdapter>, + ) -> Result { + // Approve if the update authority matches the authority. + if *authority_info.key == self.update_authority { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + + /// Validate the remove external plugin adapter lifecycle event. + pub fn validate_remove_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _plugin_to_remove: Option<&ExternalPluginAdapter>, + ) -> Result { + if self.update_authority == *authority_info.key { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + + /// Validate the update external plugin adapter lifecycle event. + pub fn validate_update_external_plugin_adapter( + &self, + authority_info: &AccountInfo, + _: Option<&Plugin>, + _plugin: Option<&ExternalPluginAdapter>, + ) -> Result { + if self.update_authority == *authority_info.key { + solana_program::msg!("Asset: Approved"); + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + /// Increment size of the Collection pub fn increment(&mut self) -> Result<(), ProgramError> { self.num_minted = self diff --git a/programs/mpl-core/src/state/mod.rs b/programs/mpl-core/src/state/mod.rs index 2a90b9ef..bd824b36 100644 --- a/programs/mpl-core/src/state/mod.rs +++ b/programs/mpl-core/src/state/mod.rs @@ -41,7 +41,7 @@ pub enum DataState { /// Variants representing the different types of authority that can have permissions over plugins. #[repr(u8)] -#[derive(Copy, Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Authority { /// No authority, used for immutability. None, @@ -56,28 +56,6 @@ pub enum Authority { }, } -/// Different types of extra accounts that can be passed in for lifecycle hooks. -#[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] -pub enum ExtraAccounts { - /// No extra accounts. - None, - /// Compatible with spl-token-2022 transfer hooks. - SplHook { - /// An account meta accounts derived from the account pubkey. - extra_account_metas: Pubkey, - }, - /// A simpler method of passing in extra accounts using deterministic PDAs. - MplHook { - /// The PDA derived from the account pubkey. - mint_pda: Option, - /// The PDA derived from the collection pubkey. - collection_pda: Option, - /// The PDA derived from the asset owner pubkey. - owner_pda: Option, - }, -} - /// An enum representing account discriminators. #[derive( Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, ToPrimitive, FromPrimitive, diff --git a/programs/mpl-core/src/state/update_authority.rs b/programs/mpl-core/src/state/update_authority.rs index 93024b29..800163a0 100644 --- a/programs/mpl-core/src/state/update_authority.rs +++ b/programs/mpl-core/src/state/update_authority.rs @@ -5,11 +5,11 @@ use solana_program::{program_error::ProgramError, pubkey::Pubkey}; use crate::{ error::MplCoreError, instruction::accounts::{ - BurnV1Accounts, CompressV1Accounts, CreateV1Accounts, DecompressV1Accounts, + BurnV1Accounts, CompressV1Accounts, CreateV2Accounts, DecompressV1Accounts, TransferV1Accounts, UpdateV1Accounts, }, plugins::{fetch_plugin, CheckResult, PluginType, UpdateDelegate, ValidationResult}, - processor::CreateV1Args, + processor::CreateV2Args, state::{Authority, CollectionV1, SolanaAccount}, utils::assert_collection_authority, }; @@ -48,8 +48,8 @@ impl UpdateAuthority { /// Validate the create lifecycle event. pub(crate) fn validate_create( &self, - ctx: &CreateV1Accounts, - _args: &CreateV1Args, + ctx: &CreateV2Accounts, + _args: &CreateV2Args, ) -> Result { match (ctx.collection, self) { // If you're trying to add a collection, then check the authority. diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 5cc4588a..bf68bc19 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -12,7 +12,9 @@ use solana_program::{ use crate::{ error::MplCoreError, plugins::{ - create_meta_idempotent, initialize_plugin, validate_plugin_checks, CheckResult, Plugin, + create_meta_idempotent, initialize_plugin, validate_external_plugin_adapter_checks, + validate_plugin_checks, CheckResult, ExternalCheckResultBits, ExternalPluginAdapter, + ExternalPluginAdapterKey, ExternalRegistryRecord, HookableLifecycleEvent, Plugin, PluginHeaderV1, PluginRegistryV1, PluginType, PluginValidationContext, RegistryRecord, ValidationResult, }, @@ -164,6 +166,11 @@ pub(crate) fn resize_or_reallocate_account<'a>( system_program: &AccountInfo<'a>, new_size: usize, ) -> ProgramResult { + // If the account is already the correct size, return. + if new_size == target_account.data_len() { + return Ok(()); + } + let rent = Rent::get()?; let new_minimum_balance = rent.minimum_balance(new_size); let current_minimum_balance = rent.minimum_balance(target_account.data_len()); @@ -195,11 +202,13 @@ pub(crate) fn resize_or_reallocate_account<'a>( #[allow(clippy::too_many_arguments, clippy::type_complexity)] /// Validate asset permissions using lifecycle validations for asset, collection, and plugins. pub(crate) fn validate_asset_permissions<'a>( + accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, - asset: &AccountInfo<'a>, - collection: Option<&AccountInfo<'a>>, + asset: &'a AccountInfo<'a>, + collection: Option<&'a AccountInfo<'a>>, new_owner: Option<&'a AccountInfo<'a>>, new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, asset_check_fp: fn() -> CheckResult, collection_check_fp: fn() -> CheckResult, plugin_check_fp: fn(&PluginType) -> CheckResult, @@ -207,17 +216,32 @@ pub(crate) fn validate_asset_permissions<'a>( &AssetV1, &AccountInfo, Option<&Plugin>, + Option<&ExternalPluginAdapter>, ) -> Result, collection_validate_fp: fn( &CollectionV1, &AccountInfo, Option<&Plugin>, + Option<&ExternalPluginAdapter>, ) -> Result, plugin_validate_fp: fn( &Plugin, &PluginValidationContext, ) -> Result, + external_plugin_adapter_validate_fp: Option< + fn( + &ExternalPluginAdapter, + &PluginValidationContext, + ) -> Result, + >, + hookable_lifecycle_event: Option, ) -> Result<(AssetV1, Option, Option), ProgramError> { + if external_plugin_adapter_validate_fp.is_some() && hookable_lifecycle_event.is_none() + || external_plugin_adapter_validate_fp.is_none() && hookable_lifecycle_event.is_some() + { + panic!("Missing function parameters to validate_asset_permissions"); + } + let (deserialized_asset, plugin_header, plugin_registry) = fetch_core_data::(asset)?; let resolved_authorities = resolve_pubkey_to_authorities(authority_info, collection, &deserialized_asset)?; @@ -234,6 +258,10 @@ pub(crate) fn validate_asset_permissions<'a>( } let mut checks: BTreeMap = BTreeMap::new(); + let mut external_checks: BTreeMap< + ExternalPluginAdapterKey, + (Key, ExternalCheckResultBits, ExternalRegistryRecord), + > = BTreeMap::new(); // The asset approval overrides the collection approval. let asset_check = asset_check_fp(); @@ -245,25 +273,46 @@ pub(crate) fn validate_asset_permissions<'a>( // Check the collection plugins first. if let Some(collection_info) = collection { - fetch_core_data::(collection_info).map(|(_, _, registry)| { - registry.map(|r| { - r.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); - r - }) - })?; + let (_, _, registry) = fetch_core_data::(collection_info)?; + + if let Some(r) = registry { + r.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); + + if let Some(lifecycle_event) = &hookable_lifecycle_event { + r.check_adapter_registry( + collection_info, + Key::CollectionV1, + lifecycle_event, + &mut external_checks, + )?; + } + } } // Next check the asset plugins. Plugins on the asset override the collection plugins, // so we don't need to validate the collection plugins if the asset has a plugin. if let Some(registry) = plugin_registry.as_ref() { registry.check_registry(Key::AssetV1, plugin_check_fp, &mut checks); + if let Some(lifecycle_event) = &hookable_lifecycle_event { + registry.check_adapter_registry( + asset, + Key::AssetV1, + lifecycle_event, + &mut external_checks, + )?; + } } // Do the core validation. let mut approved = false; let mut rejected = false; if asset_check != CheckResult::None { - match asset_validate_fp(&deserialized_asset, authority_info, new_plugin)? { + match asset_validate_fp( + &deserialized_asset, + authority_info, + new_plugin, + new_external_plugin_adapter, + )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, ValidationResult::Pass => (), @@ -278,6 +327,7 @@ pub(crate) fn validate_asset_permissions<'a>( &CollectionV1::load(collection.ok_or(MplCoreError::MissingCollection)?, 0)?, authority_info, new_plugin, + new_external_plugin_adapter, )? { ValidationResult::Approved => approved = true, ValidationResult::Rejected => rejected = true, @@ -290,6 +340,7 @@ pub(crate) fn validate_asset_permissions<'a>( match validate_plugin_checks( Key::CollectionV1, + accounts, &checks, authority_info, new_owner, @@ -309,6 +360,7 @@ pub(crate) fn validate_asset_permissions<'a>( match validate_plugin_checks( Key::AssetV1, + accounts, &checks, authority_info, new_owner, @@ -326,6 +378,46 @@ pub(crate) fn validate_asset_permissions<'a>( } }; + if let Some(external_plugin_adapter_validate_fp) = external_plugin_adapter_validate_fp { + match validate_external_plugin_adapter_checks( + Key::CollectionV1, + accounts, + &external_checks, + authority_info, + new_owner, + new_plugin, + Some(asset), + collection, + &resolved_authorities, + external_plugin_adapter_validate_fp, + )? { + ValidationResult::Approved => approved = true, + ValidationResult::Rejected => rejected = true, + ValidationResult::Pass => (), + // Force approved will not be possible from external plugin adapters. + ValidationResult::ForceApproved => unreachable!(), + }; + + match validate_external_plugin_adapter_checks( + Key::AssetV1, + accounts, + &external_checks, + authority_info, + new_owner, + new_plugin, + Some(asset), + collection, + &resolved_authorities, + external_plugin_adapter_validate_fp, + )? { + ValidationResult::Approved => approved = true, + ValidationResult::Rejected => rejected = true, + ValidationResult::Pass => (), + // Force approved will not be possible from external plugin adapters. + ValidationResult::ForceApproved => unreachable!(), + }; + } + if rejected { return Err(MplCoreError::InvalidAuthority.into()); } else if !approved { @@ -336,22 +428,32 @@ pub(crate) fn validate_asset_permissions<'a>( } /// Validate collection permissions using lifecycle validations for collection and plugins. -#[allow(clippy::type_complexity)] +#[allow(clippy::type_complexity, clippy::too_many_arguments)] pub(crate) fn validate_collection_permissions<'a>( + accounts: &'a [AccountInfo<'a>], authority_info: &'a AccountInfo<'a>, - collection: &AccountInfo<'a>, + collection: &'a AccountInfo<'a>, new_plugin: Option<&Plugin>, + new_external_plugin_adapter: Option<&ExternalPluginAdapter>, collection_check_fp: fn() -> CheckResult, plugin_check_fp: fn(&PluginType) -> CheckResult, collection_validate_fp: fn( &CollectionV1, &AccountInfo, Option<&Plugin>, + Option<&ExternalPluginAdapter>, ) -> Result, plugin_validate_fp: fn( &Plugin, &PluginValidationContext, ) -> Result, + external_plugin_adapter_validate_fp: Option< + fn( + &ExternalPluginAdapter, + &PluginValidationContext, + ) -> Result, + >, + hookable_lifecycle_event: Option, ) -> Result< ( CollectionV1, @@ -360,17 +462,35 @@ pub(crate) fn validate_collection_permissions<'a>( ), ProgramError, > { + if external_plugin_adapter_validate_fp.is_some() && hookable_lifecycle_event.is_none() + || external_plugin_adapter_validate_fp.is_none() && hookable_lifecycle_event.is_some() + { + panic!("Missing function parameters to validate_asset_permissions"); + } + let (deserialized_collection, plugin_header, plugin_registry) = fetch_core_data::(collection)?; let resolved_authorities = resolve_pubkey_to_authorities_collection(authority_info, collection)?; let mut checks: BTreeMap = BTreeMap::new(); + let mut external_checks: BTreeMap< + ExternalPluginAdapterKey, + (Key, ExternalCheckResultBits, ExternalRegistryRecord), + > = BTreeMap::new(); let core_check = (Key::CollectionV1, collection_check_fp()); - // Check the collection plugins first. + // Check the collection plugins. if let Some(registry) = plugin_registry.as_ref() { registry.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); + if let Some(lifecycle_event) = hookable_lifecycle_event { + registry.check_adapter_registry( + collection, + Key::CollectionV1, + &lifecycle_event, + &mut external_checks, + )?; + } } // Do the core validation. @@ -384,9 +504,12 @@ pub(crate) fn validate_collection_permissions<'a>( ) ) { let result = match core_check.0 { - Key::CollectionV1 => { - collection_validate_fp(&deserialized_collection, authority_info, new_plugin)? - } + Key::CollectionV1 => collection_validate_fp( + &deserialized_collection, + authority_info, + new_plugin, + new_external_plugin_adapter, + )?, _ => return Err(MplCoreError::IncorrectAccount.into()), }; match result { @@ -401,6 +524,7 @@ pub(crate) fn validate_collection_permissions<'a>( match validate_plugin_checks( Key::CollectionV1, + accounts, &checks, authority_info, None, @@ -418,6 +542,27 @@ pub(crate) fn validate_collection_permissions<'a>( } }; + if let Some(external_plugin_adapter_validate_fp) = external_plugin_adapter_validate_fp { + match validate_external_plugin_adapter_checks( + Key::CollectionV1, + accounts, + &external_checks, + authority_info, + None, + new_plugin, + None, + Some(collection), + &resolved_authorities, + external_plugin_adapter_validate_fp, + )? { + ValidationResult::Approved => approved = true, + ValidationResult::Rejected => rejected = true, + ValidationResult::Pass => (), + // Force approved will not be possible from external plugin adapters. + ValidationResult::ForceApproved => unreachable!(), + }; + } + if rejected || !approved { return Err(MplCoreError::InvalidAuthority.into()); }