From 781aa7f3215000b8170d2fcbb6f07d7b59e1f096 Mon Sep 17 00:00:00 2001 From: BL Date: Tue, 8 Sep 2026 16:20:55 +0000 Subject: [PATCH 1/2] feat!: split the SDK into @misofm/musicos, @misofm/partyos, and @misofm/platform Closes #26. `@misofm/musicos` and `@misofm/partyos` are the object models. Everything Miso offers on top of them is platform. - `packages/protocol` becomes `packages/musicos` (`@misofm/musicos`), bound to the single `musicos` Move package (composition, recording, release, track) with a one-key deployment manifest. - New `packages/partyos` (`@misofm/partyos`), bound to the single `partyos` Move package: Party identity, admin cap, and group membership. - `packages/platform` absorbs every other binding (work and Party extensions, royalty primitives, Vault, Actions, plugins, Record and Record Shop), the Party extension client, the royalty queries and builders, the extension event decoders, and the platform package bindings. It depends on both object-model packages and exports `./party`, `./packages`, `./contracts`, and `./contracts/*`. - Codegen generates three trees from one config; the prune set is derived from the config and a label that disagrees with a Move package name fails the run instead of leaving an orphan. Generated directories follow the renamed Move packages (`musicos`, `partyos`, `record`, `record_shop`). - Deployment manifests carry the 2026-09-08 testnet generation: all 46 Move packages were republished immutably through the Onara sponsor and verified; `recording_genre` is published for the first time. - The isolated-consumer fixture installs real tarballs of all five packages under `types: []`, asserts a single instance of every `@misofm/*` package, and runs in CI. `@misofm/streaming` is pinned at 0.2.1 by platform and transcoding; transcoding no longer strips `@internal` declarations that its public declarations reference. - Publish workflow gains the `musicos-v*` and `partyos-v*` tag arms. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QGmuA3eMr4iaRTdz3J8JZJ --- .github/workflows/ci.yml | 20 + .github/workflows/publish.yml | 16 +- CLAUDE.md | 38 +- README.md | 24 +- bun.lock | 96 ++-- package.json | 9 +- packages/{protocol => musicos}/LICENSE | 0 packages/musicos/README.md | 75 +++ packages/{protocol => musicos}/package.json | 16 +- .../scripts/codegen-output.ts | 0 packages/{protocol => musicos}/src/client.ts | 28 +- packages/musicos/src/contracts.ts | 29 ++ .../src/contracts/musicos}/composition.ts | 20 +- .../src/contracts/musicos}/deps/bps/bps.ts | 0 .../src/contracts/musicos}/recording.ts | 20 +- .../src/contracts/musicos}/release.ts | 24 +- .../src/contracts/musicos}/track.ts | 12 +- .../src/contracts/utils/index.ts | 0 packages/musicos/src/deployments.ts | 166 +++++++ packages/musicos/src/events.ts | 30 ++ packages/{protocol => musicos}/src/execute.ts | 0 packages/{protocol => musicos}/src/index.ts | 9 +- .../{protocol => musicos}/src/internal.ts | 0 packages/{protocol => musicos}/src/numeric.ts | 0 packages/musicos/src/packages.ts | 117 +++++ packages/{protocol => musicos}/src/parsers.ts | 6 +- packages/{protocol => musicos}/src/queries.ts | 60 +-- .../{protocol => musicos}/src/transactions.ts | 133 +----- packages/{protocol => musicos}/src/types.ts | 0 packages/{protocol => musicos}/src/view.ts | 2 +- .../tests/batching.test.ts | 6 +- .../tests/client.test.ts | 135 +----- packages/musicos/tests/codegen-output.test.ts | 60 +++ packages/musicos/tests/events.test.ts | 21 + .../tests/mappers.test.ts | 6 +- .../tests/parsers.test.ts | 6 +- .../tests/queries.test.ts | 43 +- .../tests/transactions.test.ts | 84 ---- .../{protocol => musicos}/tests/view.test.ts | 0 .../{protocol => musicos}/tsconfig.build.json | 0 packages/{protocol => musicos}/tsconfig.json | 0 packages/{protocol => musicos}/typedoc.json | 2 +- packages/partyos/LICENSE | 201 ++++++++ packages/partyos/README.md | 50 ++ packages/partyos/package.json | 97 ++++ packages/partyos/scripts/codegen-output.ts | 8 + packages/partyos/src/client.ts | 165 +++++++ packages/partyos/src/contracts.ts | 31 ++ .../contracts/partyos}/deps/sui/vec_set.ts | 0 .../src/contracts/partyos}/party.ts | 54 +-- packages/partyos/src/contracts/utils/index.ts | 428 ++++++++++++++++++ packages/partyos/src/deployments.ts | 90 ++++ packages/partyos/src/index.ts | 15 + packages/partyos/src/internal.ts | 30 ++ packages/partyos/src/queries.ts | 167 +++++++ .../src/party => partyos/src}/transactions.ts | 2 +- packages/partyos/src/types.ts | 18 + packages/partyos/tests/deployments.test.ts | 31 ++ .../tests/queries.test.ts} | 12 +- packages/partyos/tsconfig.build.json | 33 ++ packages/partyos/tsconfig.json | 4 + packages/partyos/typedoc.json | 20 + packages/platform/README.md | 182 +++++--- packages/platform/package.json | 34 +- packages/platform/scripts/format-codegen.ts | 33 -- packages/platform/src/catalog.ts | 4 +- packages/platform/src/client.ts | 80 ++-- packages/platform/src/contracts.ts | 124 +++-- .../composition_credits.ts | 4 +- .../composition_party_role.ts | 2 +- .../deps/credit}/credit.ts | 2 +- .../composition_credits/deps/sui/vec_map.ts | 0 .../composition_routed_stake.ts | 0 .../composition_royalty_pool.ts | 2 +- .../composition_royalty_pool_plugin.ts | 0 .../witness.ts | 0 .../src/contracts/cover_art/cover_art.ts | 0 .../cover_art/deps/ori/confidentiality.ts | 0 .../src/contracts/cover_art/deps/ori/data.ts | 0 .../src/contracts/genre/genre.ts | 0 .../src/contracts/party_cta/party_cta.ts | 0 .../src/contracts/party_genre/party_genre.ts | 0 .../src/contracts/party_media/party_media.ts | 0 .../src/contracts/party_music/party_music.ts | 0 .../party_platform_link.ts | 0 .../party_pro_link/party_pro_link.ts | 0 .../deps/country_code/country_code.ts | 0 .../deps/language_code/language_code.ts | 0 .../contracts/party_profile/party_profile.ts | 0 .../src/contracts/party_roles/party_roles.ts | 0 .../contracts/party_social/party_social.ts | 0 .../src/contracts/party_tags/party_tags.ts | 0 .../contracts/party_wallet/party_wallet.ts | 0 .../contracts/record}/deps/std/type_name.ts | 0 .../src/contracts/record}/deps/sui/vec_set.ts | 0 .../src/contracts/record}/pressing.ts | 34 +- .../src/contracts/record}/record.ts | 26 +- .../record_shop}/deps/std/type_name.ts | 0 .../src/contracts/record_shop}/listing.ts | 40 +- .../src/contracts/record_shop}/witness.ts | 2 +- .../recording_advisory/recording_advisory.ts | 0 .../recording_credits/deps/credit}/credit.ts | 2 +- .../recording_credits/deps/sui/vec_map.ts | 0 .../recording_credits/deps/sui/vec_set.ts | 0 .../recording_credits/recording_credits.ts | 4 +- .../recording_credits/recording_party_role.ts | 2 +- .../deps/ori/confidentiality.ts | 0 .../recording_engine_session/deps/ori/data.ts | 0 .../recording_engine_session.ts | 0 .../recording_genre/recording_genre.ts | 2 +- .../deps/language_code/language_code.ts | 0 .../recording_language/recording_language.ts | 0 .../deps/ori/confidentiality.ts | 0 .../deps/ori/data.ts | 0 .../recording_master_reference.ts | 8 +- .../recording_royalty_pool.ts | 2 +- .../recording_royalty_pool_plugin.ts | 0 .../recording_royalty_pool_plugin/witness.ts | 0 .../deps/ori/data.ts | 0 .../recording_streaming_transcode.ts | 0 .../confidentiality.ts | 0 .../data.ts | 0 .../deps/cover_art/cover_art.ts | 0 .../deps/per_track/per_track.ts | 0 .../release_cover_art/release_cover_art.ts | 0 .../release_credits/deps/credit}/credit.ts | 2 +- .../release_credits/deps/sui/vec_map.ts | 0 .../release_credits/release_credits.ts | 4 +- .../release_credits/release_party_role.ts | 2 +- .../release_description.ts | 0 .../release_dsp_link/release_dsp_link.ts | 4 +- .../contracts/release_genre/release_genre.ts | 4 +- .../contracts/release_kind/release_kind.ts | 0 .../release_revenue_distributor.ts | 0 .../release_revenue_distributor_plugin.ts | 0 .../witness.ts | 0 .../routed_stake/deps/royalty_pool/stake.ts | 0 .../routed_stake/deps/std/type_name.ts | 0 .../routed_stake/deps/sui/balance.ts | 0 .../routed_stake/deps/sui/vec_map.ts | 0 .../contracts/routed_stake/routed_stake.ts | 0 .../royalty_pool/deps/std/type_name.ts | 0 .../royalty_pool/deps/sui/balance.ts | 0 .../royalty_pool/deps/sui/vec_map.ts | 0 .../src/contracts/royalty_pool/pool.ts | 0 .../src/contracts/royalty_pool/stake.ts | 0 .../platform/src/contracts/utils/index.ts | 428 ++++++++++++++++++ .../src/contracts/vault/deps/sui/bag.ts | 0 .../src/contracts/vault/deps/sui/borrow.ts | 0 .../src/contracts/vault/vault.ts | 0 packages/platform/src/cover.ts | 4 +- packages/platform/src/credits.ts | 12 +- packages/platform/src/deployments.ts | 294 ++++++++++-- packages/{protocol => platform}/src/events.ts | 33 +- packages/platform/src/execute.ts | 6 +- packages/platform/src/genre.ts | 6 +- packages/platform/src/index.ts | 10 +- packages/platform/src/internal.ts | 4 +- .../{protocol => platform}/src/packages.ts | 169 +++---- .../src/party/client.ts | 173 ++++--- .../src/party/extensions/ctas.ts | 2 +- .../src/party/extensions/genres.ts | 2 +- .../src/party/extensions/links.ts | 2 +- .../src/party/extensions/media.ts | 2 +- .../src/party/extensions/profile.ts | 2 +- .../src/party/extensions/roles.ts | 2 +- .../src/party/extensions/tags.ts | 2 +- packages/platform/src/party/index.ts | 40 ++ .../src/party/internal.ts | 19 +- packages/platform/src/party/queries.ts | 163 +++++++ .../{protocol => platform}/src/party/types.ts | 19 +- packages/platform/src/pressing.ts | 10 +- packages/platform/src/publication.ts | 73 +-- packages/platform/src/queries.ts | 4 +- packages/platform/src/read/artist.ts | 6 +- packages/platform/src/read/catalog.ts | 8 +- packages/platform/src/read/client.ts | 12 +- packages/platform/src/read/config.ts | 11 +- packages/platform/src/read/index.ts | 2 +- packages/platform/src/read/receipts.ts | 8 +- packages/platform/src/read/types.ts | 2 +- packages/platform/src/read/wallet.ts | 29 +- packages/platform/src/read/works.ts | 2 +- packages/platform/src/recording-extensions.ts | 14 +- packages/platform/src/release-extensions.ts | 6 +- packages/platform/src/release-graph.ts | 4 +- packages/platform/src/royalty.ts | 167 +++++++ packages/platform/src/share.ts | 2 +- packages/platform/src/transactions.ts | 12 +- packages/platform/src/vault.ts | 12 +- packages/platform/sui-codegen.config.ts | 153 ------- packages/platform/tests/batching.test.ts | 2 +- packages/platform/tests/client.test.ts | 219 +++++---- .../platform/tests/codegen-format.test.ts | 32 +- .../tests/dependency-contract.test.ts | 5 +- packages/platform/tests/events.test.ts | 110 +++++ packages/platform/tests/fixtures/.gitignore | 1 + .../fixtures/isolated-consumer/.gitignore | 2 + .../fixtures/isolated-consumer/imports.ts | 105 +++-- .../fixtures/isolated-consumer/package.json | 7 +- .../fixtures/isolated-consumer/tsconfig.json | 3 +- .../fixtures/isolated-consumer/verify.mjs | 118 +++-- packages/platform/tests/genre.test.ts | 4 +- packages/platform/tests/packages.test.ts | 147 ++++++ .../tests/party-internal.test.ts | 0 packages/platform/tests/pressing-read.test.ts | 6 +- packages/platform/tests/publication.test.ts | 16 +- .../tests/read/catalog-resources.test.ts | 4 +- packages/platform/tests/read/receipts.test.ts | 2 +- .../platform/tests/read/release-kind.test.ts | 2 +- .../tests/read/vaulted-wallet.test.ts | 6 +- packages/platform/tests/read/wallet.test.ts | 2 +- packages/platform/tests/read/works.test.ts | 2 +- .../tests/recording-extensions.test.ts | 8 +- packages/platform/tests/royalty.test.ts | 120 +++++ packages/platform/tests/transactions.test.ts | 2 +- packages/platform/tests/vault.test.ts | 2 +- packages/protocol/README.md | 34 -- packages/protocol/src/contracts.ts | 78 ---- packages/protocol/src/deployments.ts | 221 --------- packages/protocol/src/party/index.ts | 14 - packages/protocol/src/party/queries.ts | 337 -------------- .../protocol/tests/codegen-output.test.ts | 25 - packages/protocol/tests/events.test.ts | 114 ----- packages/transcoding/package.json | 4 +- .../test/dependency-contract.test.ts | 2 +- packages/transcoding/tsconfig.build.json | 3 +- scripts/check-consumer.ts | 71 +++ scripts/codegen-output.ts | 123 ++--- scripts/codegen.ts | 99 ++-- sui-codegen.config.ts | 192 ++++---- 231 files changed, 4900 insertions(+), 2649 deletions(-) rename packages/{protocol => musicos}/LICENSE (100%) create mode 100644 packages/musicos/README.md rename packages/{protocol => musicos}/package.json (91%) rename packages/{protocol => musicos}/scripts/codegen-output.ts (100%) rename packages/{protocol => musicos}/src/client.ts (91%) create mode 100644 packages/musicos/src/contracts.ts rename packages/{protocol/src/contracts/miso => musicos/src/contracts/musicos}/composition.ts (94%) rename packages/{protocol/src/contracts/miso => musicos/src/contracts/musicos}/deps/bps/bps.ts (100%) rename packages/{protocol/src/contracts/miso => musicos/src/contracts/musicos}/recording.ts (94%) rename packages/{protocol/src/contracts/miso => musicos/src/contracts/musicos}/release.ts (95%) rename packages/{protocol/src/contracts/miso => musicos/src/contracts/musicos}/track.ts (96%) rename packages/{protocol => musicos}/src/contracts/utils/index.ts (100%) create mode 100644 packages/musicos/src/deployments.ts create mode 100644 packages/musicos/src/events.ts rename packages/{protocol => musicos}/src/execute.ts (100%) rename packages/{protocol => musicos}/src/index.ts (63%) rename packages/{protocol => musicos}/src/internal.ts (100%) rename packages/{protocol => musicos}/src/numeric.ts (100%) create mode 100644 packages/musicos/src/packages.ts rename packages/{protocol => musicos}/src/parsers.ts (94%) rename packages/{protocol => musicos}/src/queries.ts (94%) rename packages/{protocol => musicos}/src/transactions.ts (75%) rename packages/{protocol => musicos}/src/types.ts (100%) rename packages/{protocol => musicos}/src/view.ts (97%) rename packages/{protocol => musicos}/tests/batching.test.ts (96%) rename packages/{protocol => musicos}/tests/client.test.ts (52%) create mode 100644 packages/musicos/tests/codegen-output.test.ts create mode 100644 packages/musicos/tests/events.test.ts rename packages/{protocol => musicos}/tests/mappers.test.ts (92%) rename packages/{protocol => musicos}/tests/parsers.test.ts (91%) rename packages/{protocol => musicos}/tests/queries.test.ts (87%) rename packages/{protocol => musicos}/tests/transactions.test.ts (79%) rename packages/{protocol => musicos}/tests/view.test.ts (100%) rename packages/{protocol => musicos}/tsconfig.build.json (100%) rename packages/{protocol => musicos}/tsconfig.json (100%) rename packages/{protocol => musicos}/typedoc.json (92%) create mode 100644 packages/partyos/LICENSE create mode 100644 packages/partyos/README.md create mode 100644 packages/partyos/package.json create mode 100644 packages/partyos/scripts/codegen-output.ts create mode 100644 packages/partyos/src/client.ts create mode 100644 packages/partyos/src/contracts.ts rename packages/{protocol/src/contracts/miso_party => partyos/src/contracts/partyos}/deps/sui/vec_set.ts (100%) rename packages/{protocol/src/contracts/miso_party => partyos/src/contracts/partyos}/party.ts (93%) create mode 100644 packages/partyos/src/contracts/utils/index.ts create mode 100644 packages/partyos/src/deployments.ts create mode 100644 packages/partyos/src/index.ts create mode 100644 packages/partyos/src/internal.ts create mode 100644 packages/partyos/src/queries.ts rename packages/{protocol/src/party => partyos/src}/transactions.ts (98%) create mode 100644 packages/partyos/src/types.ts create mode 100644 packages/partyos/tests/deployments.test.ts rename packages/{protocol/tests/party-queries.test.ts => partyos/tests/queries.test.ts} (91%) create mode 100644 packages/partyos/tsconfig.build.json create mode 100644 packages/partyos/tsconfig.json create mode 100644 packages/partyos/typedoc.json delete mode 100644 packages/platform/scripts/format-codegen.ts rename packages/{protocol => platform}/src/contracts/composition_credits/composition_credits.ts (97%) rename packages/{protocol => platform}/src/contracts/composition_credits/composition_party_role.ts (98%) rename packages/{protocol/src/contracts/composition_credits/deps/miso_credit => platform/src/contracts/composition_credits/deps/credit}/credit.ts (96%) rename packages/{protocol => platform}/src/contracts/composition_credits/deps/sui/vec_map.ts (100%) rename packages/{protocol => platform}/src/contracts/composition_routed_stake/composition_routed_stake.ts (100%) rename packages/{protocol => platform}/src/contracts/composition_royalty_pool/composition_royalty_pool.ts (98%) rename packages/{protocol => platform}/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts (100%) rename packages/{protocol => platform}/src/contracts/composition_royalty_pool_plugin/witness.ts (100%) rename packages/{protocol => platform}/src/contracts/cover_art/cover_art.ts (100%) rename packages/{protocol => platform}/src/contracts/cover_art/deps/ori/confidentiality.ts (100%) rename packages/{protocol => platform}/src/contracts/cover_art/deps/ori/data.ts (100%) rename packages/{protocol => platform}/src/contracts/genre/genre.ts (100%) rename packages/{protocol => platform}/src/contracts/party_cta/party_cta.ts (100%) rename packages/{protocol => platform}/src/contracts/party_genre/party_genre.ts (100%) rename packages/{protocol => platform}/src/contracts/party_media/party_media.ts (100%) rename packages/{protocol => platform}/src/contracts/party_music/party_music.ts (100%) rename packages/{protocol => platform}/src/contracts/party_platform_link/party_platform_link.ts (100%) rename packages/{protocol => platform}/src/contracts/party_pro_link/party_pro_link.ts (100%) rename packages/{protocol => platform}/src/contracts/party_profile/deps/country_code/country_code.ts (100%) rename packages/{protocol => platform}/src/contracts/party_profile/deps/language_code/language_code.ts (100%) rename packages/{protocol => platform}/src/contracts/party_profile/party_profile.ts (100%) rename packages/{protocol => platform}/src/contracts/party_roles/party_roles.ts (100%) rename packages/{protocol => platform}/src/contracts/party_social/party_social.ts (100%) rename packages/{protocol => platform}/src/contracts/party_tags/party_tags.ts (100%) rename packages/{protocol => platform}/src/contracts/party_wallet/party_wallet.ts (100%) rename packages/{protocol/src/contracts/miso_record => platform/src/contracts/record}/deps/std/type_name.ts (100%) rename packages/{protocol/src/contracts/miso_record => platform/src/contracts/record}/deps/sui/vec_set.ts (100%) rename packages/{protocol/src/contracts/miso_record => platform/src/contracts/record}/pressing.ts (93%) rename packages/{protocol/src/contracts/miso_record => platform/src/contracts/record}/record.ts (92%) rename packages/{protocol/src/contracts/miso_record_shop => platform/src/contracts/record_shop}/deps/std/type_name.ts (100%) rename packages/{protocol/src/contracts/miso_record_shop => platform/src/contracts/record_shop}/listing.ts (92%) rename packages/{protocol/src/contracts/miso_record_shop => platform/src/contracts/record_shop}/witness.ts (89%) rename packages/{protocol => platform}/src/contracts/recording_advisory/recording_advisory.ts (100%) rename packages/{protocol/src/contracts/release_credits/deps/miso_credit => platform/src/contracts/recording_credits/deps/credit}/credit.ts (96%) rename packages/{protocol => platform}/src/contracts/recording_credits/deps/sui/vec_map.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_credits/deps/sui/vec_set.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_credits/recording_credits.ts (99%) rename packages/{protocol => platform}/src/contracts/recording_credits/recording_party_role.ts (99%) rename packages/{protocol => platform}/src/contracts/recording_engine_session/deps/ori/confidentiality.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_engine_session/deps/ori/data.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_engine_session/recording_engine_session.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_genre/recording_genre.ts (99%) rename packages/{protocol => platform}/src/contracts/recording_language/deps/language_code/language_code.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_language/recording_language.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_master_reference/deps/ori/confidentiality.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_master_reference/deps/ori/data.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_master_reference/recording_master_reference.ts (95%) rename packages/{protocol => platform}/src/contracts/recording_royalty_pool/recording_royalty_pool.ts (98%) rename packages/{protocol => platform}/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_royalty_pool_plugin/witness.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_streaming_transcode/deps/ori/data.ts (100%) rename packages/{protocol => platform}/src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts (100%) rename packages/{protocol => platform}/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/confidentiality.ts (100%) rename packages/{protocol => platform}/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/data.ts (100%) rename packages/{protocol => platform}/src/contracts/release_cover_art/deps/cover_art/cover_art.ts (100%) rename packages/{protocol => platform}/src/contracts/release_cover_art/deps/per_track/per_track.ts (100%) rename packages/{protocol => platform}/src/contracts/release_cover_art/release_cover_art.ts (100%) rename packages/{protocol/src/contracts/recording_credits/deps/miso_credit => platform/src/contracts/release_credits/deps/credit}/credit.ts (96%) rename packages/{protocol => platform}/src/contracts/release_credits/deps/sui/vec_map.ts (100%) rename packages/{protocol => platform}/src/contracts/release_credits/release_credits.ts (97%) rename packages/{protocol => platform}/src/contracts/release_credits/release_party_role.ts (97%) rename packages/{protocol => platform}/src/contracts/release_description/release_description.ts (100%) rename packages/{protocol => platform}/src/contracts/release_dsp_link/release_dsp_link.ts (99%) rename packages/{protocol => platform}/src/contracts/release_genre/release_genre.ts (98%) rename packages/{protocol => platform}/src/contracts/release_kind/release_kind.ts (100%) rename packages/{protocol => platform}/src/contracts/release_revenue_distributor/release_revenue_distributor.ts (100%) rename packages/{protocol => platform}/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts (100%) rename packages/{protocol => platform}/src/contracts/release_revenue_distributor_plugin/witness.ts (100%) rename packages/{protocol => platform}/src/contracts/routed_stake/deps/royalty_pool/stake.ts (100%) rename packages/{protocol => platform}/src/contracts/routed_stake/deps/std/type_name.ts (100%) rename packages/{protocol => platform}/src/contracts/routed_stake/deps/sui/balance.ts (100%) rename packages/{protocol => platform}/src/contracts/routed_stake/deps/sui/vec_map.ts (100%) rename packages/{protocol => platform}/src/contracts/routed_stake/routed_stake.ts (100%) rename packages/{protocol => platform}/src/contracts/royalty_pool/deps/std/type_name.ts (100%) rename packages/{protocol => platform}/src/contracts/royalty_pool/deps/sui/balance.ts (100%) rename packages/{protocol => platform}/src/contracts/royalty_pool/deps/sui/vec_map.ts (100%) rename packages/{protocol => platform}/src/contracts/royalty_pool/pool.ts (100%) rename packages/{protocol => platform}/src/contracts/royalty_pool/stake.ts (100%) create mode 100644 packages/platform/src/contracts/utils/index.ts rename packages/{protocol => platform}/src/contracts/vault/deps/sui/bag.ts (100%) rename packages/{protocol => platform}/src/contracts/vault/deps/sui/borrow.ts (100%) rename packages/{protocol => platform}/src/contracts/vault/vault.ts (100%) rename packages/{protocol => platform}/src/events.ts (85%) rename packages/{protocol => platform}/src/packages.ts (62%) rename packages/{protocol => platform}/src/party/client.ts (69%) rename packages/{protocol => platform}/src/party/extensions/ctas.ts (96%) rename packages/{protocol => platform}/src/party/extensions/genres.ts (97%) rename packages/{protocol => platform}/src/party/extensions/links.ts (99%) rename packages/{protocol => platform}/src/party/extensions/media.ts (98%) rename packages/{protocol => platform}/src/party/extensions/profile.ts (97%) rename packages/{protocol => platform}/src/party/extensions/roles.ts (98%) rename packages/{protocol => platform}/src/party/extensions/tags.ts (96%) create mode 100644 packages/platform/src/party/index.ts rename packages/{protocol => platform}/src/party/internal.ts (81%) create mode 100644 packages/platform/src/party/queries.ts rename packages/{protocol => platform}/src/party/types.ts (81%) create mode 100644 packages/platform/src/royalty.ts delete mode 100644 packages/platform/sui-codegen.config.ts create mode 100644 packages/platform/tests/events.test.ts create mode 100644 packages/platform/tests/fixtures/.gitignore create mode 100644 packages/platform/tests/fixtures/isolated-consumer/.gitignore create mode 100644 packages/platform/tests/packages.test.ts rename packages/{protocol => platform}/tests/party-internal.test.ts (100%) create mode 100644 packages/platform/tests/royalty.test.ts delete mode 100644 packages/protocol/README.md delete mode 100644 packages/protocol/src/contracts.ts delete mode 100644 packages/protocol/src/deployments.ts delete mode 100644 packages/protocol/src/party/index.ts delete mode 100644 packages/protocol/src/party/queries.ts delete mode 100644 packages/protocol/tests/codegen-output.test.ts delete mode 100644 packages/protocol/tests/events.test.ts create mode 100644 scripts/check-consumer.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92e1ed3..cf05821 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,23 @@ jobs: (cd "$dir" && npm pack --dry-run) echo "::endgroup::" done + + # Packs every workspace package into a real tarball and installs them into + # an isolated fixture consumer, then typechecks and runs it. Inside the + # workspace every import resolves locally regardless of a package's + # `exports` map, so `bun test` and `npm pack --dry-run` above cannot catch + # a broken or missing published entry point — only a real tarball install + # fails the way a downstream consumer would. + consumer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.4.0 + - name: Install dependencies + run: bun install --frozen-lockfile + - name: Build + run: bun run build + - name: Consumer check + run: bun run test:consumer diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 05d856b..bff8fb2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,13 +1,13 @@ name: Publish to npm -# Publishes on a per-package version tag (protocol-v0.12.0, platform-v0.17.4, +# Publishes on a per-package version tag (musicos-v0.1.0, partyos-v0.1.0, platform-v0.25.0, # streaming-v0.1.0, transcoding-v0.3.0, …), so a release is a deliberate act rather # than a side effect of merging, and releasing one workspace package never # implies another. Failed tag jobs # can be re-run from Actions without permitting a branch to publish a package. on: push: - tags: ["protocol-v*", "platform-v*", "streaming-v*", "transcoding-v*"] + tags: ["musicos-v*", "partyos-v*", "platform-v*", "streaming-v*", "transcoding-v*"] concurrency: group: publish-${{ github.ref }} @@ -53,9 +53,13 @@ jobs: run: | TAG="${GITHUB_REF_NAME}" case "$TAG" in - protocol-v*) - DIR="packages/protocol" - VERSION="${TAG#protocol-v}" + musicos-v*) + DIR="packages/musicos" + VERSION="${TAG#musicos-v}" + ;; + partyos-v*) + DIR="packages/partyos" + VERSION="${TAG#partyos-v}" ;; platform-v*) DIR="packages/platform" @@ -70,7 +74,7 @@ jobs: VERSION="${TAG#transcoding-v}" ;; *) - echo "::error::Tag '$TAG' does not match protocol-v*, platform-v*, streaming-v*, or transcoding-v*" + echo "::error::Tag '$TAG' does not match musicos-v*, partyos-v*, platform-v*, streaming-v*, or transcoding-v*" exit 1 ;; esac diff --git a/CLAUDE.md b/CLAUDE.md index 9c85659..ed3c414 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,14 +26,46 @@ extrapolate from other blockchains. ## Project Structure +This is a Bun workspace of publishable packages under `packages/*`: + +- `packages/musicos` — `@misofm/musicos`, the works object model SDK (Composition, + Recording, Release, Track). +- `packages/partyos` — `@misofm/partyos`, the Party object model SDK (Party, admin cap, + group membership). +- `packages/platform` — `@misofm/platform`, everything Miso builds on top of the object + models (work and Party extensions, royalty primitives, Vault, Actions, product workflows). +- `packages/streaming`, `packages/transcoding` — the streaming transcode contract and + transcoder. + +Within `packages/musicos`, `packages/partyos`, and `packages/platform`: + - `src/` — public TypeScript SDK, transaction builders, reads, and generated bindings -- `src/contracts/` — generated bindings; regenerate with `bun run codegen` +- `src/contracts/` — generated bindings; regenerate with `bun run codegen` at the repo root - `tests/` — Bun unit and transaction-shape tests -- `sui-codegen.config.ts` — sibling Move package inputs for binding generation + +At the repo root: + +- `sui-codegen.config.ts` — the three generated trees' Move package inputs, and the source of + truth for which package lands in `packages/musicos/src/contracts/`, + `packages/partyos/src/contracts/`, or `packages/platform/src/contracts/`. See the "Expected checkout layout" comment there for + where this repo expects its sibling Move package checkouts to live — do not restate that + layout elsewhere. +- `scripts/codegen.ts` — the codegen runner that generates every tree. + +## The object-model / platform boundary + +> `@misofm/musicos` and `@misofm/partyos` are the object models. Everything Miso offers on top +> of them is platform. + +When adding a feature, decide which package it belongs to using that rule, not convenience: +if it is intrinsic to a Composition/Recording/Release/Track, it belongs in `musicos`; if it is +intrinsic to a Party (identity, admin cap, membership), it belongs in `partyos`; if it is an +opinion Miso's product adds on top — including every `party_*` extension — it belongs in +`platform`. ## Project Rules - Use `@mysten/sui` v2 APIs and gRPC/Core client patterns; do not add JSON-RPC. -- Do not hand-edit generated files under `src/contracts/`. +- Do not hand-edit generated files under any `packages/*/src/contracts/`. - Keep PTB helpers composable: accept a `Transaction`, return results when useful, and do not execute. - Run `bun run typecheck`, `bun test`, `bun run build`, and `bun run codegen:check` before handoff. diff --git a/README.md b/README.md index d21e40a..075f61e 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ Monorepo for the Miso platform-layer TypeScript SDKs. | Package | Description | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| [`packages/platform`](packages/platform/README.md) | `@misofm/platform` — the complete client SDK for the Miso platform layer: catalog/artist/wallet reads, record production and sale, and Vault custody/Actions/plugins. | -| [`packages/protocol`](packages/protocol/README.md) | `@misofm/protocol` — typed bindings, queries, event decoders, and PTB builders for the Miso protocol and Party package set. | +| [`packages/musicos`](packages/musicos/README.md) | `@misofm/musicos` — typed bindings, queries, event decoders, and PTB builders for the works object model (Composition, Recording, Release, Track). | +| [`packages/partyos`](packages/partyos/README.md) | `@misofm/partyos` — typed bindings, queries, and PTB builders for the Party object model (Party, admin cap, group membership). | +| [`packages/platform`](packages/platform/README.md) | `@misofm/platform` — the complete client SDK for the Miso platform layer: work and Party extensions, catalog/artist/wallet reads, record production and sale, and Vault custody/Actions/plugins. | | [`packages/streaming`](packages/streaming/README.md) | `@misofm/streaming` — the `miso-hls/v1` streaming transcode contract (ladder, segment policy, Quilt item naming) plus a browser player entry point. | | [`packages/transcoding`](packages/transcoding/README.md) | `@misofm/transcoding` — deterministic AAC-LC fMP4 HLS transcoder that produces and verifies the `miso-hls/v1` layout. Node only, FFmpeg required. | @@ -13,6 +14,25 @@ This root `package.json` is private; each package under `packages/*` is published independently. See each package's own README for install instructions and API surface. +## The object-model / platform boundary + +The three Sui packages split along one rule: + +> `@misofm/musicos` and `@misofm/partyos` are the object models. Everything Miso +> offers on top of them is platform. + +`@misofm/musicos` binds only the `musicos` Move package (Composition, Recording, +Release, Track) and `@misofm/partyos` only the `partyos` Move package (Party, +admin cap, group membership) — the permissionless object models anyone can +build on. Everything else Miso's own product adds on top — work and Party +extensions, royalty primitives, Vault, Actions, and every product-specific +workflow — ships from `@misofm/platform`. Generated bindings follow the same +split: `packages/musicos/src/contracts/` and `packages/partyos/src/contracts/` +hold only their object model, and `packages/platform/src/contracts/` holds +everything else. `sui-codegen.config.ts` +is the source of truth for exactly which Move package lands in which tree — +see its `packages` list rather than looking for an enumeration here. + ## Development This is a Bun workspace. diff --git a/bun.lock b/bun.lock index 7ad340d..7ddc70d 100644 --- a/bun.lock +++ b/bun.lock @@ -11,30 +11,27 @@ "typescript": "^7.0.2", }, }, - "packages/platform": { - "name": "@misofm/platform", - "version": "0.24.0", + "packages/musicos": { + "name": "@misofm/musicos", + "version": "0.2.0", "dependencies": { - "@misofm/protocol": "0.17.0", - "@misofm/streaming": "0.1.0", "@mysten/bcs": "^2.1.1", }, "devDependencies": { - "@mysten/codegen": "^0.11.14", - "@mysten/move-bytecode-template": "^0.4.0", "@mysten/sui": "2.29.0", - "@types/bun": "^1.3.14", + "@types/bun": "^1.4.0", + "typedoc": "^0.28.20", + "typedoc-plugin-markdown": "^4.13.0", "typescript": "^7.0.2", }, "peerDependencies": { - "@mysten/move-bytecode-template": "^0.4.0", "@mysten/sui": "2.29.0", "typescript": "^5.7 || ^6 || ^7", }, }, - "packages/protocol": { - "name": "@misofm/protocol", - "version": "0.17.0", + "packages/partyos": { + "name": "@misofm/partyos", + "version": "0.2.0", "dependencies": { "@mysten/bcs": "^2.1.1", }, @@ -50,6 +47,27 @@ "typescript": "^5.7 || ^6 || ^7", }, }, + "packages/platform": { + "name": "@misofm/platform", + "version": "0.25.0", + "dependencies": { + "@misofm/musicos": "0.2.0", + "@misofm/partyos": "0.2.0", + "@misofm/streaming": "0.2.1", + "@mysten/bcs": "^2.1.1", + }, + "devDependencies": { + "@mysten/move-bytecode-template": "^0.4.0", + "@mysten/sui": "2.29.0", + "@types/bun": "^1.3.14", + "typescript": "^7.0.2", + }, + "peerDependencies": { + "@mysten/move-bytecode-template": "^0.4.0", + "@mysten/sui": "2.29.0", + "typescript": "^5.7 || ^6 || ^7", + }, + }, "packages/streaming": { "name": "@misofm/streaming", "version": "0.2.1", @@ -69,7 +87,7 @@ "name": "@misofm/transcoding", "version": "0.3.0", "dependencies": { - "@misofm/streaming": "0.1.0", + "@misofm/streaming": "0.2.1", "m3u8-parser": "7.2.0", }, "devDependencies": { @@ -93,10 +111,6 @@ "@0no-co/graphqlsp": ["@0no-co/graphqlsp@1.17.5", "", { "dependencies": { "@gql.tada/internal": "^1.2.1", "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0" }, "peerDependencies": { "typescript": "^5.0.0 || ^6.0.0" } }, "sha512-wsygfTO3pdkz4idDw8BxdMdgB9AB8nuJDJTUwYy87BiZEZRXS4bEfn4oNl9VtxTo6j2lzX/gED+IvOgxsSy2Jg=="], - "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], "@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="], @@ -111,9 +125,11 @@ "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], - "@misofm/platform": ["@misofm/platform@workspace:packages/platform"], + "@misofm/musicos": ["@misofm/musicos@workspace:packages/musicos"], - "@misofm/protocol": ["@misofm/protocol@workspace:packages/protocol"], + "@misofm/partyos": ["@misofm/partyos@workspace:packages/partyos"], + + "@misofm/platform": ["@misofm/platform@workspace:packages/platform"], "@misofm/streaming": ["@misofm/streaming@workspace:packages/streaming"], @@ -245,8 +261,6 @@ "bun-types": ["bun-types@1.4.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-loKuVrAFZKfEv+JvWkHRS9GW5IqLuLRjVXN9p+vZvBN86O5hf/pBZQ5hSoyipsrMmWObZBDvWnlmKvjKTM0PdA=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], "cosmiconfig": ["cosmiconfig@10.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "js-yaml": "^5.4.1" } }, "sha512-/zvGoYn0y27QTBmK+0B+oPdEPC4fKGUmJsMTQbDjqph00TrkqfVFW/M+C6ya54sYCgHC6QCwK/mI6djt8cEGHQ=="], @@ -263,8 +277,6 @@ "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], @@ -277,18 +289,8 @@ "hls.js": ["hls.js@1.6.13", "", {}, "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@5.4.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.mjs" } }, "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ=="], - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - "linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="], "lunr": ["lunr@2.3.9", "", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="], @@ -311,12 +313,6 @@ "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "playwright": ["playwright@1.55.0", "", { "dependencies": { "playwright-core": "1.55.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA=="], "playwright-core": ["playwright-core@1.55.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg=="], @@ -333,8 +329,6 @@ "redis": ["redis@6.2.1", "", { "dependencies": { "@redis/bloom": "6.2.1", "@redis/client": "6.2.1", "@redis/json": "6.2.1", "@redis/search": "6.2.1", "@redis/time-series": "6.2.1" } }, "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "toml": ["toml@5.0.0", "", {}, "sha512-bsqEd+g7fzlrw7LEynQ6W6aOK/lTOlepz5x1sJyIV9fTZVJS2Q76nuju6FK+gZhW5bUpvB7mCEqdR0u4WziMFw=="], "typedoc": ["typedoc@0.28.20", "", { "dependencies": { "@gerrit0/mini-shiki": "^3.23.0", "lunr": "^2.3.9", "markdown-it": "^14.3.0", "minimatch": "^10.2.5", "yaml": "^2.9.0" }, "peerDependencies": { "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" }, "bin": { "typedoc": "bin/typedoc" } }, "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg=="], @@ -357,12 +351,6 @@ "zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], - "@misofm/platform/@misofm/streaming": ["@misofm/streaming@0.1.0", "", { "peerDependencies": { "hls.js": "^1.6" }, "optionalPeers": ["hls.js"] }, "sha512-amxvRkj/OOjhFJrT8EPflglkSGHYDqiYV1HEjwAqYFuJTKNEVXuxG9s1jnDdaW1AzssKljx/umKB3vUKyrRO9w=="], - - "@misofm/platform/@mysten/codegen": ["@mysten/codegen@0.11.14", "", { "dependencies": { "@mysten/bcs": "^2.1.0", "@mysten/sui": "^2.24.0", "@stricli/auto-complete": "^1.2.7", "@stricli/core": "^1.2.7", "@types/node": "^25.9.3", "cosmiconfig": "^9.0.2", "prettier": "^3.8.4", "toml": "^4.1.1", "typescript": "^6.0.3", "zod": "^4.4.3" }, "bin": { "sui-ts-codegen": "dist/bin/cli.mjs", "__sui-ts-codegen_bash_complete": "dist/bin/bash-complete.mjs" } }, "sha512-QOMeI/uKzlHW9LblCDgGeByVxGBIHNRB0HpsqLGgk1hrXguv9QSZIQ5OqYEz/ZetyipncsTnyU4XQQppR7WoaA=="], - - "@misofm/transcoding/@misofm/streaming": ["@misofm/streaming@0.1.0", "", { "peerDependencies": { "hls.js": "^1.6" }, "optionalPeers": ["hls.js"] }, "sha512-amxvRkj/OOjhFJrT8EPflglkSGHYDqiYV1HEjwAqYFuJTKNEVXuxG9s1jnDdaW1AzssKljx/umKB3vUKyrRO9w=="], - "@misofm/transcoding/@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], "@misofm/transcoding/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -377,16 +365,6 @@ "bun-types/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], - "@misofm/platform/@mysten/codegen/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - - "@misofm/platform/@mysten/codegen/cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], - - "@misofm/platform/@mysten/codegen/prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], - - "@misofm/platform/@mysten/codegen/toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], - - "@misofm/platform/@mysten/codegen/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "@misofm/transcoding/@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], "@mysten/codegen/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], @@ -395,10 +373,6 @@ "bun-types/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "@misofm/platform/@mysten/codegen/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - - "@misofm/platform/@mysten/codegen/cosmiconfig/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], - "@misofm/transcoding/@types/bun/bun-types/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], "@misofm/transcoding/@types/bun/bun-types/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], diff --git a/package.json b/package.json index b5200d2..7b727d8 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,16 @@ "name": "@misofm/sdk-monorepo", "private": true, "packageManager": "bun@1.4.0", - "workspaces": ["packages/*"], + "workspaces": [ + "packages/*" + ], "scripts": { "typecheck": "bun run --filter '*' typecheck", "test": "bun run --filter '*' test", - "build": "bun run --filter '*' build", + "build": "bun run --filter @misofm/streaming build && bun run --filter @misofm/musicos build && bun run --filter @misofm/partyos build && bun run --filter @misofm/platform build && bun run --filter @misofm/transcoding build", "codegen": "bun scripts/codegen.ts", - "codegen:check": "bun run codegen && git diff --exit-code -- packages/protocol/src/contracts" + "codegen:check": "bun run codegen && git diff --exit-code -- packages/musicos/src/contracts packages/partyos/src/contracts packages/platform/src/contracts", + "test:consumer": "bun scripts/check-consumer.ts" }, "devDependencies": { "@mysten/codegen": "^0.12.6", diff --git a/packages/protocol/LICENSE b/packages/musicos/LICENSE similarity index 100% rename from packages/protocol/LICENSE rename to packages/musicos/LICENSE diff --git a/packages/musicos/README.md b/packages/musicos/README.md new file mode 100644 index 0000000..abc5d61 --- /dev/null +++ b/packages/musicos/README.md @@ -0,0 +1,75 @@ +# @misofm/musicos + +`@misofm/musicos` is the object model. Everything Miso offers on top of it — +work extensions, royalty primitives, Party, and every product-specific +workflow — is platform, and lives in +[`@misofm/platform`](../platform/README.md). This package holds ONLY the +typed bindings, queries, event decoders, and PTB builders for the `musicos` +Move package: Composition, Recording, Release, and Track. + +## Usage + +Register the `miso()` client extension on any `@mysten/sui` Core-API client: + +```ts +import { SuiGrpcClient } from "@mysten/sui/grpc"; +import { miso } from "@misofm/musicos/client"; + +const client = new SuiGrpcClient({ network: "testnet" }).$extend(miso()); + +const composition = await client.miso.getCompositionById("0x..."); +``` + +`miso()` resolves the bundled, verified `MISO_DEPLOYMENTS` manifest for the +client's network by default. Pass an explicit `deployment` to override it — +either the minimal `{ packageId }` shape (core calls, types, and derived IDs +only) or a complete `MisoDeployment` (also unlocks `client.miso.packages`, +the full generated call/BCS bindings bound to that manifest). + +## Exports + +| Subpath | Purpose | +| --------------------------- | ------------------------------------------------------- | +| `@misofm/musicos` | Root entrypoint | +| `@misofm/musicos/client` | Client construction (`miso()`, `MisoProtocolClient`) | +| `@misofm/musicos/deployments` | Deployed package IDs by network | +| `@misofm/musicos/queries` | Read queries over Composition, Recording, Release, Track | +| `@misofm/musicos/transactions` | PTB command builders | +| `@misofm/musicos/execute` | Transaction execution helpers | +| `@misofm/musicos/view` | `simulateTransaction`-backed view helpers | +| `@misofm/musicos/types` | Shared TypeScript types | +| `@misofm/musicos/parsers` | Event parsers | +| `@misofm/musicos/events` | Event decoders | +| `@misofm/musicos/packages` | Module→package bindings | +| `@misofm/musicos/contracts` and `@misofm/musicos/contracts/*` | Generated ABI-bound bindings (BCS structs + Move calls) | + +## Deployment manifest + +`MisoDeployment` has exactly one key, `musicos` — the published `musicos` +package address. `MISO_DEPLOYMENTS` bundles the verified immutable manifest +per network (currently `testnet`); `getMisoDeployment(network)` resolves it, +failing closed for networks without a bundled manifest. Pass an explicit +manifest to `miso({ deployment })` to override it, e.g. for a local or +freshly-published deployment. + +## Everything else is platform + +Work extensions (composition/recording/release credits, advisory ratings, +genres, DSP links, cover art, ...), royalty primitives (royalty pools, +stakes, routed stakes), Party (`partyos` and its extensions), and every +other first-party Move package live in `@misofm/platform`. See +[`sui-codegen.config.ts`](../../sui-codegen.config.ts) at the repo root for +the authoritative list of Move packages and which generated tree each one +lands in. + +## Install + +```sh +bun add @misofm/musicos +``` + +Peer dependency: `@mysten/sui@2.29.0` (exact). + +## License + +Apache-2.0 diff --git a/packages/protocol/package.json b/packages/musicos/package.json similarity index 91% rename from packages/protocol/package.json rename to packages/musicos/package.json index b897cd2..8a76194 100644 --- a/packages/protocol/package.json +++ b/packages/musicos/package.json @@ -1,8 +1,8 @@ { - "name": "@misofm/protocol", - "version": "0.17.0", + "name": "@misofm/musicos", + "version": "0.2.0", "packageManager": "bun@1.4.0", - "description": "Typed bindings, queries, event decoders, and PTB builders for the Miso protocol and Party package set on Sui.", + "description": "Typed bindings, queries, event decoders, and PTB builders for the Miso object model (Composition, Recording, Release, Track) on Sui.", "license": "Apache-2.0", "repository": { "type": "git", @@ -18,7 +18,11 @@ "miso", "music", "blockchain", - "sdk" + "sdk", + "object-model", + "composition", + "recording", + "release" ], "publishConfig": { "access": "public" @@ -80,10 +84,6 @@ "types": "./dist/packages.d.ts", "default": "./dist/packages.js" }, - "./party": { - "types": "./dist/party/index.d.ts", - "default": "./dist/party/index.js" - }, "./contracts": { "types": "./dist/contracts.d.ts", "default": "./dist/contracts.js" diff --git a/packages/protocol/scripts/codegen-output.ts b/packages/musicos/scripts/codegen-output.ts similarity index 100% rename from packages/protocol/scripts/codegen-output.ts rename to packages/musicos/scripts/codegen-output.ts diff --git a/packages/protocol/src/client.ts b/packages/musicos/src/client.ts similarity index 91% rename from packages/protocol/src/client.ts rename to packages/musicos/src/client.ts index 7d7613b..3c59429 100644 --- a/packages/protocol/src/client.ts +++ b/packages/musicos/src/client.ts @@ -17,7 +17,6 @@ import { import { bindModulePackage, MisoPackageBindings, REF_RETURNING_CALLS } from "./packages.ts"; import * as parsers from "./parsers.ts"; import { eventParsers } from "./events.ts"; -import { PartyProtocolClient } from "./party/client.ts"; import * as queries from "./queries.ts"; import * as view from "./view.ts"; import type { @@ -30,10 +29,10 @@ import type { } from "./types.ts"; // Generated call modules (type-safe Move calls) and BCS structs. -import * as composition from "./contracts/miso/composition.ts"; -import * as recording from "./contracts/miso/recording.ts"; -import * as release from "./contracts/miso/release.ts"; -import * as track from "./contracts/miso/track.ts"; +import * as composition from "./contracts/musicos/composition.ts"; +import * as recording from "./contracts/musicos/recording.ts"; +import * as release from "./contracts/musicos/release.ts"; +import * as track from "./contracts/musicos/track.ts"; export interface MisoOptions { /** Name for the client extension. Defaults to "miso". */ @@ -57,7 +56,7 @@ export interface MisoOptions { function isFullDeployment( deployment: MisoProtocolDeployment | MisoDeployment, ): deployment is MisoDeployment { - return "miso" in deployment; + return "musicos" in deployment; } /** @@ -111,7 +110,6 @@ export class MisoProtocolClient { #graphqlClient?: SuiGraphQLClient; #deployment: MisoProtocolDeployment; #packageDeployment?: MisoDeployment; - #partyClient?: PartyProtocolClient; constructor(client: ClientWithCoreApi, options: MisoProtocolClientOptions) { this.#client = client; @@ -120,9 +118,6 @@ export class MisoProtocolClient { this.#packageDeployment = options.packageDeployment ? normalizeMisoDeployment(options.packageDeployment) : undefined; - this.#partyClient = this.#packageDeployment - ? new PartyProtocolClient(client, this.#packageDeployment) - : undefined; } /** The exact deployment selected for this client. */ @@ -137,24 +132,13 @@ export class MisoProtocolClient { get packages(): MisoPackageBindings { if (!this.#packageDeployment) { throw new Error( - "@misonetwork/sdk: full package bindings require a complete MisoDeployment. " + + "@misofm/musicos: full package bindings require a complete MisoDeployment. " + "Pass the verified post-publish manifest to miso({ deployment }).", ); } return new MisoPackageBindings(this.#packageDeployment); } - /** Party identity, profile, membership, and extension APIs. */ - get party(): PartyProtocolClient { - if (!this.#partyClient) { - throw new Error( - "@misonetwork/sdk: Party APIs require a complete MisoDeployment. " + - "Pass the verified post-publish manifest to miso({ deployment }).", - ); - } - return this.#partyClient; - } - get #misoPackageId(): string { return this.#deployment.packageId; } diff --git a/packages/musicos/src/contracts.ts b/packages/musicos/src/contracts.ts new file mode 100644 index 0000000..eb16858 --- /dev/null +++ b/packages/musicos/src/contracts.ts @@ -0,0 +1,29 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Barrel for the codegen-generated, ABI-bound bindings (BCS structs + type-safe +// Move calls). Re-exported from the package root as the `contracts` namespace. + +// Every package here is generated from the current first-party Move source. The +// generated default `@local-pkg/*` identities are source labels only: callers +// must inject exact published addresses through `MisoDeployment` before building +// a transaction. Do not use a historic package ID as a substitute for a fresh +// deployment. + +type PublicModule = Omit; +function withoutUnsafeCalls( + module: M, + keys: K, +): PublicModule { + return Object.fromEntries(Object.entries(module).filter(([key]) => !keys.includes(key as keyof M))) as PublicModule; +} + +import * as rawComposition from "./contracts/musicos/composition.ts"; +import * as rawRecording from "./contracts/musicos/recording.ts"; +import * as rawRelease from "./contracts/musicos/release.ts"; + +// Object model core (BCS codecs remain available; PTB-inaccessible references do not). +export const composition = withoutUnsafeCalls(rawComposition, ["title", "uid", "uidMut"] as const); +export const recording = withoutUnsafeCalls(rawRecording, ["uid", "uidMut"] as const); +export const release = withoutUnsafeCalls(rawRelease, ["title", "tracks", "uid", "uidMut"] as const); +export * as track from "./contracts/musicos/track.ts"; diff --git a/packages/protocol/src/contracts/miso/composition.ts b/packages/musicos/src/contracts/musicos/composition.ts similarity index 94% rename from packages/protocol/src/contracts/miso/composition.ts rename to packages/musicos/src/contracts/musicos/composition.ts index 5f29c28..5cc67f9 100644 --- a/packages/protocol/src/contracts/miso/composition.ts +++ b/packages/musicos/src/contracts/musicos/composition.ts @@ -4,9 +4,9 @@ /** - * Represents a musical composition (song, instrumental work) in Miso. Compositions - * are the underlying written works that recordings are based on. Each composition - * has its own share token for ownership distribution. + * Represents a musical composition (song, instrumental work) in musicos. + * Compositions are the underlying written works that recordings are based on. Each + * composition has its own share token for ownership distribution. * * ### Key Features: * @@ -42,7 +42,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction } from '@mysten/sui/transactions'; import * as bps from './deps/bps/bps.ts'; -const $moduleName = '@local-pkg/miso::composition'; +const $moduleName = '@local-pkg/musicos::composition'; /** Lifecycle state of a composition. */ export const CompositionState = new MoveEnum({ name: `${$moduleName}::CompositionState`, fields: { /** Composition is initialized but not published. */ @@ -104,7 +104,7 @@ export interface NewOptions { * - Initial share token balance */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ '0x1::string::String', 'u16', @@ -142,7 +142,7 @@ export interface PublishOptions { * extension and may be attached before or after publish via `uid_mut`. */ export function publish(options: PublishOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null, @@ -171,7 +171,7 @@ export interface TitleOptions { } /** Returns the primary title. */ export function title(options: TitleOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -202,7 +202,7 @@ export interface RoyaltyRateOptions { * value `recording::new` will apply. */ export function royaltyRate(options: RoyaltyRateOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -229,7 +229,7 @@ export interface UidOptions { } /** Returns a reference to the composition's UID for reading dynamic fields. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -264,7 +264,7 @@ export interface UidMutOptions { * fields attached by other extensions. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null diff --git a/packages/protocol/src/contracts/miso/deps/bps/bps.ts b/packages/musicos/src/contracts/musicos/deps/bps/bps.ts similarity index 100% rename from packages/protocol/src/contracts/miso/deps/bps/bps.ts rename to packages/musicos/src/contracts/musicos/deps/bps/bps.ts diff --git a/packages/protocol/src/contracts/miso/recording.ts b/packages/musicos/src/contracts/musicos/recording.ts similarity index 94% rename from packages/protocol/src/contracts/miso/recording.ts rename to packages/musicos/src/contracts/musicos/recording.ts index 25301f0..e0595e7 100644 --- a/packages/protocol/src/contracts/miso/recording.ts +++ b/packages/musicos/src/contracts/musicos/recording.ts @@ -4,9 +4,9 @@ /** - * Represents an audio recording of a composition in Miso. Recordings are the audio - * performances that are distributed and played. Each recording has its own share - * token for ownership distribution. + * Represents an audio recording of a composition in musicos. Recordings are the + * audio performances that are distributed and played. Each recording has its own + * share token for ownership distribution. * * ### Key Features: * @@ -47,7 +47,7 @@ * * The recording carries its parent composition's identity two ways. The * `CompositionShare` phantom type parameter is the durable identity (a share - * currency is published independently of miso and survives a fresh republish, + * currency is published independently of musicos and survives a fresh republish, * whereas an object ID does not) and makes the recording↔composition lineage * compile-time enforced wherever the two meet. The embedded `composition_id` is * the address-level handle: Move cannot chase a type (or an ID) to an object, so @@ -66,7 +66,7 @@ import { MoveEnum, MoveStruct, MoveTuple, normalizeMoveArguments, type RawTransa import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction } from '@mysten/sui/transactions'; -const $moduleName = '@local-pkg/miso::recording'; +const $moduleName = '@local-pkg/musicos::recording'; /** Lifecycle state of a recording. */ export const RecordingState = new MoveEnum({ name: `${$moduleName}::RecordingState`, fields: { /** Recording is being set up and can be modified. */ @@ -155,7 +155,7 @@ export interface NewOptions { * cut) */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null, @@ -193,7 +193,7 @@ export interface PublishOptions { * extension and may be attached before or after publish via `uid_mut`. */ export function publish(options: PublishOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null, @@ -228,7 +228,7 @@ export interface CompositionIdOptions { * ownership. */ export function compositionId(options: CompositionIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -256,7 +256,7 @@ export interface UidOptions { } /** Returns a reference to the recording's UID for reading dynamic fields. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -292,7 +292,7 @@ export interface UidMutOptions { * the object, including fields attached by other extensions. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null diff --git a/packages/protocol/src/contracts/miso/release.ts b/packages/musicos/src/contracts/musicos/release.ts similarity index 95% rename from packages/protocol/src/contracts/miso/release.ts rename to packages/musicos/src/contracts/musicos/release.ts index 4f468a3..b6a5fa3 100644 --- a/packages/protocol/src/contracts/miso/release.ts +++ b/packages/musicos/src/contracts/musicos/release.ts @@ -4,7 +4,7 @@ /** - * Represents a music release in Miso. A release is an ordered tracklist with + * Represents a music release in musicos. A release is an ordered tracklist with * per-track revenue distribution configuration. Cover art lives in the `cover_art` * extension, not in core. * @@ -79,7 +79,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as track from './track.ts'; -const $moduleName = '@local-pkg/miso::release'; +const $moduleName = '@local-pkg/musicos::release'; /** Lifecycle state of a release. */ export const ReleaseState = new MoveEnum({ name: `${$moduleName}::ReleaseState`, fields: { /** Release is initialized but not yet published. */ @@ -141,7 +141,7 @@ export interface NewOptions { * PTB. */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, '0x1::string::String', @@ -177,7 +177,7 @@ export interface DeriveTargetReleaseIdOptions { * for clients preparing tracks. */ export function deriveTargetReleaseId(options: DeriveTargetReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, 'vector<0x2::object::ID>', @@ -208,7 +208,7 @@ export interface ReleaseRegistryIdOptions { * `release::id(&Release)` ABI function. */ export function releaseRegistryId(options: ReleaseRegistryIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -239,7 +239,7 @@ export interface PublishOptions { * extension and may be attached before or after publish via `uid_mut`. */ export function publish(options: PublishOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null, @@ -266,7 +266,7 @@ export interface AuthorizeOptions { } /** Verifies that the admin capability matches this release. */ export function authorize(options: AuthorizeOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null @@ -290,7 +290,7 @@ export interface TitleOptions { } /** Returns the release title. */ export function title(options: TitleOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -317,7 +317,7 @@ export interface TracksOptions { * (`tracks().length()`, `tracks().any!(..)`, indexing). */ export function tracks(options: TracksOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -340,7 +340,7 @@ export interface ReleaseAdminCapReleaseIdOptions { } /** Returns the release ID associated with the admin capability. */ export function releaseAdminCapReleaseId(options: ReleaseAdminCapReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -363,7 +363,7 @@ export interface UidOptions { } /** Returns a reference to the release's UID for reading dynamic fields. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -394,7 +394,7 @@ export interface UidMutOptions { * fields attached by other extensions. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null diff --git a/packages/protocol/src/contracts/miso/track.ts b/packages/musicos/src/contracts/musicos/track.ts similarity index 96% rename from packages/protocol/src/contracts/miso/track.ts rename to packages/musicos/src/contracts/musicos/track.ts index f2085b2..0f3da84 100644 --- a/packages/protocol/src/contracts/miso/track.ts +++ b/packages/musicos/src/contracts/musicos/track.ts @@ -33,7 +33,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as bps from './deps/bps/bps.ts'; -const $moduleName = '@local-pkg/miso::track'; +const $moduleName = '@local-pkg/musicos::track'; /** * Lifecycle state of a track within a release. A track is born `Unassigned`, * carrying the target release id the consent committed to at creation (the release @@ -136,7 +136,7 @@ export interface NewOptions { * from a phantom. */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null, null, @@ -163,7 +163,7 @@ export interface RecordingIdOptions { } /** Returns the ID of the recording. */ export function recordingId(options: RecordingIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -191,7 +191,7 @@ export interface CompositionIdOptions { * ownership, and a track routes its full split to the recording. */ export function compositionId(options: CompositionIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -214,7 +214,7 @@ export interface SplitBpsOptions { } /** Returns this track's share of the release's revenue (in basis points). */ export function splitBps(options: SplitBpsOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -241,7 +241,7 @@ export interface TargetReleaseIdOptions { * so its release is the object you fetched it from. */ export function targetReleaseId(options: TargetReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso'; + const packageAddress = options.package ?? '@local-pkg/musicos'; const argumentsTypes = [ null ] satisfies (string | null)[]; diff --git a/packages/protocol/src/contracts/utils/index.ts b/packages/musicos/src/contracts/utils/index.ts similarity index 100% rename from packages/protocol/src/contracts/utils/index.ts rename to packages/musicos/src/contracts/utils/index.ts diff --git a/packages/musicos/src/deployments.ts b/packages/musicos/src/deployments.ts new file mode 100644 index 0000000..6372e99 --- /dev/null +++ b/packages/musicos/src/deployments.ts @@ -0,0 +1,166 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { normalizeSuiAddress } from "@mysten/sui/utils"; + +/** Sui networks for which this SDK may bundle a verified deployment. */ +export type MisoNetwork = "mainnet" | "testnet"; + +/** + * The single independently published package required by this SDK surface: + * the `musicos` object-model package (modules composition, recording, + * release, track). + * + * This name is a stable deployment-manifest key, not a Move module name. Its + * value is never inferred from a source label or a previous publish. + */ +const CANONICAL_MISO_PACKAGE_NAMES = ["musicos"] as const; + +/** Public immutable copy; validation always uses the private canonical tuple. */ +export const MISO_PACKAGE_NAMES = Object.freeze([ + ...CANONICAL_MISO_PACKAGE_NAMES, +]) as readonly [...typeof CANONICAL_MISO_PACKAGE_NAMES]; + +export type MisoPackageName = (typeof CANONICAL_MISO_PACKAGE_NAMES)[number]; + +/** + * Exact package address from one compatible publish. + * + * A complete manifest is intentional: a caller cannot accidentally combine + * generated bindings for a fresh ABI with a stale package ID. Fill this only + * from the verified immutable `admin-cli` publish record after the + * dependency-ordered release is complete. + */ +export type MisoDeployment = Readonly>; + +/** A minimal core-only deployment, retained for callers that use only `miso()`. */ +export interface MisoProtocolDeployment { + /** The published `musicos` package used for calls, types, and derived IDs. */ + readonly packageId: string; +} + +/** Validate and freeze a core-only package binding at a client boundary. */ +export function normalizeMisoProtocolDeployment( + deployment: unknown, +): MisoProtocolDeployment { + if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { + throw new Error("@misofm/musicos: core deployment must be an object with a normalized Miso package ID."); + } + const packageId = (deployment as { packageId?: unknown }).packageId; + if (typeof packageId !== "string") { + throw new Error("@misofm/musicos: core deployment is missing a Miso package ID."); + } + let normalized: string; + try { + normalized = normalizeSuiAddress(packageId); + } catch { + throw new Error("@misofm/musicos: core deployment package ID is not a valid Sui address."); + } + if (packageId !== normalized) { + throw new Error( + `@misofm/musicos: core deployment package ID must be normalized (${normalized}).`, + ); + } + return Object.freeze({ packageId }); +} + +/** Verified immutable deployments bundled with this SDK release. */ +export const MISO_DEPLOYMENTS = Object.freeze({ + testnet: Object.freeze({ + musicos: "0x2a4f8d83bffa73a13c9cfefdc4376256d4cef4330d5f60233c082cfab9a34e68", + } as const), +} as const) satisfies Partial< + Record +>; + +/** + * @deprecated Use `MISO_DEPLOYMENTS`. This alias preserves source compatibility + * for callers that still use the former protocol-only constant name. + */ +export const MISO_PROTOCOL_DEPLOYMENTS = MISO_DEPLOYMENTS; + +/** Resolve a verified full manifest, failing closed for unbundled networks. */ +export function getMisoDeployment(network: string): MisoDeployment { + const deployment = ( + MISO_DEPLOYMENTS as Partial> + )[network]; + if (!deployment) { + throw new Error( + `@misofm/musicos: no verified Miso deployment is bundled for network "${network}". ` + + "Inject the exact post-publish manifest or pass an explicit deployment; historic package IDs are rejected.", + ); + } + return normalizeMisoDeployment(deployment); +} + +/** Extract the core package identity from a complete deployment manifest. */ +export function protocolDeployment( + deployment: MisoDeployment, +): MisoProtocolDeployment { + return normalizeMisoProtocolDeployment({ packageId: normalizeMisoDeployment(deployment).musicos }); +} + +/** Resolve the core package only, preserving the established `miso()` default API. */ +export function getMisoProtocolDeployment( + network: string, +): MisoProtocolDeployment { + return protocolDeployment(getMisoDeployment(network)); +} + +/** + * Validates an explicit manifest before any Move target is constructed. This is + * useful at configuration boundaries such as environment-file loading. + */ +export function assertMisoDeployment( + deployment: unknown, +): asserts deployment is MisoDeployment { + if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { + throw new Error( + `@misofm/musicos: deployment must be an object with exactly ${CANONICAL_MISO_PACKAGE_NAMES.length} Miso package IDs.`, + ); + } + const entries = deployment as Record; + const keys = Object.keys(entries); + const unexpected = keys.filter((key) => !CANONICAL_MISO_PACKAGE_NAMES.includes(key as MisoPackageName)); + if (unexpected.length > 0 || keys.length !== CANONICAL_MISO_PACKAGE_NAMES.length) { + throw new Error( + `@misofm/musicos: deployment must contain exactly these package IDs: ${CANONICAL_MISO_PACKAGE_NAMES.join(", ")}.`, + ); + } + const seenPackageIds = new Set(); + for (const name of CANONICAL_MISO_PACKAGE_NAMES) { + const packageId = entries[name]; + if (typeof packageId !== "string") { + throw new Error( + `@misofm/musicos: deployment is missing package ID for "${name}". ` + + "Use a complete manifest from the fresh verified immutable admin-cli publish record.", + ); + } + let normalized: string; + try { + normalized = normalizeSuiAddress(packageId); + } catch { + throw new Error(`@misofm/musicos: deployment package ID for "${name}" is not a valid Sui address.`); + } + if (packageId !== normalized) { + throw new Error( + `@misofm/musicos: deployment package ID for "${name}" must be normalized (${normalized}).`, + ); + } + if (seenPackageIds.has(packageId)) { + throw new Error( + `@misofm/musicos: deployment package ID for "${name}" duplicates another package. ` + + "A publish manifest must bind every package to its own address.", + ); + } + seenPackageIds.add(packageId); + } +} + +/** Validate and snapshot an untrusted manifest so later caller mutation is inert. */ +export function normalizeMisoDeployment(deployment: unknown): MisoDeployment { + assertMisoDeployment(deployment); + return Object.freeze( + Object.fromEntries(CANONICAL_MISO_PACKAGE_NAMES.map((name) => [name, deployment[name]])), + ) as MisoDeployment; +} diff --git a/packages/musicos/src/events.ts b/packages/musicos/src/events.ts new file mode 100644 index 0000000..3dcefe8 --- /dev/null +++ b/packages/musicos/src/events.ts @@ -0,0 +1,30 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/** + * BCS decoders for object-model execution events outside the legacy core + * convenience parsers. Each decoder preserves the generated Move field names + * and nested layouts exactly, which is the safe indexer boundary. + */ + +import * as release from "./contracts/musicos/release.ts"; +import type { BcsParser } from "./queries.ts"; + +/** Decode raw event BCS with the corresponding generated codec. */ +export function decodeEvent(codec: BcsParser, bytes: Uint8Array): T { + return codec.parse(bytes); +} + +function decoder(codec: BcsParser) { + return (bytes: Uint8Array): T => decodeEvent(codec, bytes); +} + +/** + * Event decoder registry grouped by package responsibility. Core publication + * events retain camel-case wrappers in `parsers.ts`. + */ +export const eventParsers = { + core: { + releaseRegistryCreated: decoder(release.ReleaseRegistryCreatedEvent), + }, +} as const; diff --git a/packages/protocol/src/execute.ts b/packages/musicos/src/execute.ts similarity index 100% rename from packages/protocol/src/execute.ts rename to packages/musicos/src/execute.ts diff --git a/packages/protocol/src/index.ts b/packages/musicos/src/index.ts similarity index 63% rename from packages/protocol/src/index.ts rename to packages/musicos/src/index.ts index e12cc9a..6c6c5fe 100644 --- a/packages/protocol/src/index.ts +++ b/packages/musicos/src/index.ts @@ -1,9 +1,10 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// ABI-bound protocol bindings, including core, extensions, utilities, and -// generic royalty primitives. Product-specific workflows remain in the -// platform SDK. +// ABI-bound object-model bindings ONLY: Composition, Recording, Release, and +// Track (the `musicos` Move package). `@misofm/musicos` is the object model; +// everything Miso offers on top of it — extensions, Party, royalty +// primitives, and product-specific workflows — lives in `@misofm/platform`. export * from "./types.ts"; export * from "./transactions.ts"; export * from "./execute.ts"; @@ -15,8 +16,6 @@ export * from "./view.ts"; export * from "./client.ts"; export * from "./deployments.ts"; export * from "./packages.ts"; -export * as party from "./party/index.ts"; -export type { PartyProtocolClient } from "./party/client.ts"; // Generated, ABI-bound bindings (BCS structs + type-safe Move calls). export * as contracts from "./contracts.ts"; diff --git a/packages/protocol/src/internal.ts b/packages/musicos/src/internal.ts similarity index 100% rename from packages/protocol/src/internal.ts rename to packages/musicos/src/internal.ts diff --git a/packages/protocol/src/numeric.ts b/packages/musicos/src/numeric.ts similarity index 100% rename from packages/protocol/src/numeric.ts rename to packages/musicos/src/numeric.ts diff --git a/packages/musicos/src/packages.ts b/packages/musicos/src/packages.ts new file mode 100644 index 0000000..7449fde --- /dev/null +++ b/packages/musicos/src/packages.ts @@ -0,0 +1,117 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Package-bound generated APIs. + * + * `@mysten/codegen` intentionally emits `@local-pkg/*` source labels. This + * adapter replaces those labels with the exact addresses from a complete Miso + * deployment manifest, so applications never have to hand-write a Move target. + */ + +import { normalizeMisoDeployment, type MisoDeployment } from "./deployments.ts"; + +import * as composition from "./contracts/musicos/composition.ts"; +import * as recording from "./contracts/musicos/recording.ts"; +import * as release from "./contracts/musicos/release.ts"; +import * as track from "./contracts/musicos/track.ts"; + +type BoundMoveFunction = F extends (options: infer Options) => infer Result + ? Options extends { package?: unknown } + ? (options: Omit) => Result + : F + : F; +type BoundModule = { + [Key in Exclude]: BoundMoveFunction; +}; +/** Generated modules mix BCS constants with transaction-builder functions. */ +type CodecModule = { + [Key in keyof M as M[Key] extends (...args: never[]) => unknown ? never : Key]: M[Key]; +}; + +/** BCS-only projection: never leak Move-call builders into the `bcs` namespace. */ +function codecsOnly(mod: M): CodecModule { + return Object.fromEntries( + Object.entries(mod).filter(([, value]) => typeof value !== "function"), + ) as CodecModule; +} + +/** Defaults a generated module's optional `package` field to `packageId`. */ +export function bindModulePackage< + M extends object, + K extends readonly (keyof M)[] = readonly [], +>( + mod: M, + packageId: string, + unavailable: K = [] as unknown as K, +): BoundModule { + const bound: Record = {}; + for (const [key, value] of Object.entries(mod)) { + if ((unavailable as readonly string[]).includes(key)) continue; + bound[key] = + typeof value === "function" + ? (options: { package?: string }) => + (value as (input: unknown) => unknown)({ + ...options, + package: packageId, + }) + : value; + } + return bound as BoundModule; +} + +/** + * Public Move functions that return references. A PTB can borrow internally, + * but a Move-call command result cannot carry a reference to a later command. + * Keep them out of `call`; use object/dynamic-field BCS queries instead. + */ +export const REF_RETURNING_CALLS = { + composition: ["title", "uid", "uidMut"], + recording: ["uid", "uidMut"], + release: ["title", "tracks", "uid", "uidMut"], +} as const; + +/** + * All generated functions and BCS codecs bound to one full, verified deployment. + * + * The `call` namespaces construct only caller-owned PTB commands. They neither + * set gas nor sign/execute. Query dynamic fields directly rather than using a + * Move function whose return type is a reference: PTB command outputs cannot + * carry references. + */ +export class MisoPackageBindings { + readonly deployment: MisoDeployment; + + constructor(deployment: MisoDeployment) { + this.deployment = normalizeMisoDeployment(deployment); + } + + get call() { + const d = this.deployment; + return { + core: { + composition: bindModulePackage(composition, d.musicos, REF_RETURNING_CALLS.composition), + recording: bindModulePackage(recording, d.musicos, REF_RETURNING_CALLS.recording), + release: bindModulePackage(release, d.musicos, REF_RETURNING_CALLS.release), + track: bindModulePackage(track, d.musicos), + }, + }; + } + + /** Generated BCS codecs, grouped with the same ownership boundary as `call`. */ + get bcs() { + return { + core: { + composition: codecsOnly(composition), + recording: codecsOnly(recording), + release: codecsOnly(release), + track: codecsOnly(track), + }, + }; + } +} + +/** Construct complete package bindings from an explicit verified manifest. */ +export function misoPackages(deployment: MisoDeployment): MisoPackageBindings { + return new MisoPackageBindings(deployment); +} diff --git a/packages/protocol/src/parsers.ts b/packages/musicos/src/parsers.ts similarity index 94% rename from packages/protocol/src/parsers.ts rename to packages/musicos/src/parsers.ts index 6cffcfb..4b3f4a5 100644 --- a/packages/protocol/src/parsers.ts +++ b/packages/musicos/src/parsers.ts @@ -5,15 +5,15 @@ // they track the on-chain ABI automatically); these functions parse raw event // bytes and map them to the public camelCase event types. -import { CompositionPublishedEvent as CompositionPublishedEventBcs } from "./contracts/miso/composition.ts"; +import { CompositionPublishedEvent as CompositionPublishedEventBcs } from "./contracts/musicos/composition.ts"; import { CompositionSharesGrantedEvent as CompositionSharesGrantedEventBcs, RecordingPublishedEvent as RecordingPublishedEventBcs, -} from "./contracts/miso/recording.ts"; +} from "./contracts/musicos/recording.ts"; import { ReleasePublishedEvent as ReleasePublishedEventBcs, ReleaseRegistryCreatedEvent as ReleaseRegistryCreatedEventBcs, -} from "./contracts/miso/release.ts"; +} from "./contracts/musicos/release.ts"; import type { CompositionPublishedEvent, CompositionSharesGrantedEvent, diff --git a/packages/protocol/src/queries.ts b/packages/musicos/src/queries.ts similarity index 94% rename from packages/protocol/src/queries.ts rename to packages/musicos/src/queries.ts index 5f59102..2522313 100644 --- a/packages/protocol/src/queries.ts +++ b/packages/musicos/src/queries.ts @@ -22,15 +22,12 @@ import type { SuiGraphQLClient } from "@mysten/sui/graphql"; import { graphql } from "@mysten/sui/graphql/schema"; import { deriveObjectID, normalizeSuiAddress } from "@mysten/sui/utils"; -import { Composition as CompositionBcs } from "./contracts/miso/composition.ts"; -import { Recording as RecordingBcs } from "./contracts/miso/recording.ts"; +import { Composition as CompositionBcs } from "./contracts/musicos/composition.ts"; +import { Recording as RecordingBcs } from "./contracts/musicos/recording.ts"; import { Release as ReleaseBcs, ReleaseRegistry as ReleaseRegistryBcs, -} from "./contracts/miso/release.ts"; -import { RoyaltyPool as RoyaltyPoolBcs } from "./contracts/royalty_pool/pool.ts"; -import { Stake as RoyaltyStakeBcs } from "./contracts/royalty_pool/stake.ts"; -import { RoutedStake as RoutedStakeBcs } from "./contracts/routed_stake/routed_stake.ts"; +} from "./contracts/musicos/release.ts"; import { mapBps, mapComposition, @@ -273,57 +270,6 @@ export async function getReleaseRegistryById( return getObjectByBcs(client, registryId, ReleaseRegistryBcs); } -/** Parse a royalty pool by object ID. Phantom type arguments do not affect BCS. */ -export async function getRoyaltyPoolById( - client: ClientWithCoreApi, - poolId: string, -) { - return getObjectByBcs(client, poolId, RoyaltyPoolBcs); -} - -/** Parse an owned `Stake` by object ID. */ -export async function getRoyaltyStakeById( - client: ClientWithCoreApi, - stakeId: string, -) { - return getObjectByBcs(client, stakeId, RoyaltyStakeBcs); -} - -/** Parse a shared routed stake by object ID. */ -export async function getRoutedStakeById( - client: ClientWithCoreApi, - routedStakeId: string, -) { - return getObjectByBcs(client, routedStakeId, RoutedStakeBcs); -} - -/** Deterministically derive the royalty-pool ID for a parent and type pair. */ -export function deriveRoyaltyPoolId( - parentId: string, - shareType: string, - currencyType: string, - royaltyPoolPackageId: string, -): string { - return deriveObjectID( - parentId, - `${royaltyPoolPackageId}::pool::RoyaltyPoolKey<${shareType}, ${currencyType}>`, - UNIT_STRUCT_KEY_BYTES, - ); -} - -/** Deterministically derive the routed-stake ID for a parent and stake share. */ -export function deriveRoutedStakeId( - parentId: string, - stakeShareType: string, - routedStakePackageId: string, -): string { - return deriveObjectID( - parentId, - `${routedStakePackageId}::routed_stake::RoutedStakeKey<${stakeShareType}>`, - UNIT_STRUCT_KEY_BYTES, - ); -} - // ============================================================================ // GraphQL discovery queries // ============================================================================ diff --git a/packages/protocol/src/transactions.ts b/packages/musicos/src/transactions.ts similarity index 75% rename from packages/protocol/src/transactions.ts rename to packages/musicos/src/transactions.ts index 75d2fe8..336c3c6 100644 --- a/packages/protocol/src/transactions.ts +++ b/packages/musicos/src/transactions.ts @@ -31,18 +31,14 @@ import { Transaction, - type TransactionArgument, type TransactionObjectArgument, } from "@mysten/sui/transactions"; -import * as composition from "./contracts/miso/composition.ts"; -import * as recording from "./contracts/miso/recording.ts"; -import * as release from "./contracts/miso/release.ts"; +import * as composition from "./contracts/musicos/composition.ts"; +import * as recording from "./contracts/musicos/recording.ts"; +import * as release from "./contracts/musicos/release.ts"; import { asU256, type UnsignedInput } from "./numeric.ts"; -import * as track from "./contracts/miso/track.ts"; -import * as royaltyPool from "./contracts/royalty_pool/pool.ts"; -import * as royaltyStake from "./contracts/royalty_pool/stake.ts"; -import * as routedStake from "./contracts/routed_stake/routed_stake.ts"; +import * as track from "./contracts/musicos/track.ts"; /** A thunk that adds commands to a transaction. May be async (resolves at build time). */ export type TxThunk = (tx: Transaction) => void | Promise; @@ -307,124 +303,3 @@ export function createRelease( ); return { release: result[0]!, adminCap: result[1]! }; } - -// ============================================================================ -// Generic royalty primitives -// ============================================================================ - -/** Create an owned `Stake` from a by-value `Balance`. */ -export function createRoyaltyStake( - tx: Transaction, - params: { - balance: TransactionArgument; - shareType: string; - royaltyPoolPackageId: string; - }, -): TransactionObjectArgument { - return tx.add( - royaltyStake._new({ - package: params.royaltyPoolPackageId, - typeArguments: [params.shareType], - arguments: [params.balance], - }), - ); -} - -/** Destroy an unregistered stake and return its principal `Balance`. */ -export function destroyRoyaltyStake( - tx: Transaction, - params: { - stake: TransactionObjectArgument; - shareType: string; - royaltyPoolPackageId: string; - }, -): TransactionObjectArgument { - return tx.add( - royaltyStake.destroy({ - package: params.royaltyPoolPackageId, - typeArguments: [params.shareType], - arguments: [params.stake], - }), - ); -} - -/** Register an owned stake against a shared royalty pool. */ -export function registerRoyaltyStake( - tx: Transaction, - params: { - poolId: string; - stakeId?: string; - /** A `Stake` created earlier in this same PTB. */ - stake?: TransactionObjectArgument; - shareType: string; - currencyType: string; - royaltyPoolPackageId: string; - }, -): void { - if (!params.stake && !params.stakeId) { - throw new Error("registerRoyaltyStake: stakeId or stake required"); - } - tx.add( - royaltyPool.registerStake({ - package: params.royaltyPoolPackageId, - typeArguments: [params.shareType, params.currencyType], - arguments: [tx.object(params.poolId), params.stake ?? tx.object(params.stakeId!)], - }), - ); -} - -/** Claim rewards. The returned `Balance` must be consumed in this PTB. */ -export function claimRoyaltyRewards( - tx: Transaction, - params: { - poolId: string; - stakeId: string; - shareType: string; - currencyType: string; - royaltyPoolPackageId: string; - }, -): TransactionObjectArgument { - return tx.add( - royaltyPool.claimRewards({ - package: params.royaltyPoolPackageId, - typeArguments: [params.shareType, params.currencyType], - arguments: [tx.object(params.poolId), tx.object(params.stakeId)], - }), - ); -} - -/** - * Permissionlessly route rewards from a shared routed stake into its parent - * pool. Pool/routed-stake creation and lifecycle operations take `&mut UID` and - * belong in the parent authority module, not in a client-side PTB helper. - */ -export function sweepRoutedStake( - tx: Transaction, - params: { - routedStakeId: string; - stakePoolId: string; - routedPoolId: string; - parentId: string; - stakeShareType: string; - poolShareType: string; - currencyType: string; - routedStakePackageId: string; - }, -): void { - tx.add( - routedStake.sweep({ - package: params.routedStakePackageId, - typeArguments: [ - params.stakeShareType, - params.poolShareType, - params.currencyType, - ], - arguments: [ - tx.object(params.routedStakeId), - tx.object(params.stakePoolId), - tx.object(params.routedPoolId), - tx.pure.id(params.parentId), - ], - }), - ); -} diff --git a/packages/protocol/src/types.ts b/packages/musicos/src/types.ts similarity index 100% rename from packages/protocol/src/types.ts rename to packages/musicos/src/types.ts diff --git a/packages/protocol/src/view.ts b/packages/musicos/src/view.ts similarity index 97% rename from packages/protocol/src/view.ts rename to packages/musicos/src/view.ts index fd3f3dd..fec288b 100644 --- a/packages/protocol/src/view.ts +++ b/packages/musicos/src/view.ts @@ -9,7 +9,7 @@ import { Transaction } from "@mysten/sui/transactions"; import { bcs } from "@mysten/sui/bcs"; import type { ClientWithCoreApi } from "@mysten/sui/client"; -import * as release from "./contracts/miso/release.ts"; +import * as release from "./contracts/musicos/release.ts"; import { asU256, asU64, type UnsignedInput } from "./numeric.ts"; export interface DeriveTargetReleaseIdParams { diff --git a/packages/protocol/tests/batching.test.ts b/packages/musicos/tests/batching.test.ts similarity index 96% rename from packages/protocol/tests/batching.test.ts rename to packages/musicos/tests/batching.test.ts index 954a58e..dd95e2a 100644 --- a/packages/protocol/tests/batching.test.ts +++ b/packages/musicos/tests/batching.test.ts @@ -9,9 +9,9 @@ import { getWorkAddressesByShareTypes, getWorksByIds, } from "../src/queries.ts"; -import { Composition } from "../src/contracts/miso/composition.ts"; -import { Recording } from "../src/contracts/miso/recording.ts"; -import { Release } from "../src/contracts/miso/release.ts"; +import { Composition } from "../src/contracts/musicos/composition.ts"; +import { Recording } from "../src/contracts/musicos/recording.ts"; +import { Release } from "../src/contracts/musicos/release.ts"; const id = (digit: string) => `0x${digit.repeat(64)}`; diff --git a/packages/protocol/tests/client.test.ts b/packages/musicos/tests/client.test.ts similarity index 52% rename from packages/protocol/tests/client.test.ts rename to packages/musicos/tests/client.test.ts index 589898e..eb0b135 100644 --- a/packages/protocol/tests/client.test.ts +++ b/packages/musicos/tests/client.test.ts @@ -19,44 +19,10 @@ const MISO = "0x" + "cd".repeat(32); const SHARE = "0x" + "ab".repeat(32) + "::share::Share"; const A = "0x" + "11".repeat(32); -const FULL_DEPLOYMENT = Object.fromEntries( - MISO_PACKAGE_NAMES.map((name, index) => [ - name, - name === "miso" - ? MISO - : `0x${(index + 1).toString(16).padStart(64, "0")}`, - ]), -) as MisoDeployment; +const FULL_DEPLOYMENT = { musicos: MISO } as const satisfies MisoDeployment; const VERIFIED_TESTNET_DEPLOYMENT = { - miso: "0x95bb43d650fe582caba27d4ead2c3f9939c38125943f8716c5baf5af528bc671", - compositionCredits: "0x54cac9dde365a08eff983af16d74d798e6c0a14264bf6cc8a24790708498e37b", - recordingAdvisory: "0x48a16738e6af6548e50d8f76dce9b9de247f365ef2a07c171f732bdc9c88b235", - recordingCredits: "0x0ca2c727c3eb8d3f889d9b29ce1118bc60518c6990e26e9ab278a7ffe635bf1e", - recordingLanguage: "0x677cef9c766b17f87fdf9d6623f1adca0d2e244ee228c86d82c23599ad9a828a", - recordingMasterReference: "0x2b1ca393e8d41eba5b0d3cabf6df1e207a349bba0486ca9ea3a31734d3445971", - releaseCoverArt: "0xb289dfb58ccf2af3722f7bc93b072426a791d05a401115f2901d130f12d8e4b2", - releaseCredits: "0x3a05d0c863ca0b5210f90f69cf87da59791b127c9eaaa10633961aee229a3ed5", - releaseDescription: "0x60a8bc11b7d41d594a2c54c8dee4534dcf94d3be20054100c7774026a543ec6f", - releaseDspLink: "0xbeffd79f656ce89d3595c9fb36dac42a169b72a5e7090504c48ba6c2425961a6", - releaseGenre: "0x111dd8bff35a1779067d7c75f8a514691342f91f67d2fcdae392768ba6db26f2", - releaseKind: "0x90bf2633b45699d424da616869ecb3d824a88d78d8f8d2ccf5b711b18a734bf1", - royaltyPool: "0xa49e297e4ed8c29ea9bd5941b3ac7d41327f97c85fc35e11bda466f09fce1943", - routedStake: "0xc920af18421cd11c315fa8d0cdd56056854cf453f1ced1fa1b32d555bca2955a", - misoParty: "0xc9fc5d918da992b7c6499880fc509635def384d8529948b28f58494daedf8ec8", - partyCta: "0x25bbdb3dc3fa9482be90294a2b3e328ab4fc2658f1258f1cdf4fe73b13a8d7c5", - partyGenre: "0xa02cffae5be2820e8cda8ecd4f10ae4149a40800aee1a78c74736a409aa515b9", - partyMedia: "0xcafdbe4be3dd6bfade79e546f7faf08972ed864fdb1d49157ad420d71f927783", - partyMusic: "0x76405c486eccde051a621d91a9fd05a911fdc88a3282ef58a3c58eeaad200008", - partyPlatformLink: "0x1d555a8ed265f4a76119c5c01be3646807b91b2cec588e33db016671bdcc259c", - partyProLink: "0xbe6f66e75c6f41afcbf88b166e11cf0a849337379e2f12673598ff976c9f327b", - partyProfile: "0xa5b333defcf07ad3c59feac4b831f4d6775776b1734c9fb1145f590c3028e789", - partyRoles: "0x7a3ab86bfe19b3d291a18d7c0582a1248cf9d74f981f901e9dc75dce0dc3ced9", - partySocial: "0xaaa1af3282370b3f1cd376b2e6f1e0c3b457498eae8ba6150b86e7fe56285392", - partyTags: "0x68a90de97c82f90f22eaa8f2e2b2bfcc3bcfef4847d62e8540474a494f1198d9", - countryCode: "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", - languageCode: "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", - genre: "0x6ae4aefdd9d147db6f04a14e4f6149944c485fc7d87d8201dcdb0dee15f0f296", + musicos: "0x2a4f8d83bffa73a13c9cfefdc4376256d4cef4330d5f60233c082cfab9a34e68", } as const satisfies MisoDeployment; function coreClient() { @@ -96,51 +62,6 @@ test("client.call binds an explicit core package, never the codegen source label expect(moveCalls(tx).find((call) => call.module === "composition")?.package).toBe(MISO); }); -test("a complete manifest binds extension calls to their own fresh package IDs", () => { - const client = new SuiGrpcClient({ - network: "testnet", - baseUrl: "https://fullnode.testnet.sui.io:443", - }).$extend(miso({ deployment: FULL_DEPLOYMENT })); - const tx = new Transaction(); - - client.miso.packages.call.extensions.recordingAdvisory.unsetRating({ - typeArguments: [SHARE, SHARE], - arguments: [tx.object(A), tx.object(A)], - })(tx); - client.miso.packages.call.primitives.royaltyPool.pool.sweepAndDeposit({ - typeArguments: [SHARE, "0x2::sui::SUI"], - arguments: [tx.object(A)], - })(tx); - - const call = moveCalls(tx).find((item) => item.module === "recording_advisory"); - expect(call).toMatchObject({ - package: FULL_DEPLOYMENT.recordingAdvisory, - function: "unset_rating", - }); - expect(moveCalls(tx).find((item) => item.function === "sweep_and_deposit")).toMatchObject({ - package: FULL_DEPLOYMENT.royaltyPool, - module: "pool", - }); -}); - -test("Party APIs live at client.miso.party and bind the consolidated manifest", () => { - const client = new SuiGrpcClient({ - network: "testnet", - baseUrl: "https://fullnode.testnet.sui.io:443", - }).$extend(miso({ deployment: FULL_DEPLOYMENT })); - const tx = new Transaction(); - - tx.add(client.miso.party.tx.setName({ partyId: A, capId: A, name: "Miso" })); - - expect(moveCalls(tx).find((item) => item.module === "party")).toMatchObject({ - package: FULL_DEPLOYMENT.misoParty, - function: "set_name", - }); - expect(client.miso.party.genrePackageId).toBe(FULL_DEPLOYMENT.genre); - expect("uid" in client.miso.party.call.party).toBeFalse(); - expect("profile" in client.miso.party.call.profile).toBeFalse(); -}); - test("package-bound calls omit reference-returning Move views", () => { const client = new SuiGrpcClient({ network: "testnet", @@ -148,12 +69,10 @@ test("package-bound calls omit reference-returning Move views", () => { }).$extend(miso({ deployment: FULL_DEPLOYMENT })); expect("title" in client.miso.packages.call.core.composition).toBeFalse(); - expect("releaseLink" in client.miso.packages.call.extensions.releaseDspLink).toBeTrue(); - expect("credits" in client.miso.packages.call.extensions.compositionCredits.compositionCredits).toBeFalse(); - expect("register" in client.miso.packages.call.primitives.routedStake).toBeFalse(); - expect("unregister" in client.miso.packages.call.primitives.routedStake).toBeFalse(); - expect("unstake" in client.miso.packages.call.primitives.routedStake).toBeFalse(); - expect("restake" in client.miso.packages.call.primitives.routedStake).toBeFalse(); + expect("uid" in client.miso.packages.call.core.composition).toBeFalse(); + expect("uidMut" in client.miso.packages.call.core.recording).toBeFalse(); + expect("title" in client.miso.packages.call.core.release).toBeFalse(); + expect("tracks" in client.miso.packages.call.core.release).toBeFalse(); }); test("package BCS projection exposes codecs only, not transaction builders", () => { @@ -176,12 +95,11 @@ test("Testnet defaults to the bundled manifest while partial and unbundled deplo network: "testnet", baseUrl: "https://fullnode.testnet.sui.io:443", }).$extend(miso()); - expect(client.miso.deployment.packageId).toBe(MISO_DEPLOYMENTS.testnet.miso); + expect(client.miso.deployment.packageId).toBe(MISO_DEPLOYMENTS.testnet.musicos); expect(client.miso.packages.deployment).toEqual(MISO_DEPLOYMENTS.testnet); - expect(client.miso.party.genrePackageId).toBe(MISO_DEPLOYMENTS.testnet.genre); expect(getMisoDeployment("testnet")).toEqual(MISO_DEPLOYMENTS.testnet); expect(getMisoProtocolDeployment("testnet")).toEqual({ - packageId: MISO_DEPLOYMENTS.testnet.miso, + packageId: MISO_DEPLOYMENTS.testnet.musicos, }); expect(() => getMisoProtocolDeployment("mainnet")).toThrow( /no verified Miso deployment/, @@ -193,17 +111,16 @@ test("Testnet defaults to the bundled manifest while partial and unbundled deplo new SuiGrpcClient({ network: "testnet", baseUrl: "https://fullnode.testnet.sui.io:443", - }).$extend(miso({ deployment: { miso: MISO } as MisoDeployment })), + }).$extend(miso({ deployment: { musicos: MISO, unexpected: MISO } as unknown as MisoDeployment })), ).toThrow(/exactly these package IDs/); expect(() => coreClient().miso.packages).toThrow(/complete MisoDeployment/); - expect(() => coreClient().miso.party).toThrow(/Party APIs require a complete MisoDeployment/); }); test("Testnet deployment export matches the verified immutable admin-cli publish record", () => { expect(MISO_DEPLOYMENTS.testnet).toEqual(VERIFIED_TESTNET_DEPLOYMENT); expect(getMisoDeployment("testnet")).toEqual(VERIFIED_TESTNET_DEPLOYMENT); expect(getMisoProtocolDeployment("testnet")).toEqual({ - packageId: VERIFIED_TESTNET_DEPLOYMENT.miso, + packageId: VERIFIED_TESTNET_DEPLOYMENT.musicos, }); }); @@ -211,41 +128,38 @@ test("deployment manifests are exact, normalized, and snapshotted", () => { expect(() => assertMisoDeployment({ ...FULL_DEPLOYMENT, unexpected: MISO })).toThrow( /exactly these package IDs/, ); - expect(() => assertMisoDeployment({ ...FULL_DEPLOYMENT, miso: "0xAB" })).toThrow( + expect(() => assertMisoDeployment({ ...FULL_DEPLOYMENT, musicos: "0xAB" })).toThrow( /normalized|valid Sui address/, ); - expect(() => - assertMisoDeployment({ ...FULL_DEPLOYMENT, recordingAdvisory: MISO }), - ).toThrow(/duplicates another package/); - const mutable = { ...FULL_DEPLOYMENT }; + const mutable = { ...FULL_DEPLOYMENT } as { musicos: string }; const client = new SuiGrpcClient({ network: "testnet", baseUrl: "https://fullnode.testnet.sui.io:443" }) .$extend(miso({ deployment: mutable })); - mutable.miso = "0x" + "ef".repeat(32); + mutable.musicos = "0x" + "ef".repeat(32); const tx = new Transaction(); client.miso.call.release.releaseRegistryId({ arguments: [tx.object(A)] })(tx); expect(moveCalls(tx)[0]?.package).toBe(MISO); }); test("bundled deployment defaults are runtime immutable", () => { - const bundledMiso = VERIFIED_TESTNET_DEPLOYMENT.miso; + const bundledMiso = VERIFIED_TESTNET_DEPLOYMENT.musicos; const alternate = "0x" + "ef".repeat(32); const mutableRoot = MISO_DEPLOYMENTS as unknown as { testnet: MisoDeployment; }; const mutableTestnet = MISO_DEPLOYMENTS.testnet as unknown as { - miso: string; + musicos: string; }; expect(Object.isFrozen(MISO_DEPLOYMENTS)).toBeTrue(); expect(Object.isFrozen(MISO_DEPLOYMENTS.testnet)).toBeTrue(); expect(() => { - mutableRoot.testnet = { ...FULL_DEPLOYMENT, miso: alternate }; + mutableRoot.testnet = { ...FULL_DEPLOYMENT, musicos: alternate }; }).toThrow(TypeError); expect(() => { - mutableTestnet.miso = alternate; + mutableTestnet.musicos = alternate; }).toThrow(TypeError); - expect(MISO_DEPLOYMENTS.testnet.miso).toBe(bundledMiso); + expect(MISO_DEPLOYMENTS.testnet.musicos).toBe(bundledMiso); expect(getMisoDeployment("testnet")).toEqual(MISO_DEPLOYMENTS.testnet); expect(getMisoProtocolDeployment("testnet")).toEqual({ packageId: bundledMiso, @@ -291,15 +205,6 @@ test("public contracts retain BCS but omit unsafe reference-returning calls", () expect("Composition" in contracts.composition).toBeTrue(); expect("uid" in contracts.composition).toBeFalse(); expect("uidMut" in contracts.release).toBeFalse(); - expect("credits" in contracts.compositionCredits).toBeFalse(); - expect("_new" in contracts.royaltyPool).toBeFalse(); - expect("_new" in contracts.routedStake).toBeFalse(); - expect("register" in contracts.routedStake).toBeFalse(); - expect("unregister" in contracts.routedStake).toBeFalse(); - expect("unstake" in contracts.routedStake).toBeFalse(); - expect("restake" in contracts.routedStake).toBeFalse(); - if (false) { - // @ts-expect-error UID-gated routed-stake transitions are not client calls. - contracts.routedStake.register; - } + expect("title" in contracts.release).toBeFalse(); + expect("tracks" in contracts.release).toBeFalse(); }); diff --git a/packages/musicos/tests/codegen-output.test.ts b/packages/musicos/tests/codegen-output.test.ts new file mode 100644 index 0000000..f5fa5c4 --- /dev/null +++ b/packages/musicos/tests/codegen-output.test.ts @@ -0,0 +1,60 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { expect, test } from "bun:test"; +import { existsSync, mkdtempSync, mkdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import config, { generatedDirectoryName } from "../../../sui-codegen.config.ts"; +import { + generatedContractDirectories, + pruneRemovedPackageDirectories, + unexpectedPackageDirectories, +} from "../scripts/codegen-output.ts"; + +test("the musicos tree holds exactly the object model plus codegen utils", () => { + expect([...generatedContractDirectories(config.packages, "musicos")].sort()).toEqual([ + "musicos", + "utils", + ]); +}); + +test("the partyos tree holds exactly the party object model plus codegen utils", () => { + expect([...generatedContractDirectories(config.packages, "partyos")].sort()).toEqual([ + "partyos", + "utils", + ]); +}); + +test("every configured package lands in exactly one tree", () => { + const trees = (Object.keys(config.outputs) as (keyof typeof config.outputs)[]).map((tree) => + generatedContractDirectories(config.packages, tree), + ); + for (const entry of config.packages) { + const name = generatedDirectoryName(entry); + expect(trees.filter((tree) => tree.has(name)).length).toBe(1); + } + // Every package directory plus one `utils` per tree. + expect(trees.reduce((total, tree) => total + tree.size, 0)).toBe(config.packages.length + trees.length); +}); + +test("codegen output is closed-world: stale directories are pruned and drift is reported", () => { + const fixture = mkdtempSync(join(tmpdir(), "miso-sdk-codegen-output-")); + try { + mkdirSync(join(fixture, "musicos")); + mkdirSync(join(fixture, "miso")); + mkdirSync(join(fixture, "unrelated-directory")); + const expected = generatedContractDirectories(config.packages, "musicos"); + + expect(unexpectedPackageDirectories(fixture, expected)).toEqual(["miso", "unrelated-directory"]); + expect(pruneRemovedPackageDirectories(fixture, expected).sort()).toEqual([ + "miso", + "unrelated-directory", + ]); + expect(existsSync(join(fixture, "musicos"))).toBeTrue(); + expect(existsSync(join(fixture, "miso"))).toBeFalse(); + expect(unexpectedPackageDirectories(fixture, expected)).toEqual([]); + } finally { + rmSync(fixture, { recursive: true, force: true }); + } +}); diff --git a/packages/musicos/tests/events.test.ts b/packages/musicos/tests/events.test.ts new file mode 100644 index 0000000..ca27f4d --- /dev/null +++ b/packages/musicos/tests/events.test.ts @@ -0,0 +1,21 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { expect, test } from "bun:test"; +import { eventParsers } from "../src/events.ts"; +import { ReleaseRegistryCreatedEvent } from "../src/contracts/musicos/release.ts"; + +const A = "0x" + "12".repeat(32); +const B = "0x" + "34".repeat(32); + +test("core-registry event decoder round-trips the current ABI", () => { + const registry = ReleaseRegistryCreatedEvent.serialize({ + registry_id: A, + created_by: B, + }).toBytes(); + + expect(eventParsers.core.releaseRegistryCreated(registry)).toEqual({ + registry_id: A, + created_by: B, + }); +}); diff --git a/packages/protocol/tests/mappers.test.ts b/packages/musicos/tests/mappers.test.ts similarity index 92% rename from packages/protocol/tests/mappers.test.ts rename to packages/musicos/tests/mappers.test.ts index ffe3272..4a63d99 100644 --- a/packages/protocol/tests/mappers.test.ts +++ b/packages/musicos/tests/mappers.test.ts @@ -7,9 +7,9 @@ import { test, expect } from "bun:test"; -import { Composition } from "../src/contracts/miso/composition.ts"; -import { Recording } from "../src/contracts/miso/recording.ts"; -import { Release } from "../src/contracts/miso/release.ts"; +import { Composition } from "../src/contracts/musicos/composition.ts"; +import { Recording } from "../src/contracts/musicos/recording.ts"; +import { Release } from "../src/contracts/musicos/release.ts"; import { mapComposition, mapBps, mapRecording, mapRelease } from "../src/internal.ts"; const ADDR = "0x" + "ab".repeat(32); diff --git a/packages/protocol/tests/parsers.test.ts b/packages/musicos/tests/parsers.test.ts similarity index 91% rename from packages/protocol/tests/parsers.test.ts rename to packages/musicos/tests/parsers.test.ts index 522858e..8dfd09a 100644 --- a/packages/protocol/tests/parsers.test.ts +++ b/packages/musicos/tests/parsers.test.ts @@ -8,15 +8,15 @@ import { test, expect } from "bun:test"; -import { CompositionPublishedEvent } from "../src/contracts/miso/composition.ts"; +import { CompositionPublishedEvent } from "../src/contracts/musicos/composition.ts"; import { CompositionSharesGrantedEvent, RecordingPublishedEvent, -} from "../src/contracts/miso/recording.ts"; +} from "../src/contracts/musicos/recording.ts"; import { ReleasePublishedEvent, ReleaseRegistryCreatedEvent, -} from "../src/contracts/miso/release.ts"; +} from "../src/contracts/musicos/release.ts"; import * as parse from "../src/parsers.ts"; const A1 = "0x" + "11".repeat(32); diff --git a/packages/protocol/tests/queries.test.ts b/packages/musicos/tests/queries.test.ts similarity index 87% rename from packages/protocol/tests/queries.test.ts rename to packages/musicos/tests/queries.test.ts index 315a93b..9c37e71 100644 --- a/packages/protocol/tests/queries.test.ts +++ b/packages/musicos/tests/queries.test.ts @@ -7,7 +7,6 @@ import { test, expect } from "bun:test"; import type { SuiGraphQLClient } from "@mysten/sui/graphql"; import { - deriveRoutedStakeId, getExtensionField, getReleaseDspLink, getReleaseRegistryById, @@ -18,9 +17,9 @@ import { getRecordingByShareType, isNotFound, } from "../src/queries.ts"; -import { DescriptionSetEvent } from "../src/contracts/release_description/release_description.ts"; -import { ReleaseRegistry } from "../src/contracts/miso/release.ts"; -import { Recording } from "../src/contracts/miso/recording.ts"; +import { CompositionPublishedEvent } from "../src/contracts/musicos/composition.ts"; +import { ReleaseRegistry } from "../src/contracts/musicos/release.ts"; +import { Recording } from "../src/contracts/musicos/recording.ts"; const PKG = "0x" + "cd".repeat(32); @@ -162,9 +161,8 @@ test("getRecordingByShareType follows GraphQL pages before loading the matching test("getExtensionField reads a fieldless ExtensionKey and returns null only for absence", async () => { const calls: unknown[] = []; - const value = DescriptionSetEvent.serialize({ - release_id: PKG, - description: "description", + const value = CompositionPublishedEvent.serialize({ + composition_id: PKG, }).toBytes(); const client = { core: { @@ -178,15 +176,15 @@ test("getExtensionField reads a fieldless ExtensionKey and returns null only for await expect( getExtensionField(client, PKG, { packageId: PKG, - module: "release_description", - codec: DescriptionSetEvent, + module: "some_extension", + codec: CompositionPublishedEvent, }), - ).resolves.toEqual({ release_id: PKG, description: "description" }); + ).resolves.toEqual({ composition_id: PKG }); expect(calls).toEqual([ { parentId: PKG, name: { - type: `${PKG}::release_description::ExtensionKey`, + type: `${PKG}::some_extension::ExtensionKey`, bcs: new Uint8Array([0]), }, }, @@ -204,17 +202,16 @@ test("getExtensionField reads a fieldless ExtensionKey and returns null only for await expect( getExtensionField(missing, PKG, { packageId: PKG, - module: "release_description", - codec: DescriptionSetEvent, + module: "some_extension", + codec: CompositionPublishedEvent, }), ).resolves.toBeNull(); }); test("DSP fields use their platform-keyed dynamic-field names", async () => { const calls: unknown[] = []; - const value = DescriptionSetEvent.serialize({ - release_id: PKG, - description: "link payload", + const value = CompositionPublishedEvent.serialize({ + composition_id: PKG, }).toBytes(); const client = { core: { @@ -228,12 +225,12 @@ test("DSP fields use their platform-keyed dynamic-field names", async () => { await getReleaseDspLink(client, PKG, { packageId: PKG, platform: 7, - codec: DescriptionSetEvent, + codec: CompositionPublishedEvent, }); await getTrackDspLinks(client, PKG, { packageId: PKG, platform: 7, - codec: DescriptionSetEvent, + codec: CompositionPublishedEvent, }); expect(calls).toEqual([ @@ -256,12 +253,12 @@ test("DSP fields use their platform-keyed dynamic-field names", async () => { getReleaseDspLink(client, PKG, { packageId: PKG, platform: 256, - codec: DescriptionSetEvent, + codec: CompositionPublishedEvent, }), ).rejects.toThrow(/u8/); }); -test("core registry parsing and derived routed-stake helper are deterministic", async () => { +test("core registry parsing is deterministic", async () => { const client = { core: { getObject: async () => ({ @@ -270,10 +267,4 @@ test("core registry parsing and derived routed-stake helper are deterministic", }, } as unknown as ClientWithCoreApi; await expect(getReleaseRegistryById(client, PKG)).resolves.toEqual({ id: PKG }); - - const first = deriveRoutedStakeId(PKG, `${PKG}::share::S`, `${PKG}::routed_stake`); - const second = deriveRoutedStakeId(PKG, `${PKG}::share::S`, `${PKG}::routed_stake`); - const differentShare = deriveRoutedStakeId(PKG, `${PKG}::share::T`, `${PKG}::routed_stake`); - expect(first).toBe(second); - expect(first).not.toBe(differentShare); }); diff --git a/packages/protocol/tests/transactions.test.ts b/packages/musicos/tests/transactions.test.ts similarity index 79% rename from packages/protocol/tests/transactions.test.ts rename to packages/musicos/tests/transactions.test.ts index e0edd3c..8da5745 100644 --- a/packages/protocol/tests/transactions.test.ts +++ b/packages/musicos/tests/transactions.test.ts @@ -9,15 +9,11 @@ import { test, expect } from "bun:test"; import { Transaction } from "@mysten/sui/transactions"; import { - claimRoyaltyRewards, createComposition, createRelease, - createRoyaltyStake, createTrack, createRecording, publishComposition, - registerRoyaltyStake, - sweepRoutedStake, } from "../src/transactions.ts"; const PKG = "0x" + "cd".repeat(32); @@ -267,83 +263,3 @@ test("publish finalizers consume their PTB-local values without setting gas or s expect(call.argCount).toBe(3); // Codegen adds the framework Clock input. }); -test("royalty helpers preserve by-value rewards for the caller to consume", () => { - const tx = new Transaction(); - const composition = createComposition(tx, { - title: "T", - royaltyRateBps: 1000, - shareType: CS, - shareCurrencyId: A, - shareTreasuryCapId: A, - misoPackageId: PKG, - }); - const stake = createRoyaltyStake(tx, { - balance: composition.balance, - shareType: CS, - royaltyPoolPackageId: PKG, - }); - const reward = claimRoyaltyRewards(tx, { - poolId: A, - stakeId: A, - shareType: CS, - currencyType: RS, - royaltyPoolPackageId: PKG, - }); - - expect(moveCalls(tx).map((call) => `${call.module}::${call.function}`)).toEqual([ - "composition::new", - "stake::new", - "pool::claim_rewards", - ]); - expect(stake).toEqual({ $kind: "Result", Result: 1 }); - expect(reward).toEqual({ $kind: "Result", Result: 2 }); -}); - -test("registerRoyaltyStake accepts a PTB-local Stake", () => { - const tx = new Transaction(); - const stake = createRoyaltyStake(tx, { - balance: tx.object(A), - shareType: CS, - royaltyPoolPackageId: PKG, - }); - registerRoyaltyStake(tx, { - poolId: A, - stake, - shareType: CS, - currencyType: RS, - royaltyPoolPackageId: PKG, - }); - const data = tx.getData() as { - commands: { - $kind: string; - MoveCall?: { module: string; function: string; arguments: unknown[] }; - }[]; - }; - const call = data.commands.at(-1)!.MoveCall!; - expect(call).toMatchObject({ module: "pool", function: "register_stake" }); - // `stake::new` returns exactly one `Stake`; its Result is borrowed by the - // registration call in the same PTB, leaving no address round-trip. - expect(call.arguments[1]).toEqual({ $kind: "Result", Result: 0 }); -}); - -test("sweepRoutedStake appends the one permissionless routed-stake command", () => { - const tx = new Transaction(); - sweepRoutedStake(tx, { - routedStakeId: A, - stakePoolId: A, - routedPoolId: A, - parentId: A, - stakeShareType: CS, - poolShareType: RS, - currencyType: `${PKG}::currency::USD`, - routedStakePackageId: PKG, - }); - - expect(kinds(tx)).toEqual(["MoveCall"]); - expect(moveCalls(tx)[0]).toMatchObject({ - module: "routed_stake", - function: "sweep", - typeArguments: [CS, RS, `${PKG}::currency::USD`], - argCount: 4, - }); -}); diff --git a/packages/protocol/tests/view.test.ts b/packages/musicos/tests/view.test.ts similarity index 100% rename from packages/protocol/tests/view.test.ts rename to packages/musicos/tests/view.test.ts diff --git a/packages/protocol/tsconfig.build.json b/packages/musicos/tsconfig.build.json similarity index 100% rename from packages/protocol/tsconfig.build.json rename to packages/musicos/tsconfig.build.json diff --git a/packages/protocol/tsconfig.json b/packages/musicos/tsconfig.json similarity index 100% rename from packages/protocol/tsconfig.json rename to packages/musicos/tsconfig.json diff --git a/packages/protocol/typedoc.json b/packages/musicos/typedoc.json similarity index 92% rename from packages/protocol/typedoc.json rename to packages/musicos/typedoc.json index 5479daa..9683a49 100644 --- a/packages/protocol/typedoc.json +++ b/packages/musicos/typedoc.json @@ -4,7 +4,7 @@ "src/index.ts" ], "out": "docs", - "name": "@misonetwork/sdk", + "name": "@misofm/musicos", "readme": "README.md", "excludePrivate": true, "excludeProtected": true, diff --git a/packages/partyos/LICENSE b/packages/partyos/LICENSE new file mode 100644 index 0000000..34ce76b --- /dev/null +++ b/packages/partyos/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Miso Labs, Inc. (build@miso.network) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/partyos/README.md b/packages/partyos/README.md new file mode 100644 index 0000000..c12d832 --- /dev/null +++ b/packages/partyos/README.md @@ -0,0 +1,50 @@ +# @misofm/partyos + +Typed bindings, queries, and PTB builders for the PartyOS object model on Sui: the +`Party` object, its `PartyAdminCap`, and consent-based group membership. + +> `@misofm/partyos` is the object model. Everything Miso offers on top of it is platform. + +A Party's profile, media, roles, tags, genres, CTAs, platform links, and wallet are +extensions someone chose to offer, so they ship from +[`@misofm/platform`](https://www.npmjs.com/package/@misofm/platform), which depends on +this package. Which Move package generates into which SDK is decided by +`sui-codegen.config.ts` in the [misofm/sdks](https://github.com/misofm/sdks) repo. + +## Install + +```sh +bun add @misofm/partyos @mysten/sui +``` + +`@mysten/sui` is a peer dependency pinned to one exact version. + +## Use + +```ts +import { SuiGrpcClient } from "@mysten/sui/grpc"; +import { partyos } from "@misofm/partyos"; + +const client = new SuiGrpcClient({ network: "testnet" }).$extend(partyos()); + +const party = await client.partyos.getPartyById("0x..."); +const groups = await client.partyos.getMemberships(party.id); + +const tx = new Transaction(); +await client.partyos.tx.createIndividualParty({ name: "Ada", recipient: "0x..." })(tx); +``` + +Every builder under `tx` appends commands to a caller-owned `Transaction` and never +executes. `call` exposes the generated, package-bound Move calls (minus the +reference-returning views a PTB cannot use), and `bcs` the generated codecs. + +## Deployment manifest + +`PARTYOS_DEPLOYMENTS` bundles the verified testnet deployment: a single key, `partyos`. +Pass `partyos({ deployment })` for another network; a manifest with any other shape is +rejected before a Move target is constructed. + +## Subpaths + +`@misofm/partyos` (everything), `/client`, `/deployments`, `/queries`, `/transactions`, +`/types`, `/contracts` (curated bindings), `/contracts/*` (raw generated modules). diff --git a/packages/partyos/package.json b/packages/partyos/package.json new file mode 100644 index 0000000..0624259 --- /dev/null +++ b/packages/partyos/package.json @@ -0,0 +1,97 @@ +{ + "name": "@misofm/partyos", + "version": "0.2.0", + "packageManager": "bun@1.4.0", + "description": "Typed bindings, queries, and PTB builders for the PartyOS object model (Party identity, admin caps, and group membership) on Sui.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/misofm/sdks.git" + }, + "homepage": "https://github.com/misofm/sdks#readme", + "bugs": { + "url": "https://github.com/misofm/sdks/issues" + }, + "keywords": [ + "sui", + "move", + "miso", + "partyos", + "party", + "identity", + "blockchain", + "sdk" + ], + "publishConfig": { + "access": "public" + }, + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "sideEffects": false, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "bun test", + "docs": "typedoc", + "build": "rm -rf dist && tsc -p tsconfig.build.json", + "prepack": "rm -rf dist && tsc -p tsconfig.build.json", + "prepare": "rm -rf dist && tsc -p tsconfig.build.json --noCheck" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./client": { + "types": "./dist/client.d.ts", + "default": "./dist/client.js" + }, + "./deployments": { + "types": "./dist/deployments.d.ts", + "default": "./dist/deployments.js" + }, + "./queries": { + "types": "./dist/queries.d.ts", + "default": "./dist/queries.js" + }, + "./transactions": { + "types": "./dist/transactions.d.ts", + "default": "./dist/transactions.js" + }, + "./types": { + "types": "./dist/types.d.ts", + "default": "./dist/types.js" + }, + "./contracts": { + "types": "./dist/contracts.d.ts", + "default": "./dist/contracts.js" + }, + "./contracts/*": { + "types": "./dist/contracts/*.d.ts", + "default": "./dist/contracts/*.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], + "dependencies": { + "@mysten/bcs": "^2.1.1" + }, + "devDependencies": { + "@mysten/sui": "2.29.0", + "@types/bun": "^1.4.0", + "typedoc": "^0.28.20", + "typedoc-plugin-markdown": "^4.13.0", + "typescript": "^7.0.2" + }, + "peerDependencies": { + "@mysten/sui": "2.29.0", + "typescript": "^5.7 || ^6 || ^7" + }, + "main": "./dist/index.js" +} diff --git a/packages/partyos/scripts/codegen-output.ts b/packages/partyos/scripts/codegen-output.ts new file mode 100644 index 0000000..6a6fcd5 --- /dev/null +++ b/packages/partyos/scripts/codegen-output.ts @@ -0,0 +1,8 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Codegen tooling now lives at the monorepo root (see `../../../scripts/`), +// unified with the runner that used to live only here. This re-export keeps +// the existing test suite (`tests/codegen-output.test.ts`) pointed at a single +// implementation instead of duplicating it. +export * from "../../../scripts/codegen-output.ts"; diff --git a/packages/partyos/src/client.ts b/packages/partyos/src/client.ts new file mode 100644 index 0000000..b672ae5 --- /dev/null +++ b/packages/partyos/src/client.ts @@ -0,0 +1,165 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Party queries and transaction builders bound to one verified partyos +// deployment. Consumers reach this through `client.partyos` after +// `$extend(partyos())`, or construct `PartyosClient` directly. + +import type { ClientWithCoreApi, SuiClientRegistration } from "@mysten/sui/client"; +import { getPartyDeployment, normalizePartyDeployment, type PartyDeployment } from "./deployments.ts"; +import { PARTY_REF_RETURNING_CALLS } from "./contracts.ts"; +import * as queries from "./queries.ts"; +import * as transactions from "./transactions.ts"; +import type { TxThunk } from "./transactions.ts"; +import type { Party } from "./types.ts"; +import * as partyMod from "./contracts/partyos/party.ts"; + +type BoundMoveFunction = F extends (options: infer Options) => infer Result + ? Options extends { package?: unknown } + ? (options: Omit) => Result + : F + : F; +type BoundModule = { + [Key in Exclude]: BoundMoveFunction; +}; + +/** Defaults generated calls to one package and removes reference-returning calls. */ +export function bindModulePackage( + mod: M, + pkg: string, + unavailable: K = [] as unknown as K, +): BoundModule { + const out: Record = {}; + for (const [key, value] of Object.entries({ ...mod })) { + if ((unavailable as readonly string[]).includes(key)) continue; + out[key] = + typeof value === "function" + ? (options: { package?: string }) => (value as (o: unknown) => unknown)({ package: pkg, ...options }) + : value; + } + return out as BoundModule; +} + +export interface PartyosOptions { + /** Name for the client extension. Defaults to "partyos". */ + name?: Name; + /** Explicit deployment. Omit only when this SDK bundles a verified manifest for the client's network. */ + deployment?: PartyDeployment; +} + +/** + * Creates a PartyOS client extension for use with `$extend()`. + * + * @example + * ```ts + * const client = new SuiGrpcClient({ network: "testnet" }).$extend(partyos()); + * const party = await client.partyos.getPartyById("0x..."); + * ``` + */ +export function partyos( + options: PartyosOptions = {}, +): SuiClientRegistration { + const name = (options.name ?? "partyos") as Name; + return { + name, + register: (client) => + new PartyosClient( + client, + options.deployment ? normalizePartyDeployment(options.deployment) : getPartyDeployment(client.network), + ), + }; +} + +/** Package-bound PartyOS client. */ +export class PartyosClient { + #client: ClientWithCoreApi; + #deployment: PartyDeployment; + + constructor(client: ClientWithCoreApi, deployment: PartyDeployment) { + this.#client = client; + this.#deployment = normalizePartyDeployment(deployment); + } + + /** The exact deployment selected for this client. */ + get deployment(): PartyDeployment { + return this.#deployment; + } + + get #pkg(): string { + return this.#deployment.partyos; + } + + // === Queries === + + async getPartyById(partyId: string): Promise { + return queries.getPartyById(this.#client, partyId, this.#pkg); + } + async getPartiesByIds(partyIds: readonly string[]): Promise>> { + return queries.getPartiesByIds(this.#client, partyIds, this.#pkg); + } + derivePartyAdminCapId(partyId: string): string { + return queries.derivePartyAdminCapId(partyId, this.#pkg); + } + /** Group ids a party belongs to (member-side membership records). */ + async getMemberships(partyId: string): Promise { + return queries.getMemberships(this.#client, partyId); + } + /** Member ids invited to a group but not yet accepted. */ + async getPendingInvites(groupId: string): Promise { + return queries.getPendingInvites(this.#client, groupId); + } + /** Group ids that have invited this party but are awaiting its response. */ + async getPendingMemberships(partyId: string): Promise { + return queries.getPendingMemberships(this.#client, partyId); + } + /** Whether a party is a member of a group. */ + async isMember(memberId: string, groupId: string): Promise { + return queries.isMember(this.#client, memberId, groupId, this.#pkg); + } + + // === Transaction builders (thunks; the package id is bound from the client) === + + get tx() { + const pkg = this.#pkg; + return { + createIndividualParty: (p: Omit): TxThunk => + transactions.createIndividualParty({ ...p, partyPackageId: pkg }), + createGroupParty: (p: Omit): TxThunk => + transactions.createGroupParty({ ...p, partyPackageId: pkg }), + setName: (p: Omit): TxThunk => + transactions.setName({ ...p, partyPackageId: pkg }), + inviteParty: (p: Omit): TxThunk => + transactions.inviteParty({ ...p, partyPackageId: pkg }), + acceptInvite: (p: Omit): TxThunk => + transactions.acceptInvite({ ...p, partyPackageId: pkg }), + declineInvite: (p: Omit): TxThunk => + transactions.declineInvite({ ...p, partyPackageId: pkg }), + revokeInvite: (p: Omit): TxThunk => + transactions.revokeInvite({ ...p, partyPackageId: pkg }), + leaveGroup: (p: Omit): TxThunk => + transactions.leaveGroup({ ...p, partyPackageId: pkg }), + removeMember: (p: Omit): TxThunk => + transactions.removeMember({ ...p, partyPackageId: pkg }), + }; + } + + // === Generated type-safe Move calls (for tx.add) === + + get call() { + return { + party: bindModulePackage(partyMod, this.#pkg, PARTY_REF_RETURNING_CALLS), + }; + } + + // === Generated BCS structs (for parsing object/event content) === + + get bcs() { + return { + Party: partyMod.Party, + PartyAdminCap: partyMod.PartyAdminCap, + }; + } +} + +/** @deprecated Use `PartyosClient`. Retained for callers migrating from `@misofm/protocol/party`. */ +export { PartyosClient as PartyProtocolClient }; diff --git a/packages/partyos/src/contracts.ts b/packages/partyos/src/contracts.ts new file mode 100644 index 0000000..a06faac --- /dev/null +++ b/packages/partyos/src/contracts.ts @@ -0,0 +1,31 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Barrel for the codegen-generated, ABI-bound bindings (BCS structs + type-safe +// Move calls). Re-exported from the package root as the `contracts` namespace. +// +// The generated default `@local-pkg/partyos` identity is a source label only: +// callers must inject the exact published address through `PartyDeployment` +// before building a transaction. + +import * as rawParty from "./contracts/partyos/party.ts"; + +type PublicModule = Omit; +function withoutUnsafeCalls( + module: M, + keys: K, +): PublicModule { + return Object.fromEntries( + Object.entries(module).filter(([key]) => !keys.includes(key as keyof M)), + ) as PublicModule; +} + +/** + * Public Move functions that return references. A PTB can borrow internally, + * but a Move-call command result cannot carry a reference to a later command, + * so they are kept out of the curated barrel and the client's `call` namespace. + */ +export const PARTY_REF_RETURNING_CALLS = ["groupMembers", "uid", "uidMut"] as const; + +/** Party core (BCS codecs remain available; PTB-inaccessible references do not). */ +export const party = withoutUnsafeCalls(rawParty, PARTY_REF_RETURNING_CALLS); diff --git a/packages/protocol/src/contracts/miso_party/deps/sui/vec_set.ts b/packages/partyos/src/contracts/partyos/deps/sui/vec_set.ts similarity index 100% rename from packages/protocol/src/contracts/miso_party/deps/sui/vec_set.ts rename to packages/partyos/src/contracts/partyos/deps/sui/vec_set.ts diff --git a/packages/protocol/src/contracts/miso_party/party.ts b/packages/partyos/src/contracts/partyos/party.ts similarity index 93% rename from packages/protocol/src/contracts/miso_party/party.ts rename to packages/partyos/src/contracts/partyos/party.ts index 50fc5fc..a1ef891 100644 --- a/packages/protocol/src/contracts/miso_party/party.ts +++ b/packages/partyos/src/contracts/partyos/party.ts @@ -20,7 +20,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as vec_set from './deps/sui/vec_set.ts'; -const $moduleName = '@local-pkg/miso_party::party'; +const $moduleName = '@local-pkg/partyos::party'; /** The type of self: individual person or group. */ export const PartyKind = new MoveEnum({ name: `${$moduleName}::PartyKind`, fields: { /** A single person (artist, producer, etc.). */ @@ -124,7 +124,7 @@ export interface NewOptions { * state. */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, '0x1::string::String', @@ -154,7 +154,7 @@ export interface ShareOptions { * capability. */ export function share(options: ShareOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null @@ -182,7 +182,7 @@ export interface SetNameOptions { } /** Sets the human-readable name of the party. Requires the admin capability. */ export function setName(options: SetNameOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -217,7 +217,7 @@ export interface InvitePartyOptions { * individual (not another group). */ export function inviteParty(options: InvitePartyOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -251,7 +251,7 @@ export interface AcceptInviteOptions { * sides in one transaction, so they can't diverge. */ export function acceptInvite(options: AcceptInviteOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -283,7 +283,7 @@ export interface DeclineInviteOptions { * Clears the group-side invitation and the member-facing inbox entry together. */ export function declineInvite(options: DeclineInviteOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -315,7 +315,7 @@ export interface RevokeInviteOptions { * group-side invitation and the member-facing inbox entry together. */ export function revokeInvite(options: RevokeInviteOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -348,7 +348,7 @@ export interface LeaveOptions { * and the member's own membership record. */ export function leave(options: LeaveOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -383,7 +383,7 @@ export interface RemoveMemberOptions { * group's membership" and nothing else on the member is touchable. */ export function removeMember(options: RemoveMemberOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null, @@ -404,7 +404,7 @@ export interface NewIndividualKindOptions { } /** Creates a new individual party kind. */ export function newIndividualKind(options: NewIndividualKindOptions = {}) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; return (tx: Transaction) => tx.moveCall({ package: packageAddress, module: 'party', @@ -418,7 +418,7 @@ export interface NewGroupKindOptions { } /** Creates a new group party kind with an empty member set. */ export function newGroupKind(options: NewGroupKindOptions = {}) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; return (tx: Transaction) => tx.moveCall({ package: packageAddress, module: 'party', @@ -436,7 +436,7 @@ export interface NameOptions { } /** Returns the human-readable name of this party. */ export function name(options: NameOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -459,7 +459,7 @@ export interface CreatedAtMsOptions { } /** Returns the Unix ms when this party was created. */ export function createdAtMs(options: CreatedAtMsOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -482,7 +482,7 @@ export interface IsIndividualKindOptions { } /** Returns true if this party is an individual. */ export function isIndividualKind(options: IsIndividualKindOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -505,7 +505,7 @@ export interface IsGroupKindOptions { } /** Returns true if this party is a group. */ export function isGroupKind(options: IsGroupKindOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -528,7 +528,7 @@ export interface GroupMembersOptions { } /** Returns a reference to the group members. Aborts if this party is not a group. */ export function groupMembers(options: GroupMembersOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -557,7 +557,7 @@ export interface IsMemberOptions { * extensions use for member-gated authorization. */ export function isMember(options: IsMemberOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, '0x2::object::ID' @@ -583,7 +583,7 @@ export interface HasPendingInviteOptions { } /** Whether the group has a pending invite outstanding for `member_id`. */ export function hasPendingInvite(options: HasPendingInviteOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, '0x2::object::ID' @@ -613,7 +613,7 @@ export interface HasPendingMembershipOptions { * enumerating only the member party's dynamic fields. */ export function hasPendingMembership(options: HasPendingMembershipOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, '0x2::object::ID' @@ -637,7 +637,7 @@ export interface PartyKindNameOptions { } /** Returns the human-readable name of the party kind. */ export function partyKindName(options: PartyKindNameOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -662,7 +662,7 @@ export interface AuthorizeOptions { } /** Verifies that the admin capability matches this party. */ export function authorize(options: AuthorizeOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null @@ -686,7 +686,7 @@ export interface PartyAdminCapPartyIdOptions { } /** Returns the ID of the party associated with the admin capability. */ export function partyAdminCapPartyId(options: PartyAdminCapPartyIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -709,7 +709,7 @@ export interface UidOptions { } /** Returns a reference to the party's UID for reading dynamic fields. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -737,7 +737,7 @@ export interface UidMutOptions { * Requires the admin capability. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null, null @@ -761,7 +761,7 @@ export interface AssertIsIndividualKindOptions { } /** Aborts if this party is not an individual. */ export function assertIsIndividualKind(options: AssertIsIndividualKindOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -784,7 +784,7 @@ export interface AssertIsGroupKindOptions { } /** Aborts if this party is not a group. */ export function assertIsGroupKind(options: AssertIsGroupKindOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_party'; + const packageAddress = options.package ?? '@local-pkg/partyos'; const argumentsTypes = [ null ] satisfies (string | null)[]; diff --git a/packages/partyos/src/contracts/utils/index.ts b/packages/partyos/src/contracts/utils/index.ts new file mode 100644 index 0000000..112285f --- /dev/null +++ b/packages/partyos/src/contracts/utils/index.ts @@ -0,0 +1,428 @@ + +import { + bcs, + type BcsType, + type TypeTag, + TypeTagSerializer, + BcsStruct, + BcsEnum, + BcsTuple, +} from '@mysten/sui/bcs'; +import { normalizeStructTag, normalizeSuiAddress } from '@mysten/sui/utils'; +import { + type TransactionArgument, + type TransactionObjectArgument, + isArgument, +} from '@mysten/sui/transactions'; +import { type ClientWithCoreApi, type SuiClientTypes } from '@mysten/sui/client'; + +const MOVE_STDLIB_ADDRESS = normalizeSuiAddress('0x1'); +const SUI_FRAMEWORK_ADDRESS = normalizeSuiAddress('0x2'); + +export type RawTransactionArgument = T | TransactionArgument; + +export type GetOptions = {}> = + SuiClientTypes.GetObjectOptions & { client: ClientWithCoreApi }; + +export type GetManyOptions = {}> = + SuiClientTypes.GetObjectsOptions & { client: ClientWithCoreApi }; + +export function getPureBcsSchema(typeTag: string | TypeTag): BcsType | null { + const parsedTag = typeof typeTag === 'string' ? TypeTagSerializer.parseFromStr(typeTag) : typeTag; + + if ('u8' in parsedTag) { + return bcs.U8; + } else if ('u16' in parsedTag) { + return bcs.U16; + } else if ('u32' in parsedTag) { + return bcs.U32; + } else if ('u64' in parsedTag) { + return bcs.U64; + } else if ('u128' in parsedTag) { + return bcs.U128; + } else if ('u256' in parsedTag) { + return bcs.U256; + } else if ('address' in parsedTag) { + return bcs.Address; + } else if ('bool' in parsedTag) { + return bcs.Bool; + } else if ('vector' in parsedTag) { + const type = getPureBcsSchema(parsedTag.vector); + return type ? bcs.vector(type) : null; + } else if ('struct' in parsedTag) { + const structTag = parsedTag.struct; + const pkg = normalizeSuiAddress(structTag.address); + + if (pkg === MOVE_STDLIB_ADDRESS) { + if ( + (structTag.module === 'ascii' || structTag.module === 'string') && + structTag.name === 'String' + ) { + return bcs.String; + } + + if (structTag.module === 'option' && structTag.name === 'Option') { + const inner = structTag.typeParams[0]; + const type = inner ? getPureBcsSchema(inner) : null; + return type ? bcs.option(type) : null; + } + } + + if ( + pkg === SUI_FRAMEWORK_ADDRESS && + structTag.module === 'object' && + (structTag.name === 'ID' || structTag.name === 'UID') + ) { + return bcs.Address; + } + } + + return null; +} + +export function normalizeMoveArguments( + args: unknown[] | object, + argTypes: readonly (string | null)[], + parameterNames?: string[], +) { + const argLen = Array.isArray(args) ? args.length : Object.keys(args).length; + if (parameterNames && argLen !== parameterNames.length) { + throw new Error( + `Invalid number of arguments, expected ${parameterNames.length}, got ${argLen}`, + ); + } + + const normalizedArgs: TransactionArgument[] = []; + + let index = 0; + for (const argType of argTypes) { + if (argType === '0x2::clock::Clock') { + normalizedArgs.push((tx) => tx.object.clock()); + continue; + } + + if (argType === '0x2::random::Random') { + normalizedArgs.push((tx) => tx.object.random()); + continue; + } + + if (argType === '0x2::deny_list::DenyList') { + normalizedArgs.push((tx) => tx.object.denyList()); + continue; + } + + if (argType === '0x2::accumulator::AccumulatorRoot') { + // Chain-wide shared singleton at a fixed address (SUI_ACCUMULATOR_ROOT_OBJECT_ID). + normalizedArgs.push((tx) => tx.object('0xacc')); + continue; + } + + if (argType === '0x3::sui_system::SuiSystemState') { + normalizedArgs.push((tx) => tx.object.system()); + continue; + } + + let arg; + if (Array.isArray(args)) { + if (index >= args.length) { + throw new Error( + `Invalid number of arguments, expected at least ${index + 1}, got ${args.length}`, + ); + } + arg = args[index]; + } else { + if (!parameterNames) { + throw new Error(`Expected arguments to be passed as an array`); + } + const name = parameterNames[index]; + arg = + name !== undefined && Object.prototype.hasOwnProperty.call(args, name) + ? args[name as keyof typeof args] + : undefined; + + if (arg === undefined) { + throw new Error(`Parameter ${name} is required`); + } + } + + index += 1; + + if (typeof arg === 'function' || isArgument(arg)) { + normalizedArgs.push(arg as TransactionArgument); + continue; + } + + const bcsType = argType === null ? null : getPureBcsSchema(argType); + + if (bcsType) { + const bytes = bcsType.serialize(arg as never); + normalizedArgs.push((tx) => tx.pure(bytes)); + continue; + } + + if (typeof arg === 'string') { + normalizedArgs.push((tx) => tx.object(arg)); + continue; + } + + throw new Error(`Invalid argument ${stringify(arg)} for type ${argType}`); + } + + return normalizedArgs; +} + +/* -------------------------- Config-mapped arguments -------------------------- */ + +/** Context passed to config resolver functions. */ +export interface ConfigResolverContext { + /** + * The matched parameter's own instantiated type arguments (not the whole function's type + * argument tuple). Concrete instantiations are canonical type tags; generic positions pass + * the caller's `typeArguments` strings through as provided. + */ + typeArguments: string[]; + /** The package address the generated call will be sent to. */ + packageAddress: string; + /** The Move module of the generated call. */ + moduleName: string; + /** The Move function of the generated call. */ + functionName: string; + /** The Move name of the matched parameter, when the summary includes parameter names. */ + parameterName?: string; + /** The matched parameter's position in the generated function's arguments. */ + parameterIndex: number; +} + +/** + * A non-callback transaction argument in a generated config object, used for keys whose matched + * parameter can't be supplied as an object id string. + */ +export type ConfigObjectValue = Exclude unknown>; + +/** + * A plain value in a generated config object: an object id or a non-callback transaction + * argument. Keys that can resolve multiple bindings are typed as resolver functions instead. + */ +export type ConfigValue = string | ConfigObjectValue; + +/* -------------------------- Move type tags -------------------------- */ + +/** A type argument: a type tag string, or a BCS type whose name is a Move type. */ +export type TypeArgument = string | BcsType; + +export interface TypeTagOptions { + package?: string; + typeArguments?: readonly TypeArgument[]; +} + +/** + * `typeArguments` is required when the type's name contains unfilled + * `phantom X` parameters (at any depth). Everything else — argument arity, + * position contents, and tag validity — is validated at runtime. + */ +type TypeTagParams = Name extends `${string}phantom ${string}` + ? [options: TypeTagOptions & { typeArguments: readonly TypeArgument[] }] + : [options?: TypeTagOptions]; + +type ResolveTypeTagOptions = { client: ClientWithCoreApi } & ( + Name extends `${string}phantom ${string}` + ? TypeTagOptions & { typeArguments: readonly TypeArgument[] } + : TypeTagOptions +); + +const HAS_PHANTOM_REGEX = /phantom [A-Za-z_$][A-Za-z0-9_$]*/; + +function splitTopLevelTypeArgs(inner: string): string[] { + const parts: string[] = []; + let depth = 0; + let current = ''; + for (const char of inner) { + if (char === ',' && depth === 0) { + parts.push(current.trim()); + current = ''; + continue; + } + if (char === '<') depth++; + if (char === '>') depth--; + current += char; + } + if (current) parts.push(current.trim()); + return parts; +} + +function buildTypeTag(name: string, options: TypeTagOptions | undefined): string { + const lt = name.indexOf('<'); + const base = lt === -1 ? name : name.slice(0, lt); + + if (base.split('::').length !== 3) { + throw new Error(`${name} is not a top-level Move type`); + } + + let result = name; + + if (options?.typeArguments) { + const baked = lt === -1 ? [] : splitTopLevelTypeArgs(name.slice(lt + 1, -1)); + const supplied = options.typeArguments.map((arg) => { + if (typeof arg === 'string') { + return arg; + } + if (arg && typeof arg.serialize === 'function' && typeof arg.name === 'string') { + return arg.name; + } + throw new Error(`Invalid type argument ${stringify(arg)}`); + }); + + if (supplied.length !== baked.length) { + throw new Error( + `Expected ${baked.length} type arguments for ${base}, got ${supplied.length}`, + ); + } + + result = supplied.length === 0 ? base : `${base}<${supplied.join(', ')}>`; + } + + if (HAS_PHANTOM_REGEX.test(result)) { + throw new Error( + options?.typeArguments + ? `A type argument contains an unfilled phantom parameter in ${result}` + : `Missing type arguments for ${result}`, + ); + } + + if (options?.package) { + const [, ...rest] = result.split('::'); + result = [options.package, ...rest].join('::'); + } + + // fully validate address-only tags (MVR names can't be parsed as type tags) + if (!HAS_PHANTOM_REGEX.test(result) && !/[@/]/.test(result)) { + TypeTagSerializer.parseFromStr(result); + } + + return result; +} + +async function resolveBuiltTypeTag( + name: string, + options: { client: ClientWithCoreApi } & TypeTagOptions, +): Promise { + const { client, ...rest } = options; + const { type } = await client.core.mvr.resolveType({ + type: buildTypeTag(name, rest), + }); + return normalizeStructTag(type); +} + +export class MoveStruct< + T extends Record>, + const Name extends string = string, +> extends BcsStruct { + /** + * Build the type tag for this struct. + * + * `typeArguments` is the full positional list, in Move declaration order, and + * is required when the struct has unfilled phantom parameters. The result may + * contain MVR names: those are valid in transaction `typeArguments`, but for + * queries or comparisons against on-chain data use `resolveTypeTag` instead. + */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** + * Build the type tag for this struct, then resolve any MVR names through the + * client (using its configured overrides and the MVR API) and return the + * normalized, address-only form suitable for queries and comparisons against + * on-chain data. + */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } + + async get = {}>({ + objectId, + ...options + }: GetOptions): Promise< + SuiClientTypes.Object & { json: BcsStruct['$inferType'] } + > { + const [res] = await this.getMany({ + ...options, + objectIds: [objectId], + }); + + if (!res) { + throw new Error(`No object found for id ${objectId}`); + } + + return res; + } + + async getMany = {}>({ + client, + ...options + }: GetManyOptions): Promise< + Array & { json: BcsStruct['$inferType'] }> + > { + const response = (await client.core.getObjects({ + ...options, + include: { + ...options.include, + content: true, + }, + })) as SuiClientTypes.GetObjectsResponse; + + return response.objects.map((obj) => { + if (obj instanceof Error) { + throw obj; + } + + return { + ...obj, + json: this.parse(obj.content), + }; + }); + } +} + +export class MoveEnum< + T extends Record | null>, + const Name extends string, +> extends BcsEnum { + /** Build the type tag for this enum. See `MoveStruct.typeTag` for semantics. */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** Build and resolve the type tag for this enum. See `MoveStruct.resolveTypeTag`. */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } +} + +export class MoveTuple< + const T extends readonly BcsType[], + const Name extends string, +> extends BcsTuple { + /** Build the type tag for this struct. See `MoveStruct.typeTag` for semantics. */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** Build and resolve the type tag for this struct. See `MoveStruct.resolveTypeTag`. */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } +} + +function stringify(val: unknown) { + if (typeof val === 'object') { + return JSON.stringify(val, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ); + } + if (typeof val === 'bigint') { + return val.toString(); + } + + return val; +} diff --git a/packages/partyos/src/deployments.ts b/packages/partyos/src/deployments.ts new file mode 100644 index 0000000..51aa52f --- /dev/null +++ b/packages/partyos/src/deployments.ts @@ -0,0 +1,90 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { normalizeSuiAddress } from "@mysten/sui/utils"; + +/** Sui networks for which this SDK may bundle a verified deployment. */ +export type PartyosNetwork = "mainnet" | "testnet"; + +/** + * The single independently published package required by this SDK surface: + * the `partyos` object-model package (module `party`). + * + * This name is a stable deployment-manifest key, not a Move module name. Its + * value is never inferred from a source label or a previous publish. + */ +const CANONICAL_PARTYOS_PACKAGE_NAMES = ["partyos"] as const; + +export type PartyosPackageName = (typeof CANONICAL_PARTYOS_PACKAGE_NAMES)[number]; + +/** + * Exact package address from one compatible publish. Fill this only from the + * verified immutable publish record. + */ +export type PartyDeployment = Readonly>; + +/** Verified immutable deployments bundled with this SDK release. */ +export const PARTYOS_DEPLOYMENTS = Object.freeze({ + testnet: Object.freeze({ + partyos: "0x342425da86c4389c1f5558013251f18017ead98414db5644569890ba3a4a434c", + } as const), +} as const) satisfies Partial>; + +/** Resolve a verified manifest, failing closed for unbundled networks. */ +export function getPartyDeployment(network: string): PartyDeployment { + const deployment = (PARTYOS_DEPLOYMENTS as Partial>)[network]; + if (!deployment) { + throw new Error( + `@misofm/partyos: no verified PartyOS deployment is bundled for network "${network}". ` + + "Inject the exact post-publish manifest or pass an explicit deployment; historic package IDs are rejected.", + ); + } + return normalizePartyDeployment(deployment); +} + +/** + * Validates an explicit manifest before any Move target is constructed. This is + * useful at configuration boundaries such as environment-file loading. + */ +export function assertPartyDeployment(deployment: unknown): asserts deployment is PartyDeployment { + if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { + throw new Error( + `@misofm/partyos: deployment must be an object with exactly these package IDs: ${CANONICAL_PARTYOS_PACKAGE_NAMES.join(", ")}.`, + ); + } + const entries = deployment as Record; + const keys = Object.keys(entries); + const unexpected = keys.filter( + (key) => !CANONICAL_PARTYOS_PACKAGE_NAMES.includes(key as PartyosPackageName), + ); + if (unexpected.length > 0 || keys.length !== CANONICAL_PARTYOS_PACKAGE_NAMES.length) { + throw new Error( + `@misofm/partyos: deployment must contain exactly these package IDs: ${CANONICAL_PARTYOS_PACKAGE_NAMES.join(", ")}.`, + ); + } + for (const name of CANONICAL_PARTYOS_PACKAGE_NAMES) { + const packageId = entries[name]; + if (typeof packageId !== "string") { + throw new Error(`@misofm/partyos: deployment is missing package ID for "${name}".`); + } + let normalized: string; + try { + normalized = normalizeSuiAddress(packageId); + } catch { + throw new Error(`@misofm/partyos: deployment package ID for "${name}" is not a valid Sui address.`); + } + if (packageId !== normalized) { + throw new Error( + `@misofm/partyos: deployment package ID for "${name}" must be normalized (${normalized}).`, + ); + } + } +} + +/** Validate and snapshot an untrusted manifest so later caller mutation is inert. */ +export function normalizePartyDeployment(deployment: unknown): PartyDeployment { + assertPartyDeployment(deployment); + return Object.freeze( + Object.fromEntries(CANONICAL_PARTYOS_PACKAGE_NAMES.map((name) => [name, deployment[name]])), + ) as PartyDeployment; +} diff --git a/packages/partyos/src/index.ts b/packages/partyos/src/index.ts new file mode 100644 index 0000000..2b72434 --- /dev/null +++ b/packages/partyos/src/index.ts @@ -0,0 +1,15 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// `@misofm/partyos` — the PartyOS object model: Party identity, its admin cap, +// and consent-based group membership. Everything Miso offers on top of a Party +// (profiles, media, roles, tags, genres, CTAs, platform links, the party +// wallet) is an extension and ships from `@misofm/platform`. +export * from "./types.ts"; +export * from "./transactions.ts"; +export * from "./queries.ts"; +export * from "./client.ts"; +export * from "./deployments.ts"; + +// Generated, ABI-bound bindings (BCS structs + type-safe Move calls). +export * as contracts from "./contracts.ts"; diff --git a/packages/partyos/src/internal.ts b/packages/partyos/src/internal.ts new file mode 100644 index 0000000..288a490 --- /dev/null +++ b/packages/partyos/src/internal.ts @@ -0,0 +1,30 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// The single boundary between generated BCS-parse output (snake_case, Move-shaped) +// and the public camelCase types. + +import { fromBase64 } from "@mysten/sui/utils"; +import type { Party } from "./types.ts"; + +// deno-lint-ignore no-explicit-any -- generated parse output is loosely typed +export function mapParty(id: string, d: any): Party { + const kind = d.kind; + const createdAtMs = Number(d.created_at_ms); + // PartyKind is `Individual | Group(VecSet)`. The enum parses to + // `{ $kind, Individual? , Group? }`; Group's payload is a VecSet `{ contents }`. + const groupPayload = kind?.Group ?? (kind?.$kind === "Group" ? kind.value : undefined); + if (groupPayload !== undefined) { + const contents = groupPayload?.contents ?? groupPayload ?? []; + const members = (Array.isArray(contents) ? contents : []).map((m: unknown) => + typeof m === "string" ? m : (m as { id?: string })?.id ?? String(m), + ); + return { id, kind: "group", name: d.name, members, createdAtMs }; + } + return { id, kind: "individual", name: d.name, createdAtMs }; +} + +/** Normalizes a dynamic-field key's BCS bytes (Uint8Array or base64 string). */ +export function keyBytes(bcs: unknown): Uint8Array { + return typeof bcs === "string" ? fromBase64(bcs) : (bcs as Uint8Array); +} diff --git a/packages/partyos/src/queries.ts b/packages/partyos/src/queries.ts new file mode 100644 index 0000000..4ad08b9 --- /dev/null +++ b/packages/partyos/src/queries.ts @@ -0,0 +1,167 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Typed reads: fetch an on-chain object (or dynamic field) via the Core API, BCS-parse +// it through the generated struct, and map to the public camelCase types. + +import type { ClientWithCoreApi } from "@mysten/sui/client"; +import { deriveDynamicFieldID, deriveObjectID, normalizeStructTag } from "@mysten/sui/utils"; +import { bcs } from "@mysten/sui/bcs"; +import { + MembershipKey as MembershipKeyBcs, + Party as PartyBcs, + PendingInviteKey as PendingInviteKeyBcs, + PendingMembershipKey as PendingMembershipKeyBcs, +} from "./contracts/partyos/party.ts"; +import { keyBytes, mapParty } from "./internal.ts"; +import type { Party } from "./types.ts"; + +/** True for the Core API's "object does not exist" error (a missing dynamic field). */ +export function isNotFound(e: unknown): boolean { + const msg = e instanceof Error ? e.message : String(e); + return /not\s*found|does not exist|no object/i.test(msg); +} + +/** + * Reads an object's BCS content, or null when it does not exist. A missing + * object (e.g. an unset dynamic field) surfaces as a thrown "not found" from the + * Core API, not an empty result, so optional reads can treat it as absence. + */ +export async function getObjectContent(client: ClientWithCoreApi, objectId: string): Promise { + try { + const { object } = await client.core.getObject({ objectId, include: { content: true } }); + return object?.content ?? null; + } catch (e) { + if (isNotFound(e)) return null; + throw e; + } +} + +/** The `Party` struct tag of one partyos deployment. */ +export function partyType(partyPackageId: string): string { + return normalizeStructTag(`${partyPackageId}::party::Party`); +} + +// A Party from another partyos package (a previous generation) parses +// identically but no deployed package accepts it, so the object's type is +// checked against the deployment before its content is trusted. +function assertPartyType(objectId: string, type: string | undefined, partyPackageId: string): void { + const expected = partyType(partyPackageId); + if (type === undefined || normalizeStructTag(type) !== expected) { + throw new Error( + `Object ${objectId} is ${type ?? "of unknown type"}, not a ${expected}` + + (type?.endsWith("::party::Party") ? " (a Party from a different partyos deployment)" : ""), + ); + } +} + +/** Fetches and parses a shared `Party` object of this deployment's partyos package. */ +export async function getPartyById( + client: ClientWithCoreApi, + partyId: string, + partyPackageId: string, +): Promise { + let object; + try { + ({ object } = await client.core.getObject({ objectId: partyId, include: { content: true } })); + } catch (e) { + if (isNotFound(e)) throw new Error(`Party not found: ${partyId}`); + throw e; + } + if (!object?.content) throw new Error(`Party not found: ${partyId}`); + assertPartyType(partyId, object.type, partyPackageId); + return mapParty(partyId, PartyBcs.parse(object.content)); +} + +/** + * Fetches and parses multiple shared `Party` objects in one Core request. + * Missing objects are skipped; an object of another type is an error. + */ +export async function getPartiesByIds( + client: ClientWithCoreApi, + partyIds: readonly string[], + partyPackageId: string, +): Promise>> { + if (partyIds.length === 0) return {}; + const { objects } = await client.core.getObjects({ + objectIds: [...new Set(partyIds)], + include: { content: true }, + }); + const parties: Partial> = {}; + for (const obj of objects) { + if (obj instanceof Error) continue; + assertPartyType(obj.objectId, obj.type, partyPackageId); + parties[obj.objectId] = mapParty(obj.objectId, PartyBcs.parse(obj.content)); + } + return parties; +} + +/** Derives a party's `PartyAdminCap` id (it is a `derived_object` off the party UID). */ +export function derivePartyAdminCapId(partyId: string, partyPackageId: string): string { + return deriveObjectID( + partyId, + `${partyPackageId}::party::PartyAdminCapKey`, + bcs.Address.serialize(partyId).toBytes(), + ); +} + +// === Group membership reads === + +/** Collects the ids stored in every dynamic-field key of `parentId` whose type ends with `keySuffix`. */ +async function collectKeyIds( + client: ClientWithCoreApi, + parentId: string, + keySuffix: string, + codec: { parse(bytes: Uint8Array): readonly unknown[] }, +): Promise { + const ids: string[] = []; + let cursor: string | null | undefined; + do { + const page = await client.core.listDynamicFields({ parentId, cursor: cursor ?? undefined }); + for (const f of page.dynamicFields) { + if (typeof f.name?.type === "string" && f.name.type.endsWith(keySuffix) && f.name.bcs != null) { + const [id] = codec.parse(keyBytes(f.name.bcs)); + ids.push(String(id)); + } + } + cursor = page.hasNextPage ? page.cursor : null; + } while (cursor); + return ids; +} + +/** + * The group ids a party currently belongs to, read from its `MembershipKey` + * dynamic fields (the member-side record). No indexer required. + */ +export async function getMemberships(client: ClientWithCoreApi, partyId: string): Promise { + return collectKeyIds(client, partyId, "::party::MembershipKey", MembershipKeyBcs); +} + +/** The member ids invited to a group but not yet accepted (its `PendingInviteKey` fields). */ +export async function getPendingInvites(client: ClientWithCoreApi, groupId: string): Promise { + return collectKeyIds(client, groupId, "::party::PendingInviteKey", PendingInviteKeyBcs); +} + +/** + * The group ids that have invited a party but are still awaiting its response. + * This reads the member-side `PendingMembershipKey` inbox index, so it only + * enumerates the target party's dynamic fields. + */ +export async function getPendingMemberships(client: ClientWithCoreApi, partyId: string): Promise { + return collectKeyIds(client, partyId, "::party::PendingMembershipKey", PendingMembershipKeyBcs); +} + +/** Whether `memberId` currently holds a membership record for `groupId`. */ +export async function isMember( + client: ClientWithCoreApi, + memberId: string, + groupId: string, + partyPackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID( + memberId, + `${partyPackageId}::party::MembershipKey`, + MembershipKeyBcs.serialize([groupId]).toBytes(), + ); + return (await getObjectContent(client, fieldId)) !== null; +} diff --git a/packages/protocol/src/party/transactions.ts b/packages/partyos/src/transactions.ts similarity index 98% rename from packages/protocol/src/party/transactions.ts rename to packages/partyos/src/transactions.ts index 2982198..193a70a 100644 --- a/packages/protocol/src/party/transactions.ts +++ b/packages/partyos/src/transactions.ts @@ -7,7 +7,7 @@ // go through the generated typed call fns (party.*). import type { Transaction } from "@mysten/sui/transactions"; -import * as party from "../contracts/miso_party/party.ts"; +import * as party from "./contracts/partyos/party.ts"; export type TxThunk = (tx: Transaction) => void | Promise; diff --git a/packages/partyos/src/types.ts b/packages/partyos/src/types.ts new file mode 100644 index 0000000..1a5118e --- /dev/null +++ b/packages/partyos/src/types.ts @@ -0,0 +1,18 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Public, camelCase result types. The mappers in ./internal.ts turn the generated +// (snake_case, Move-shaped) parse output into these. + +export type PartyKind = "individual" | "group"; + +export interface Party { + id: string; + kind: PartyKind; + /** Human-readable name (not verified). */ + name: string; + /** Member party ids — present only when `kind === "group"`. */ + members?: string[]; + /** Unix ms when the party was created. */ + createdAtMs: number; +} diff --git a/packages/partyos/tests/deployments.test.ts b/packages/partyos/tests/deployments.test.ts new file mode 100644 index 0000000..e4b8733 --- /dev/null +++ b/packages/partyos/tests/deployments.test.ts @@ -0,0 +1,31 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { expect, test } from "bun:test"; +import { PartyosClient, partyos } from "../src/client.ts"; +import { PARTYOS_DEPLOYMENTS, assertPartyDeployment, getPartyDeployment } from "../src/deployments.ts"; + +const PKG = "0x" + "a".repeat(64); + +test("the bundled testnet manifest has exactly one key, partyos", () => { + expect(Object.keys(getPartyDeployment("testnet"))).toEqual(["partyos"]); + expect(getPartyDeployment("testnet").partyos).toBe(PARTYOS_DEPLOYMENTS.testnet.partyos); +}); + +test("unbundled networks and malformed manifests fail closed", () => { + expect(() => getPartyDeployment("localnet")).toThrow(/no verified PartyOS deployment/); + expect(() => assertPartyDeployment({ partyos: PKG, misoParty: PKG })).toThrow(/exactly these package IDs/); + expect(() => assertPartyDeployment({ partyos: "0xabc" })).toThrow(/must be normalized/); + expect(() => assertPartyDeployment({})).toThrow(/exactly these package IDs/); +}); + +test("the client extension registers under `partyos` and binds the package to every builder", async () => { + const registration = partyos({ deployment: { partyos: PKG } }); + expect(registration.name).toBe("partyos"); + const client = registration.register({ network: "testnet", core: {} } as never); + expect(client).toBeInstanceOf(PartyosClient); + expect(client.deployment).toEqual({ partyos: PKG }); + expect(client.derivePartyAdminCapId("0x" + "1".repeat(64))).toMatch(/^0x[0-9a-f]{64}$/); + expect("groupMembers" in client.call.party).toBeFalse(); + expect(typeof client.call.party.setName).toBe("function"); +}); diff --git a/packages/protocol/tests/party-queries.test.ts b/packages/partyos/tests/queries.test.ts similarity index 91% rename from packages/protocol/tests/party-queries.test.ts rename to packages/partyos/tests/queries.test.ts index 0889f48..436bd42 100644 --- a/packages/protocol/tests/party-queries.test.ts +++ b/packages/partyos/tests/queries.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, mock } from "bun:test"; import type { ClientWithCoreApi } from "@mysten/sui/client"; -import { Party as PartyBcs } from "../src/contracts/miso_party/party.ts"; -import { getPartiesByIds, getPartyById } from "../src/party/queries.ts"; +import { Party as PartyBcs } from "../src/contracts/partyos/party.ts"; +import { getPartiesByIds, getPartyById } from "../src/queries.ts"; const id = (digit: string) => `0x${digit.repeat(64)}`; const PKG = id("a"); @@ -68,7 +68,7 @@ describe("getPartiesByIds", () => { expect(getObjects).not.toHaveBeenCalled(); }); - it("rejects a Party from another miso_party deployment", async () => { + it("rejects a Party from another partyos deployment", async () => { const staleId = id("3"); const getObjects = mock(async () => ({ objects: [ @@ -87,7 +87,7 @@ describe("getPartiesByIds", () => { const client = { core: { getObjects } } as unknown as ClientWithCoreApi; await expect(getPartiesByIds(client, [staleId], PKG)).rejects.toThrow( - /a Party from a different miso_party deployment/, + /a Party from a different partyos deployment/, ); }); }); @@ -106,7 +106,7 @@ describe("getPartyById", () => { await expect(getPartyById(client, soloId, PKG)).resolves.toMatchObject({ id: soloId, name: "Solo" }); }); - it("rejects a Party from another miso_party deployment", async () => { + it("rejects a Party from another partyos deployment", async () => { const staleId = id("3"); const getObject = mock(async () => ({ object: { objectId: staleId, type: partyType(OLD_PKG), content: partyContent(staleId) }, @@ -114,7 +114,7 @@ describe("getPartyById", () => { const client = { core: { getObject } } as unknown as ClientWithCoreApi; await expect(getPartyById(client, staleId, PKG)).rejects.toThrow( - /a Party from a different miso_party deployment/, + /a Party from a different partyos deployment/, ); }); diff --git a/packages/partyos/tsconfig.build.json b/packages/partyos/tsconfig.build.json new file mode 100644 index 0000000..c064a26 --- /dev/null +++ b/packages/partyos/tsconfig.build.json @@ -0,0 +1,33 @@ +// Build config — emits the published artifact to `dist/`. +// +// The source uses `./foo.ts` relative imports, which Bun runs natively but which +// no other consumer can: Node refuses to type-strip anything under +// `node_modules`, and `tsc` rejects `.ts` import specifiers unless the CONSUMER +// opts into `allowImportingTsExtensions` (which in turn forbids them from +// emitting, so any consumer that is itself a library could not build). +// +// `rewriteRelativeImportExtensions` rewrites those specifiers to `./foo.js` on +// emit, so the shipped artifact is plain ESM + declarations with no demands on +// the consumer's tsconfig — and the source needs no edits. +// +// `lib` adds DOM here because `credits.ts` uses `TextEncoder`. Locally that +// resolves only via the ambient globals from `@types/bun`, which consumers do +// not have; declaring it explicitly keeps the emitted .d.ts self-contained. +{ + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["tests"], + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "rewriteRelativeImportExtensions": true, + "allowImportingTsExtensions": true, + "lib": ["ESNext", "DOM"], + "rootDir": "src", + "outDir": "dist" + } +} diff --git a/packages/partyos/tsconfig.json b/packages/partyos/tsconfig.json new file mode 100644 index 0000000..564a599 --- /dev/null +++ b/packages/partyos/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"] +} diff --git a/packages/partyos/typedoc.json b/packages/partyos/typedoc.json new file mode 100644 index 0000000..d55cca9 --- /dev/null +++ b/packages/partyos/typedoc.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": [ + "src/index.ts" + ], + "out": "docs", + "name": "@misofm/partyos", + "readme": "README.md", + "excludePrivate": true, + "excludeProtected": true, + "excludeInternal": true, + "categorizeByGroup": true, + "sort": [ + "source-order" + ], + "plugin": [ + "typedoc-plugin-markdown" + ], + "outputFileStrategy": "modules" +} diff --git a/packages/platform/README.md b/packages/platform/README.md index 9749ace..9ca8535 100644 --- a/packages/platform/README.md +++ b/packages/platform/README.md @@ -1,25 +1,36 @@ # @misofm/platform The complete client SDK for the **Miso platform layer** on Sui: composed catalog, -artist, wallet, and receipt reads; the record production line and sale of copies; -and fail-closed Vault custody, raw Actions, and safe crank plugins built on `@misofm/protocol`'s -protocol and data-extension primitives. - -## The split - -Miso ships two SDK packages, and the package name tells you which promise you are -holding: - -| Package | Layer | Owns | -| -------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | -| `@misofm/protocol` | **Protocol** | Composition, Recording, Release, Party identity; metadata/data extensions; utilities; generic royalty-pool and routed-stake primitives | -| `@misofm/platform` | **Platform** | Pressing, Listing, Record, and an explicitly deployed Vault/Action/plugin compatibility set | - -A release is protocol. Pressing a record off that release and selling it is -platform. So is deciding _what to do_ with a freshly-minted work's share -supply — the protocol only knows how to mint one. Keeping the boundary at the -package line is what stops the open protocol from quietly growing a -storefront (or an opinion about tokenomics). +artist, wallet, and receipt reads; the Party extensions (profile, media, roles, +tags, genres, CTAs, platform links); the record production line and sale of +copies; fail-closed Vault custody, raw Actions, and safe crank plugins; and +every first-party extension and generic royalty primitive built on top of the +`@misofm/musicos` object model and the `@misofm/partyos` Party identity model. + +## The boundary rule + +> `@misofm/musicos` is the object model. Everything Miso offers on top of it is +> platform. + +Miso ships three SDK packages, and the package name tells you which promise +you are holding: + +| Package | Layer | Owns | +| ------------------- | ------------ | -------------------------------------------------------------------------------------------------------------- | +| `@misofm/musicos` | **Object model** | Composition, Recording, Release, Track — the open Move package anyone can build on, no permission required | +| `@misofm/partyos` | **Party identity** | Party, PartyAdminCap, and consent-based group membership — the open Move package Party identity is built on | +| `@misofm/platform` | **Platform** | The Party extensions (profile, media, roles, tags, genres, CTAs, platform links); work extensions; generic royalty-pool/routed-stake primitives; Actions; Vault; Record/Record Shop; product-specific publishing workflows | + +A release is object model. Pressing a record off that release and selling it +is platform. So is deciding _what to do_ with a freshly-minted work's share +supply — the object model only knows how to mint one. Likewise, a Party is +identity — who or what is being described — and lives in `@misofm/partyos`; +everything Miso attaches to that identity (a profile, media, roles, tags, +genres, CTAs, platform links) is an opinion about how to describe or route +value around it, not part of what the Party IS, so those extensions live in +platform alongside work extensions and every royalty primitive. Keeping the +boundary at the package line is what stops the open object models from +quietly growing a storefront (or an opinion about tokenomics). Extensions add data to a work. Raw Actions accept an admin cap and remain composable with either direct authority or a scoped Vault borrow. Three safe, @@ -31,7 +42,7 @@ address-owned admin caps explicitly; it never silently treats a legacy cap as a vaulted one. New Vault IDs are derived from the shared `VaultRegistry`, the raw cap ID, and its type; each VaultAdminCap ID is then derived from its Vault. -This package depends on `@misofm/protocol` directly and imports its bare +This package depends on `@misofm/musicos` directly and imports its bare `createComposition`/`createRecording` primitives, composing them with its own minato-dispersal and share-currency logic in the same PTB — the transaction-thunk composition pattern from the @@ -39,11 +50,11 @@ transaction-thunk composition pattern from the crossing a package boundary. ```sh -bun add @misofm/platform@^0.18.0 +bun add @misofm/platform ``` -`@misofm/protocol` resolves transitively through that dependency, so -applications get exactly one protocol SDK and one compatible deployment map +`@misofm/musicos` resolves transitively through that dependency, so +applications get exactly one object-model SDK and one compatible deployment map without installing it themselves. Registration takes a recursively frozen snapshot of custom deployment/config records without freezing the caller's original objects, so later caller mutation cannot retarget an existing client. @@ -100,8 +111,13 @@ const client = new SuiGrpcClient({ network: "testnet", baseUrl }).$extend( // ledger before any synchronous client-bound builder can be used. await client.miso.ready(); -// The permissionless protocol SDK is part of the same facade. +// The permissionless object-model SDK is part of the same facade. const release = await client.miso.protocol.getReleaseById(releaseId); +// Party identity comes from @misofm/partyos; platform adds the extensions. +// client.miso.party is a PartyPlatformClient wrapping a PartyosClient (core +// reads/builders delegate straight through) bound to this deployment's +// `partyos` (core) and `party` (extensions) sections — also importable +// standalone from `@misofm/platform/party`. const party = await client.miso.party.getPartyById(partyId); // Read: run + one currency's offer, one round trip, no registry lookup. @@ -141,6 +157,42 @@ Verified package and singleton IDs are bundled in from the Sui client's network. Unbundled and custom networks still fail closed unless the caller passes one complete deployment through `miso({ deployment })`. +The deprecated `misoPlatform(config)` constructor remains for callers that +supply flat package ids instead of a complete deployment; it registers at +`client.misoPlatform` instead of `client.miso` and requires the same +`await client.misoPlatform.ready()` gate before use. Prefer zero-config +`miso()` for new integrations. + +### Party and the generated contract tree + +Party identity (the `Party` object, `PartyAdminCap`, group membership) is +owned by `@misofm/partyos`; this package only owns the extensions attached to +it. `PartyPlatformClient` wraps a `PartyosClient` — every core method +(`getPartyById`, `getMemberships`, …) delegates straight through, and `tx`/ +`call`/`bcs` merge the core and extension surfaces — and is also importable +standalone (the same class `client.miso.party` returns): + +```ts +import { PartyosClient } from "@misofm/partyos"; +import { PartyPlatformClient } from "@misofm/platform/party"; + +const core = new PartyosClient(client, verifiedDeployment.partyos); +const party = new PartyPlatformClient(client, core, verifiedDeployment.party); +``` + +Every generated Move package on this side of the boundary — the Party +extensions, work extensions, royalty/routed-stake primitives, Record/Record +Shop, Vault, and every Action/plugin — is reachable through the curated +`contracts` barrel (`@misofm/platform` → `contracts.*`, or +`@misofm/platform/contracts` directly) or, for a module the barrel doesn't +curate, the raw generated file itself. The Party core's generated bindings +live in `@misofm/partyos/contracts` instead: + +```ts +import { record } from "@misofm/platform/contracts"; +import { Record } from "@misofm/platform/contracts/record/record"; +``` + ### High-level platform reads `@misofm/platform/read` turns protocol, pressing, Party, credits, cover, and wallet @@ -240,7 +292,7 @@ the same PTB. ## Publishing (`transactions.ts`, `share.ts`, `release-graph.ts`) -`@misofm/protocol`'s `createComposition`/`createRecording` mint a work and hand +`@misofm/musicos`'s `createComposition`/`createRecording` mint a work and hand back its by-value parts (the object, its admin cap, its freshly-minted share `Balance`) without dispersing, sharing, or transferring anything. This package supplies the opinionated finish on top: @@ -269,7 +321,7 @@ const thunk = client.miso.tx.publishComposition({ `client.miso.tx.publishRecording` and `publishCompositionAndRecording` follow the same shape (the latter atomically, borrow-before-share, in one PTB — -see `@misofm/protocol`'s README for why the ordering is load-bearing). +see `@misofm/musicos`'s README for why the ordering is load-bearing). The protocol, immutable Record and Record Shop packages, minato, and core `ReleaseRegistry` address all come from the deployment selected by the Sui client's network. Record sales have no Record Registry or Settings singleton. @@ -442,7 +494,7 @@ const { currencies } = await initializeShareCurrencies( `executeViaExecutor(executor, ...thunks)` (`execute.ts`) submits a non-idempotent PTB through a `ParallelTransactionExecutor` exactly once (no auto-retry) — it's what the batched provisioning above builds on, layered over -`@misofm/protocol`'s transport-agnostic `buildTx`/`toExecResult`. +`@misofm/musicos`'s transport-agnostic `buildTx`/`toExecResult`. ## Extensions @@ -622,22 +674,30 @@ carry an optional `RecordingRoleLevel` (`Producer`, `Vocalist`, `Engineer`, ``` src/ - deployments.ts fail-closed deployment schema and future address injection point - client.ts the full client.miso facade; protocol and Party live at client.miso.protocol / .party + deployments.ts fail-closed deployment schema (MisoPlatformDeployment, PartyExtensionsDeployment) and address injection point + client.ts the full client.miso facade; object-model core lives at client.miso.protocol, Party at client.miso.party + packages.ts MisoPlatformPackageBindings: extensions/primitives/party generated calls bound to one deployment + events.ts platformEventParsers: extension + generic-primitive event decoders + royalty.ts generic royalty-pool / stake / routed-stake derive helpers and PTB builders pressing.ts facade: builders, readers, and the id derivations - queries.ts shared read plumbing (isNotFound, re-exported from @misofm/protocol) + queries.ts shared read plumbing (isNotFound, re-exported from @misofm/musicos) transactions.ts the TxThunk contract + the opinionated publish flow (disperse/finalize/publish*) release-graph.ts whole release graph in one PTB (publishReleaseGraph) + publication.ts atomic catalog publication (publishAtomicCatalog) share.ts share-currency provisioning (createShareCurrency, batched variants) share-template.ts embedded `share` package bytecode credits.ts EXTENSION: contributor credits + the three role vocabularies cover.ts EXTENSION: release cover art (Walrus blob via ori) + genre.ts EXTENSION: release/recording genre vocabulary + release-extensions.ts EXTENSION: release kind, description, DSP links + recording-extensions.ts EXTENSION: recording advisory, language, master reference, streaming transcode + party/ Party EXTENSIONS: profile, media, roles, tags, genres, CTAs, links (PartyPlatformClient, wraps @misofm/partyos's PartyosClient) — also `@misofm/platform/party` read/ high-level catalog, artist, wallet, and receipt views vault.ts Vault authority, plugin, event, and receiving-coin builders - execute.ts executeViaExecutor, layered on @misofm/protocol's buildTx/toExecResult + execute.ts executeViaExecutor, layered on @misofm/musicos's buildTx/toExecResult internal.ts private helpers (the 0x1::option moveCall targets) — NOT exported - contracts.ts barrel re-exporting the generated bindings as `contracts` - contracts/ GENERATED — do not edit by hand + contracts.ts barrel re-exporting the curated generated bindings as `contracts` + contracts/ GENERATED — do not edit by hand; also reachable raw via `@misofm/platform/contracts/*` ``` ## Codegen @@ -649,41 +709,49 @@ the on-chain ABI: bun run codegen # reads sui-codegen.config.ts → src/contracts/ ``` -`sui-codegen.config.ts` lists `miso_record`, `miso_record_shop`, data -extensions, generic `royalty_pool`/`routed_stake`, and the `vault` plus all -vault-plugin packages. The protocol CORE (`miso` — -composition/recording/release/track) generates into -`@misofm/protocol` instead, which this package depends on for those bindings — -adding the core here to save an import is how the split this package exists to -enforce gets undone. +This package's generated tree (`src/contracts/`) covers every Move package this +side of the boundary rule — the ten `party_*` extensions plus `party_wallet`, +work extensions, generic royalty/routed-stake primitives, Record and Record +Shop, Vault, and every Action/plugin package. The Party core (`partyos`) +generates into `@misofm/partyos`, and the object-model core +(composition/recording/release/track) generates into `@misofm/musicos`; +this package depends on both for those bindings — adding either core here to +save an import is how the split these packages exist to enforce gets undone. + +The root `sui-codegen.config.ts` (one level up from this package) is the +source of truth for exactly which Move packages generate into which package's +tree, and where their sibling checkouts resolve from; read it rather than +relying on an enumeration here going stale. -Paths resolve against sibling checkouts, so regenerating requires -`~/Documents/GitHub/misofm/{sdk, record, record-shop, vault, vault-plugins}` and -`~/Documents/GitHub/misonetwork/{party-actions, protocol, protocol-actions, -protocol-extensions, royalty-pool, routed-stake, share, cover-art, genre}`. +```sh +bun run codegen # reads ../../sui-codegen.config.ts → src/contracts/ +``` -For an isolated checkout, copy those source trees and set +For an isolated checkout, copy the source trees it lists and set `MISO_SDK_CODEGEN_SOURCE_ROOT` to their common parent. The codegen config reads only from that copy, avoiding writes to a developer's live source tree. -## Dependency on `@misofm/protocol` +## Dependency on `@misofm/musicos` and `@misofm/partyos` -`@misofm/protocol` is a regular runtime dependency of this package (a workspace -dependency in this monorepo, resolved to a published version range on publish), -not a peer. This package imports its primitives, deployment configuration, -protocol client, and Party client directly, then exposes them at -`client.miso.protocol` and `client.miso.party` only after -`await client.miso.ready()` validates the exact ledger. Direct protocol reads, -generated calls, package bindings, and nested Party APIs cannot be obtained -before that gate. +`@misofm/musicos` and `@misofm/partyos` are regular runtime dependencies of +this package (workspace dependencies in this monorepo, resolved to published +version ranges on publish), not peers. This package imports their primitives +and deployment configuration directly, then exposes the object-model client +at `client.miso.protocol` only after `await client.miso.ready()` validates the +exact ledger. `client.miso.party` is a `PartyPlatformClient` +(`@misofm/platform/party`) wrapping a `PartyosClient` from `@misofm/partyos` +for the Party core, with the first-party extensions (profile, media, roles, +tags, genres, CTAs, platform links) layered on top. Direct object-model +reads, generated calls, package bindings, and Party APIs (core and +extensions alike) cannot be obtained before the readiness gate. `@mysten/sui` itself stays a peer dependency here, so an application resolves -exactly one Sui SDK across both packages regardless of which protocol version -`@misofm/platform` pins. +exactly one Sui SDK across all three packages regardless of which +object-model or Party-identity version `@misofm/platform` pins. ```bash bun add @misofm/platform ``` -`@misofm/protocol` resolves automatically as a transitive dependency; there is +`@misofm/musicos` resolves automatically as a transitive dependency; there is no separate install step and no peer version for consumers to reconcile. diff --git a/packages/platform/package.json b/packages/platform/package.json index 3ebbd6c..3aa5a78 100644 --- a/packages/platform/package.json +++ b/packages/platform/package.json @@ -1,8 +1,8 @@ { "name": "@misofm/platform", - "version": "0.24.0", + "version": "0.25.0", "packageManager": "bun@1.4.0", - "description": "Complete client SDK for the Miso platform on Sui \u2014 high-level reads, pressings, listings, records, and work publishing.", + "description": "Complete client SDK for the Miso platform on Sui — high-level reads, pressings, listings, records, work publishing, Party extensions, and the first-party extensions built on top of the @misofm/musicos object model and the @misofm/partyos Party identity model.", "license": "Apache-2.0", "repository": { "type": "git", @@ -19,7 +19,8 @@ "music", "blockchain", "sdk", - "pressing" + "pressing", + "party" ], "publishConfig": { "access": "public" @@ -113,6 +114,22 @@ "types": "./dist/vault.d.ts", "default": "./dist/vault.js" }, + "./party": { + "types": "./dist/party/index.d.ts", + "default": "./dist/party/index.js" + }, + "./packages": { + "types": "./dist/packages.d.ts", + "default": "./dist/packages.js" + }, + "./contracts": { + "types": "./dist/contracts.d.ts", + "default": "./dist/contracts.js" + }, + "./contracts/*": { + "types": "./dist/contracts/*.d.ts", + "default": "./dist/contracts/*.js" + }, "./package.json": "./package.json" }, "files": [ @@ -123,9 +140,6 @@ "package.json" ], "scripts": { - "codegen": "sui-ts-codegen generate && bun run codegen:format", - "codegen:format": "bun scripts/format-codegen.ts", - "codegen:check": "bun run codegen && git diff --exit-code -- src/contracts", "typecheck": "tsc --noEmit", "test": "bun test", "build": "rm -rf dist && tsc -p tsconfig.build.json", @@ -133,12 +147,12 @@ "prepare": "rm -rf dist && tsc -p tsconfig.build.json --noCheck" }, "dependencies": { - "@misofm/streaming": "0.1.0", - "@misofm/protocol": "0.17.0", - "@mysten/bcs": "^2.1.1" + "@misofm/streaming": "0.2.1", + "@mysten/bcs": "^2.1.1", + "@misofm/musicos": "0.2.0", + "@misofm/partyos": "0.2.0" }, "devDependencies": { - "@mysten/codegen": "^0.11.14", "@mysten/move-bytecode-template": "^0.4.0", "@mysten/sui": "2.29.0", "@types/bun": "^1.3.14", diff --git a/packages/platform/scripts/format-codegen.ts b/packages/platform/scripts/format-codegen.ts deleted file mode 100644 index 39dd423..0000000 --- a/packages/platform/scripts/format-codegen.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { readdir, readFile, writeFile } from "node:fs/promises"; -import { join } from "node:path"; - -const contractsDir = new URL("../src/contracts/", import.meta.url).pathname; -const directBcsAnchor = 'import type {} from "@mysten/bcs";'; - -async function generatedFiles(directory: string): Promise { - const entries = await readdir(directory, { withFileTypes: true }); - const files = await Promise.all( - entries.map((entry) => { - const path = join(directory, entry.name); - return entry.isDirectory() - ? generatedFiles(path) - : Promise.resolve(entry.name.endsWith(".ts") ? [path] : []); - }), - ); - return files.flat(); -} - -for (const path of await generatedFiles(contractsDir)) { - const original = await readFile(path, "utf8"); - let formatted = original - .split("\n") - .filter((line) => line !== directBcsAnchor) - .map((line) => line.replace(/[ \t]+$/, "")) - .join("\n"); - - formatted = formatted.replace( - /^(import .* from '@mysten\/sui\/bcs';)$/m, - `$1\n${directBcsAnchor}`, - ); - if (formatted !== original) await writeFile(path, formatted); -} diff --git a/packages/platform/src/catalog.ts b/packages/platform/src/catalog.ts index 60e9b6e..f41c8ae 100644 --- a/packages/platform/src/catalog.ts +++ b/packages/platform/src/catalog.ts @@ -3,7 +3,7 @@ // Composed catalog reads. // -// `@misofm/protocol` exposes one-question reads: fetch an object by id, list the +// `@misofm/musicos` exposes one-question reads: fetch an object by id, list the // caps an address owns, resolve a share type to its work. Stitching several of // those together to answer a PRODUCT question — "show me everything this artist // administers" — is orchestration, and it lives here for the same reason the @@ -19,7 +19,7 @@ import { getRecordingsByIds, getReleaseById, type Recording, -} from "@misofm/protocol"; +} from "@misofm/musicos"; import { getWorkAddressesByShareTypes } from "./read/works.ts"; import { getCompositionCreditsByIds, diff --git a/packages/platform/src/client.ts b/packages/platform/src/client.ts index 1771c12..22a45b8 100644 --- a/packages/platform/src/client.ts +++ b/packages/platform/src/client.ts @@ -38,37 +38,38 @@ import type { ParallelTransactionExecutor } from "@mysten/sui/transactions"; import { miso as protocolMiso, type MisoProtocolClient, -} from "@misofm/protocol/client"; -import type { PartyProtocolClient } from "@misofm/protocol/party"; +} from "@misofm/musicos/client"; +import { PartyosClient } from "@misofm/partyos"; +import { PartyPlatformClient } from "./party/index.ts"; -import * as recordContract from "@misofm/protocol/contracts/miso_record/record"; -import * as pressingContract from "@misofm/protocol/contracts/miso_record/pressing"; -import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing"; -import * as genreContract from "@misofm/protocol/contracts/genre/genre"; -import * as releaseDescriptionContract from "@misofm/protocol/contracts/release_description/release_description"; -import * as releaseDspLinkContract from "@misofm/protocol/contracts/release_dsp_link/release_dsp_link"; -import * as releaseGenreContract from "@misofm/protocol/contracts/release_genre/release_genre"; -import * as releaseKindContract from "@misofm/protocol/contracts/release_kind/release_kind"; -import * as releaseRevenueDistributorContract from "@misofm/protocol/contracts/release_revenue_distributor/release_revenue_distributor"; -import * as vaultContract from "@misofm/protocol/contracts/vault/vault"; -import * as compositionRoyaltyPoolContract from "@misofm/protocol/contracts/composition_royalty_pool/composition_royalty_pool"; -import * as recordingRoyaltyPoolContract from "@misofm/protocol/contracts/recording_royalty_pool/recording_royalty_pool"; -import * as partyWalletContract from "@misofm/protocol/contracts/party_wallet/party_wallet"; -import * as compositionRoutedStakeContract from "@misofm/protocol/contracts/composition_routed_stake/composition_routed_stake"; -import * as compositionRoyaltyPoolPluginContract from "@misofm/protocol/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin"; -import * as recordingRoyaltyPoolPluginContract from "@misofm/protocol/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin"; -import * as releaseRevenueDistributorPluginContract from "@misofm/protocol/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin"; -import * as routedStakeContract from "@misofm/protocol/contracts/routed_stake/routed_stake"; -import * as royaltyPoolContract from "@misofm/protocol/contracts/royalty_pool/pool"; -import * as recordingAdvisoryContract from "@misofm/protocol/contracts/recording_advisory/recording_advisory"; -import * as recordingLanguageContract from "@misofm/protocol/contracts/recording_language/recording_language"; -import * as recordingMasterReferenceContract from "@misofm/protocol/contracts/recording_master_reference/recording_master_reference"; -import * as recordingStreamingTranscodeContract from "@misofm/protocol/contracts/recording_streaming_transcode/recording_streaming_transcode"; -import * as recordingGenreContract from "@misofm/protocol/contracts/recording_genre/recording_genre"; +import * as recordContract from "./contracts/record/record.ts"; +import * as pressingContract from "./contracts/record/pressing.ts"; +import * as listingContract from "./contracts/record_shop/listing.ts"; +import * as genreContract from "./contracts/genre/genre.ts"; +import * as releaseDescriptionContract from "./contracts/release_description/release_description.ts"; +import * as releaseDspLinkContract from "./contracts/release_dsp_link/release_dsp_link.ts"; +import * as releaseGenreContract from "./contracts/release_genre/release_genre.ts"; +import * as releaseKindContract from "./contracts/release_kind/release_kind.ts"; +import * as releaseRevenueDistributorContract from "./contracts/release_revenue_distributor/release_revenue_distributor.ts"; +import * as vaultContract from "./contracts/vault/vault.ts"; +import * as compositionRoyaltyPoolContract from "./contracts/composition_royalty_pool/composition_royalty_pool.ts"; +import * as recordingRoyaltyPoolContract from "./contracts/recording_royalty_pool/recording_royalty_pool.ts"; +import * as partyWalletContract from "./contracts/party_wallet/party_wallet.ts"; +import * as compositionRoutedStakeContract from "./contracts/composition_routed_stake/composition_routed_stake.ts"; +import * as compositionRoyaltyPoolPluginContract from "./contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts"; +import * as recordingRoyaltyPoolPluginContract from "./contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts"; +import * as releaseRevenueDistributorPluginContract from "./contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts"; +import * as routedStakeContract from "./contracts/routed_stake/routed_stake.ts"; +import * as royaltyPoolContract from "./contracts/royalty_pool/pool.ts"; +import * as recordingAdvisoryContract from "./contracts/recording_advisory/recording_advisory.ts"; +import * as recordingLanguageContract from "./contracts/recording_language/recording_language.ts"; +import * as recordingMasterReferenceContract from "./contracts/recording_master_reference/recording_master_reference.ts"; +import * as recordingStreamingTranscodeContract from "./contracts/recording_streaming_transcode/recording_streaming_transcode.ts"; +import * as recordingGenreContract from "./contracts/recording_genre/recording_genre.ts"; import * as vaultActions from "./vault.ts"; -import * as coverArtContract from "@misofm/protocol/contracts/cover_art/cover_art"; -import * as releaseCoverArtContract from "@misofm/protocol/contracts/release_cover_art/release_cover_art"; -import * as releaseCreditsContract from "@misofm/protocol/contracts/release_credits/release_credits"; +import * as coverArtContract from "./contracts/cover_art/cover_art.ts"; +import * as releaseCoverArtContract from "./contracts/release_cover_art/release_cover_art.ts"; +import * as releaseCreditsContract from "./contracts/release_credits/release_credits.ts"; import { authorizeRecordShop, deriveListingId, @@ -284,7 +285,7 @@ export interface MisoPlatformConfig { * unavailable legacy state that makes every sales API fail closed. */ recordSales?: RecordSalesDeployment; /** - * The `@misofm/protocol` protocol package (miso core). Required for the + * The `@misofm/musicos` protocol package (miso core). Required for the * publish builders (`publishComposition`, `publishRecording`, * `publishCompositionAndRecording`) — optional if this client only ever * sells pressed records. @@ -404,6 +405,7 @@ export class MisoPlatformClient { readonly #client: ClientWithCoreApi; readonly #config: MisoPlatformConfig; readonly #protocol?: MisoProtocolClient; + readonly #party?: PartyPlatformClient; readonly #chainIdentifier?: string; /** Bundled/custom deployment selected for the full facade, when available. */ readonly deployment?: MisoPlatformDeployment; @@ -437,6 +439,16 @@ export class MisoPlatformClient { }).register(client) : undefined); this.deployment = deploymentSnapshot; + // The one place `PartyPlatformClient` is constructed: from this platform + // deployment's `partyos` (core) and `party` (extensions) sections, not + // from the object-model protocol client. + this.#party = deploymentSnapshot + ? new PartyPlatformClient( + client, + new PartyosClient(client, deploymentSnapshot.partyos), + deploymentSnapshot.party, + ) + : undefined; this.#chainIdentifier = deploymentSnapshot?.chainIdentifier ?? configSnapshot.chainIdentifier; } @@ -516,14 +528,14 @@ export class MisoPlatformClient { } /** Party identity and profile APIs, backed by the network SDK. */ - get party(): PartyProtocolClient { - if (!this.#protocol) { + get party(): PartyPlatformClient { + if (!this.#party) { throw new Error( "misoPlatform: Party APIs require the complete platform deployment. Use miso({ deployment }).", ); } this.#requireReady("party APIs"); - return this.#protocol.party; + return this.#party; } #recordSales() { @@ -1302,7 +1314,7 @@ export function miso( client, { recordSales: deployment.recordSales, - misoPackageId: deployment.protocol.miso, + misoPackageId: deployment.protocol.musicos, minatoPackageId: deployment.packages.minato, releaseRegistryId: deployment.objects.releaseRegistry, releaseKindPackageId: deployment.packages.releaseKind, diff --git a/packages/platform/src/contracts.ts b/packages/platform/src/contracts.ts index aa7514d..a1909ac 100644 --- a/packages/platform/src/contracts.ts +++ b/packages/platform/src/contracts.ts @@ -4,52 +4,96 @@ // Barrel for the codegen-generated, ABI-bound bindings (BCS structs + type-safe // Move calls). Re-exported from the package root as the `contracts` namespace. // -// PLATFORM + EXTENSION packages. The protocol core (`miso`: Composition, -// Recording, Release, Track) generates into `@misofm/protocol` and is -// re-exported from ITS `contracts` namespace — import it from there rather than -// mirroring it here. +// The object-model core (`musicos`: Composition, Recording, Release, Track) +// generates into `@misofm/musicos` and is re-exported from ITS `contracts` +// namespace — import it from there rather than mirroring it here. Everything +// else Miso offers on top of the object model — extensions, generic royalty +// primitives, the Party extensions (the Party core is `@misofm/partyos`), +// Record/Record Shop — lives in this barrel. + +// Every package here is generated from the current first-party Move source. The +// generated default `@local-pkg/*` identities are source labels only: callers +// must inject exact published addresses through a complete deployment before +// building a transaction. Do not use a historic package ID as a substitute for +// a fresh deployment. + +type PublicModule = Omit; +function withoutUnsafeCalls( + module: M, + keys: K, +): PublicModule { + return Object.fromEntries(Object.entries(module).filter(([key]) => !keys.includes(key as keyof M))) as PublicModule; +} + +import * as rawCompositionCredits from "./contracts/composition_credits/composition_credits.ts"; +import * as rawRecordingCredits from "./contracts/recording_credits/recording_credits.ts"; +import * as rawRecordingMasterReference from "./contracts/recording_master_reference/recording_master_reference.ts"; +import * as rawReleaseCoverArt from "./contracts/release_cover_art/release_cover_art.ts"; +import * as rawReleaseCredits from "./contracts/release_credits/release_credits.ts"; +import * as rawReleaseDescription from "./contracts/release_description/release_description.ts"; +import * as rawRoyaltyPool from "./contracts/royalty_pool/pool.ts"; +import * as rawRoyaltyStake from "./contracts/royalty_pool/stake.ts"; +import * as rawRoutedStake from "./contracts/routed_stake/routed_stake.ts"; +import * as rawPartyProfile from "./contracts/party_profile/party_profile.ts"; // Record identity/issuance and Record Shop primary-sale mechanics. The // package-restricted witness constructor is intentionally not exported here. -export * as record from "@misofm/protocol/contracts/miso_record/record"; -export * as pressing from "@misofm/protocol/contracts/miso_record/pressing"; -export * as listing from "@misofm/protocol/contracts/miso_record_shop/listing"; - -// Generic royalty-pool primitive, raw-cap Actions, and the three safe crank plugins. -export * as royaltyPool from "@misofm/protocol/contracts/royalty_pool/pool"; -export * as royaltyPoolStake from "@misofm/protocol/contracts/royalty_pool/stake"; -export * as vault from "@misofm/protocol/contracts/vault/vault"; -export * as compositionRoyaltyPool from "@misofm/protocol/contracts/composition_royalty_pool/composition_royalty_pool"; -export * as recordingRoyaltyPool from "@misofm/protocol/contracts/recording_royalty_pool/recording_royalty_pool"; -export * as partyWallet from "@misofm/protocol/contracts/party_wallet/party_wallet"; -export * as routedStake from "@misofm/protocol/contracts/routed_stake/routed_stake"; -export * as compositionRoutedStake from "@misofm/protocol/contracts/composition_routed_stake/composition_routed_stake"; -export * as compositionRoyaltyPoolPlugin from "@misofm/protocol/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin"; -export * as recordingRoyaltyPoolPlugin from "@misofm/protocol/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin"; -export * as releaseRevenueDistributorPlugin from "@misofm/protocol/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin"; +export * as record from "./contracts/record/record.ts"; +export * as pressing from "./contracts/record/pressing.ts"; +export * as listing from "./contracts/record_shop/listing.ts"; + +// Generic royalty-pool primitive, raw-cap Actions, and the three safe crank +// plugins (BCS codecs remain available; PTB-inaccessible references do not). +export const royaltyPool = withoutUnsafeCalls(rawRoyaltyPool, ["balance", "_new"] as const); +export const royaltyPoolStake = withoutUnsafeCalls(rawRoyaltyStake, ["balance", "getRegistration"] as const); +export * as vault from "./contracts/vault/vault.ts"; +export * as compositionRoyaltyPool from "./contracts/composition_royalty_pool/composition_royalty_pool.ts"; +export * as recordingRoyaltyPool from "./contracts/recording_royalty_pool/recording_royalty_pool.ts"; +export * as partyWallet from "./contracts/party_wallet/party_wallet.ts"; +export const routedStake = withoutUnsafeCalls( + rawRoutedStake, + ["stake", "_new", "register", "unregister", "unstake", "restake"] as const, +); +export * as compositionRoutedStake from "./contracts/composition_routed_stake/composition_routed_stake.ts"; +export * as compositionRoyaltyPoolPlugin from "./contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts"; +export * as recordingRoyaltyPoolPlugin from "./contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts"; +export * as releaseRevenueDistributorPlugin from "./contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts"; // Cover art (the CoverArt value type + the release attachment extension). -export * as coverArt from "@misofm/protocol/contracts/cover_art/cover_art"; -export * as releaseCoverArt from "@misofm/protocol/contracts/release_cover_art/release_cover_art"; +export * as coverArt from "./contracts/cover_art/cover_art.ts"; +export const releaseCoverArt = withoutUnsafeCalls(rawReleaseCoverArt, ["cover"] as const); // Release presentation, discovery, buyer-content, and runtime economics. -export * as genre from "@misofm/protocol/contracts/genre/genre"; -export * as releaseDescription from "@misofm/protocol/contracts/release_description/release_description"; -export * as releaseDspLink from "@misofm/protocol/contracts/release_dsp_link/release_dsp_link"; -export * as releaseGenre from "@misofm/protocol/contracts/release_genre/release_genre"; -export * as releaseKind from "@misofm/protocol/contracts/release_kind/release_kind"; -export * as releaseRevenueDistributor from "@misofm/protocol/contracts/release_revenue_distributor/release_revenue_distributor"; -export * as recordingAdvisory from "@misofm/protocol/contracts/recording_advisory/recording_advisory"; -export * as recordingGenre from "@misofm/protocol/contracts/recording_genre/recording_genre"; -export * as recordingLanguage from "@misofm/protocol/contracts/recording_language/recording_language"; -export * as recordingMasterReference from "@misofm/protocol/contracts/recording_master_reference/recording_master_reference"; -export * as recordingEngineSession from "@misofm/protocol/contracts/recording_engine_session/recording_engine_session"; -export * as recordingStreamingTranscode from "@misofm/protocol/contracts/recording_streaming_transcode/recording_streaming_transcode"; +export * as genre from "./contracts/genre/genre.ts"; +export const releaseDescription = withoutUnsafeCalls(rawReleaseDescription, ["description"] as const); +export * as releaseDspLink from "./contracts/release_dsp_link/release_dsp_link.ts"; +export * as releaseGenre from "./contracts/release_genre/release_genre.ts"; +export * as releaseKind from "./contracts/release_kind/release_kind.ts"; +export * as releaseRevenueDistributor from "./contracts/release_revenue_distributor/release_revenue_distributor.ts"; +export * as recordingAdvisory from "./contracts/recording_advisory/recording_advisory.ts"; +export * as recordingGenre from "./contracts/recording_genre/recording_genre.ts"; +export * as recordingLanguage from "./contracts/recording_language/recording_language.ts"; +export const recordingMasterReference = withoutUnsafeCalls(rawRecordingMasterReference, ["masterReference"] as const); +export * as recordingEngineSession from "./contracts/recording_engine_session/recording_engine_session.ts"; +export * as recordingStreamingTranscode from "./contracts/recording_streaming_transcode/recording_streaming_transcode.ts"; // Credits extensions (per-work credit stores + their role vocabularies). -export * as compositionCredits from "@misofm/protocol/contracts/composition_credits/composition_credits"; -export * as compositionPartyRole from "@misofm/protocol/contracts/composition_credits/composition_party_role"; -export * as recordingCredits from "@misofm/protocol/contracts/recording_credits/recording_credits"; -export * as recordingPartyRole from "@misofm/protocol/contracts/recording_credits/recording_party_role"; -export * as releaseCredits from "@misofm/protocol/contracts/release_credits/release_credits"; -export * as releasePartyRole from "@misofm/protocol/contracts/release_credits/release_party_role"; +export const compositionCredits = withoutUnsafeCalls(rawCompositionCredits, ["credits"] as const); +export * as compositionPartyRole from "./contracts/composition_credits/composition_party_role.ts"; +export const recordingCredits = withoutUnsafeCalls(rawRecordingCredits, ["credits", "primaryArtistIds", "featuredArtistIds"] as const); +export * as recordingPartyRole from "./contracts/recording_credits/recording_party_role.ts"; +export const releaseCredits = withoutUnsafeCalls(rawReleaseCredits, ["credits"] as const); +export * as releasePartyRole from "./contracts/release_credits/release_party_role.ts"; + +// Party extensions. The Party core (`party`) is owned by `@misofm/partyos` — +// import it from `@misofm/partyos/contracts` instead of mirroring it here. +export * as partyCta from "./contracts/party_cta/party_cta.ts"; +export * as partyGenre from "./contracts/party_genre/party_genre.ts"; +export * as partyMedia from "./contracts/party_media/party_media.ts"; +export * as partyMusic from "./contracts/party_music/party_music.ts"; +export * as partyPlatformLink from "./contracts/party_platform_link/party_platform_link.ts"; +export * as partyProLink from "./contracts/party_pro_link/party_pro_link.ts"; +export const partyProfile = withoutUnsafeCalls(rawPartyProfile, ["profile"] as const); +export * as partyRoles from "./contracts/party_roles/party_roles.ts"; +export * as partySocial from "./contracts/party_social/party_social.ts"; +export * as partyTags from "./contracts/party_tags/party_tags.ts"; diff --git a/packages/protocol/src/contracts/composition_credits/composition_credits.ts b/packages/platform/src/contracts/composition_credits/composition_credits.ts similarity index 97% rename from packages/protocol/src/contracts/composition_credits/composition_credits.ts rename to packages/platform/src/contracts/composition_credits/composition_credits.ts index ab114b7..1a26f2e 100644 --- a/packages/protocol/src/contracts/composition_credits/composition_credits.ts +++ b/packages/platform/src/contracts/composition_credits/composition_credits.ts @@ -13,7 +13,7 @@ * and credits survive into any lifecycle state (they may be attached before or * after the composition is published). * - * This is Miso's canonical credits standard; because it is an extension, other + * This is musicos's canonical credits standard; because it is an extension, other * parties may publish their own composition-credits standard against the same * `Composition`. Credits are NOT read by the economics — they are attribution. */ @@ -23,7 +23,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as vec_map from './deps/sui/vec_map.ts'; -import * as credit from './deps/miso_credit/credit.ts'; +import * as credit from './deps/credit/credit.ts'; import * as composition_party_role from './composition_party_role.ts'; const $moduleName = '@local-pkg/composition_credits::composition_credits'; export const ExtensionKey = new MoveTuple({ name: `${$moduleName}::ExtensionKey`, fields: [bcs.bool()] }); diff --git a/packages/protocol/src/contracts/composition_credits/composition_party_role.ts b/packages/platform/src/contracts/composition_credits/composition_party_role.ts similarity index 98% rename from packages/protocol/src/contracts/composition_credits/composition_party_role.ts rename to packages/platform/src/contracts/composition_credits/composition_party_role.ts index 6d96e0e..392975e 100644 --- a/packages/protocol/src/contracts/composition_credits/composition_party_role.ts +++ b/packages/platform/src/contracts/composition_credits/composition_party_role.ts @@ -23,7 +23,7 @@ * token (e.g. `"Songwriter"`). For `Custom`, `name()` returns the user-supplied * string. * - * These identifiers are Miso's own canonical vocabulary; any overlap with an + * These identifiers are musicos's own canonical vocabulary; any overlap with an * external standard (e.g. DDEX) is coincidental, not a reproduction of it. */ diff --git a/packages/protocol/src/contracts/composition_credits/deps/miso_credit/credit.ts b/packages/platform/src/contracts/composition_credits/deps/credit/credit.ts similarity index 96% rename from packages/protocol/src/contracts/composition_credits/deps/miso_credit/credit.ts rename to packages/platform/src/contracts/composition_credits/deps/credit/credit.ts index eecbff9..209d893 100644 --- a/packages/protocol/src/contracts/composition_credits/deps/miso_credit/credit.ts +++ b/packages/platform/src/contracts/composition_credits/deps/credit/credit.ts @@ -11,7 +11,7 @@ import { type BcsType, bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { MoveStruct } from '../../../utils/index.ts'; -const $moduleName = 'miso_credit::credit'; +const $moduleName = 'credit::credit'; /** * A credit attributing roles to a party on a work. Generic over the role type to * support domain-specific roles. diff --git a/packages/protocol/src/contracts/composition_credits/deps/sui/vec_map.ts b/packages/platform/src/contracts/composition_credits/deps/sui/vec_map.ts similarity index 100% rename from packages/protocol/src/contracts/composition_credits/deps/sui/vec_map.ts rename to packages/platform/src/contracts/composition_credits/deps/sui/vec_map.ts diff --git a/packages/protocol/src/contracts/composition_routed_stake/composition_routed_stake.ts b/packages/platform/src/contracts/composition_routed_stake/composition_routed_stake.ts similarity index 100% rename from packages/protocol/src/contracts/composition_routed_stake/composition_routed_stake.ts rename to packages/platform/src/contracts/composition_routed_stake/composition_routed_stake.ts diff --git a/packages/protocol/src/contracts/composition_royalty_pool/composition_royalty_pool.ts b/packages/platform/src/contracts/composition_royalty_pool/composition_royalty_pool.ts similarity index 98% rename from packages/protocol/src/contracts/composition_royalty_pool/composition_royalty_pool.ts rename to packages/platform/src/contracts/composition_royalty_pool/composition_royalty_pool.ts index 467b708..9594e44 100644 --- a/packages/protocol/src/contracts/composition_royalty_pool/composition_royalty_pool.ts +++ b/packages/platform/src/contracts/composition_royalty_pool/composition_royalty_pool.ts @@ -4,7 +4,7 @@ /** - * Raw-cap, custody-agnostic royalty-pool actions for Miso Compositions. + * Raw-cap, custody-agnostic royalty-pool actions for musicos Compositions. * * Every mutating action requires the Composition's own admin capability. The * canonical pool remains derived from the Composition, and callers decide when to diff --git a/packages/protocol/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts b/packages/platform/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts similarity index 100% rename from packages/protocol/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts rename to packages/platform/src/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts diff --git a/packages/protocol/src/contracts/composition_royalty_pool_plugin/witness.ts b/packages/platform/src/contracts/composition_royalty_pool_plugin/witness.ts similarity index 100% rename from packages/protocol/src/contracts/composition_royalty_pool_plugin/witness.ts rename to packages/platform/src/contracts/composition_royalty_pool_plugin/witness.ts diff --git a/packages/protocol/src/contracts/cover_art/cover_art.ts b/packages/platform/src/contracts/cover_art/cover_art.ts similarity index 100% rename from packages/protocol/src/contracts/cover_art/cover_art.ts rename to packages/platform/src/contracts/cover_art/cover_art.ts diff --git a/packages/protocol/src/contracts/cover_art/deps/ori/confidentiality.ts b/packages/platform/src/contracts/cover_art/deps/ori/confidentiality.ts similarity index 100% rename from packages/protocol/src/contracts/cover_art/deps/ori/confidentiality.ts rename to packages/platform/src/contracts/cover_art/deps/ori/confidentiality.ts diff --git a/packages/protocol/src/contracts/cover_art/deps/ori/data.ts b/packages/platform/src/contracts/cover_art/deps/ori/data.ts similarity index 100% rename from packages/protocol/src/contracts/cover_art/deps/ori/data.ts rename to packages/platform/src/contracts/cover_art/deps/ori/data.ts diff --git a/packages/protocol/src/contracts/genre/genre.ts b/packages/platform/src/contracts/genre/genre.ts similarity index 100% rename from packages/protocol/src/contracts/genre/genre.ts rename to packages/platform/src/contracts/genre/genre.ts diff --git a/packages/protocol/src/contracts/party_cta/party_cta.ts b/packages/platform/src/contracts/party_cta/party_cta.ts similarity index 100% rename from packages/protocol/src/contracts/party_cta/party_cta.ts rename to packages/platform/src/contracts/party_cta/party_cta.ts diff --git a/packages/protocol/src/contracts/party_genre/party_genre.ts b/packages/platform/src/contracts/party_genre/party_genre.ts similarity index 100% rename from packages/protocol/src/contracts/party_genre/party_genre.ts rename to packages/platform/src/contracts/party_genre/party_genre.ts diff --git a/packages/protocol/src/contracts/party_media/party_media.ts b/packages/platform/src/contracts/party_media/party_media.ts similarity index 100% rename from packages/protocol/src/contracts/party_media/party_media.ts rename to packages/platform/src/contracts/party_media/party_media.ts diff --git a/packages/protocol/src/contracts/party_music/party_music.ts b/packages/platform/src/contracts/party_music/party_music.ts similarity index 100% rename from packages/protocol/src/contracts/party_music/party_music.ts rename to packages/platform/src/contracts/party_music/party_music.ts diff --git a/packages/protocol/src/contracts/party_platform_link/party_platform_link.ts b/packages/platform/src/contracts/party_platform_link/party_platform_link.ts similarity index 100% rename from packages/protocol/src/contracts/party_platform_link/party_platform_link.ts rename to packages/platform/src/contracts/party_platform_link/party_platform_link.ts diff --git a/packages/protocol/src/contracts/party_pro_link/party_pro_link.ts b/packages/platform/src/contracts/party_pro_link/party_pro_link.ts similarity index 100% rename from packages/protocol/src/contracts/party_pro_link/party_pro_link.ts rename to packages/platform/src/contracts/party_pro_link/party_pro_link.ts diff --git a/packages/protocol/src/contracts/party_profile/deps/country_code/country_code.ts b/packages/platform/src/contracts/party_profile/deps/country_code/country_code.ts similarity index 100% rename from packages/protocol/src/contracts/party_profile/deps/country_code/country_code.ts rename to packages/platform/src/contracts/party_profile/deps/country_code/country_code.ts diff --git a/packages/protocol/src/contracts/party_profile/deps/language_code/language_code.ts b/packages/platform/src/contracts/party_profile/deps/language_code/language_code.ts similarity index 100% rename from packages/protocol/src/contracts/party_profile/deps/language_code/language_code.ts rename to packages/platform/src/contracts/party_profile/deps/language_code/language_code.ts diff --git a/packages/protocol/src/contracts/party_profile/party_profile.ts b/packages/platform/src/contracts/party_profile/party_profile.ts similarity index 100% rename from packages/protocol/src/contracts/party_profile/party_profile.ts rename to packages/platform/src/contracts/party_profile/party_profile.ts diff --git a/packages/protocol/src/contracts/party_roles/party_roles.ts b/packages/platform/src/contracts/party_roles/party_roles.ts similarity index 100% rename from packages/protocol/src/contracts/party_roles/party_roles.ts rename to packages/platform/src/contracts/party_roles/party_roles.ts diff --git a/packages/protocol/src/contracts/party_social/party_social.ts b/packages/platform/src/contracts/party_social/party_social.ts similarity index 100% rename from packages/protocol/src/contracts/party_social/party_social.ts rename to packages/platform/src/contracts/party_social/party_social.ts diff --git a/packages/protocol/src/contracts/party_tags/party_tags.ts b/packages/platform/src/contracts/party_tags/party_tags.ts similarity index 100% rename from packages/protocol/src/contracts/party_tags/party_tags.ts rename to packages/platform/src/contracts/party_tags/party_tags.ts diff --git a/packages/protocol/src/contracts/party_wallet/party_wallet.ts b/packages/platform/src/contracts/party_wallet/party_wallet.ts similarity index 100% rename from packages/protocol/src/contracts/party_wallet/party_wallet.ts rename to packages/platform/src/contracts/party_wallet/party_wallet.ts diff --git a/packages/protocol/src/contracts/miso_record/deps/std/type_name.ts b/packages/platform/src/contracts/record/deps/std/type_name.ts similarity index 100% rename from packages/protocol/src/contracts/miso_record/deps/std/type_name.ts rename to packages/platform/src/contracts/record/deps/std/type_name.ts diff --git a/packages/protocol/src/contracts/miso_record/deps/sui/vec_set.ts b/packages/platform/src/contracts/record/deps/sui/vec_set.ts similarity index 100% rename from packages/protocol/src/contracts/miso_record/deps/sui/vec_set.ts rename to packages/platform/src/contracts/record/deps/sui/vec_set.ts diff --git a/packages/protocol/src/contracts/miso_record/pressing.ts b/packages/platform/src/contracts/record/pressing.ts similarity index 93% rename from packages/protocol/src/contracts/miso_record/pressing.ts rename to packages/platform/src/contracts/record/pressing.ts index 77c4e4c..5608861 100644 --- a/packages/protocol/src/contracts/miso_record/pressing.ts +++ b/packages/platform/src/contracts/record/pressing.ts @@ -19,7 +19,7 @@ import type {} from "@mysten/bcs"; import { type Transaction } from '@mysten/sui/transactions'; import * as vec_set from './deps/sui/vec_set.ts'; import * as type_name from './deps/std/type_name.ts'; -const $moduleName = '@local-pkg/miso_record::pressing'; +const $moduleName = '@local-pkg/record::pressing'; export const PressingKey = new MoveTuple({ name: `${$moduleName}::PressingKey`, fields: [bcs.u16()] }); export const PressingAdminCapKey = new MoveTuple({ name: `${$moduleName}::PressingAdminCapKey`, fields: [bcs.bool()] }); export const Pressing = new MoveStruct({ name: `${$moduleName}::Pressing`, fields: { @@ -110,7 +110,7 @@ export interface NewOptions { * capability. */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null, null, @@ -136,7 +136,7 @@ export interface ShareOptions { } /** Share a newly created Pressing after configuring its distributors. */ export function share(options: ShareOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -167,7 +167,7 @@ export interface AuthorizeDistributorOptions { * an existing distributor is a no-op. */ export function authorizeDistributor(options: AuthorizeDistributorOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null, null @@ -200,7 +200,7 @@ export interface RevokeDistributorOptions { * missing distributor is a no-op. */ export function revokeDistributor(options: RevokeDistributorOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null, null @@ -239,7 +239,7 @@ export interface MintOptions> { * wrap, freeze, or otherwise deliver it. */ export function mint>(options: MintOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null, `${options.typeArguments[0]}`, @@ -266,7 +266,7 @@ export interface UidOptions { } /** Borrow the Pressing UID for read-only extensions. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -294,7 +294,7 @@ export interface UidMutOptions { * distributor-owned objects derived from this edition. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null, null @@ -318,7 +318,7 @@ export interface ReleaseIdOptions { } /** Return the release represented by this Pressing. */ export function releaseId(options: ReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -341,7 +341,7 @@ export interface EditionOptions { } /** Return this Pressing's edition number. */ export function edition(options: EditionOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -364,7 +364,7 @@ export interface SupplyOptions { } /** Return the number of Records issued by this Pressing. */ export function supply(options: SupplyOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -387,7 +387,7 @@ export interface MaxSupplyOptions { } /** Return the immutable supply ceiling, if one exists. */ export function maxSupply(options: MaxSupplyOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -410,7 +410,7 @@ export interface DistributorsOptions { } /** Borrow the defining types of all currently authorized distributors. */ export function distributors(options: DistributorsOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -436,7 +436,7 @@ export interface IsDistributorAuthorizedOptions { } /** Return whether `Distributor` is currently authorized to mint. */ export function isDistributorAuthorized(options: IsDistributorAuthorizedOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -460,7 +460,7 @@ export interface PressingIdOptions { } /** Return the Pressing controlled by this capability. */ export function pressingId(options: PressingIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -485,7 +485,7 @@ export interface DeriveAddressOptions { } /** Derive an edition's Pressing address from its Release ID. */ export function deriveAddress(options: DeriveAddressOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ '0x2::object::ID', 'u16' @@ -509,7 +509,7 @@ export interface DeriveAdminCapAddressOptions { } /** Derive a Pressing's admin capability address. */ export function deriveAdminCapAddress(options: DeriveAdminCapAddressOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ '0x2::object::ID' ] satisfies (string | null)[]; diff --git a/packages/protocol/src/contracts/miso_record/record.ts b/packages/platform/src/contracts/record/record.ts similarity index 92% rename from packages/protocol/src/contracts/miso_record/record.ts rename to packages/platform/src/contracts/record/record.ts index 19cba81..9b20dde 100644 --- a/packages/protocol/src/contracts/miso_record/record.ts +++ b/packages/platform/src/contracts/record/record.ts @@ -16,7 +16,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction } from '@mysten/sui/transactions'; import * as type_name from './deps/std/type_name.ts'; -const $moduleName = '@local-pkg/miso_record::record'; +const $moduleName = '@local-pkg/record::record'; export const Record = new MoveStruct({ name: `${$moduleName}::Record`, fields: { id: bcs.Address, /** The release this Record is a copy of. */ @@ -69,7 +69,7 @@ export interface DestroyOptions { * inaccessible. */ export function destroy(options: DestroyOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -92,7 +92,7 @@ export interface UidOptions { } /** Borrow the Record UID for read-only extensions. */ export function uid(options: UidOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -115,7 +115,7 @@ export interface UidMutOptions { } /** Mutably borrow the Record UID for owner-authorized extensions. */ export function uidMut(options: UidMutOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -138,7 +138,7 @@ export interface ReleaseIdOptions { } /** Return the release represented by this Record. */ export function releaseId(options: ReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -161,7 +161,7 @@ export interface PressingIdOptions { } /** Return the Pressing that issued this Record. */ export function pressingId(options: PressingIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -184,7 +184,7 @@ export interface EditionOptions { } /** Return this Record's edition number. */ export function edition(options: EditionOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -207,7 +207,7 @@ export interface NumberOptions { } /** Return this Record's number within its edition. */ export function number(options: NumberOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -230,7 +230,7 @@ export interface PurchaseCurrencyOptions { } /** Return the defining type of the purchase currency. */ export function purchaseCurrency(options: PurchaseCurrencyOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -253,7 +253,7 @@ export interface PurchasePriceOptions { } /** Return the amount paid for this Record. */ export function purchasePrice(options: PurchasePriceOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -276,7 +276,7 @@ export interface PurchasedByOptions { } /** Return the transaction sender who purchased this Record. */ export function purchasedBy(options: PurchasedByOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -299,7 +299,7 @@ export interface PurchasedTimestampMsOptions { } /** Return the purchase time in Unix milliseconds from Sui's Clock. */ export function purchasedTimestampMs(options: PurchasedTimestampMsOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -324,7 +324,7 @@ export interface DeriveAddressOptions { } /** Derive the Record address for `number` in `pressing_id`'s edition. */ export function deriveAddress(options: DeriveAddressOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record'; + const packageAddress = options.package ?? '@local-pkg/record'; const argumentsTypes = [ '0x2::object::ID', 'u32' diff --git a/packages/protocol/src/contracts/miso_record_shop/deps/std/type_name.ts b/packages/platform/src/contracts/record_shop/deps/std/type_name.ts similarity index 100% rename from packages/protocol/src/contracts/miso_record_shop/deps/std/type_name.ts rename to packages/platform/src/contracts/record_shop/deps/std/type_name.ts diff --git a/packages/protocol/src/contracts/miso_record_shop/listing.ts b/packages/platform/src/contracts/record_shop/listing.ts similarity index 92% rename from packages/protocol/src/contracts/miso_record_shop/listing.ts rename to packages/platform/src/contracts/record_shop/listing.ts index da87344..65f33f0 100644 --- a/packages/protocol/src/contracts/miso_record_shop/listing.ts +++ b/packages/platform/src/contracts/record_shop/listing.ts @@ -16,7 +16,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as type_name from './deps/std/type_name.ts'; -const $moduleName = '@local-pkg/miso_record_shop::listing'; +const $moduleName = '@local-pkg/record_shop::listing'; export const ListingKey = new MoveTuple({ name: `${$moduleName}::ListingKey`, fields: [bcs.bool()] }); /** Payment rule for a Listing. */ export const Pricing = new MoveEnum({ name: `${$moduleName}::Pricing`, fields: { @@ -101,7 +101,7 @@ export interface FixedOptions { } /** Construct an exact-payment pricing rule. */ export function fixed(options: FixedOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ 'u64' ] satisfies (string | null)[]; @@ -124,7 +124,7 @@ export interface FloorOptions { } /** Construct a minimum-payment pricing rule. */ export function floor(options: FloorOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ 'u64' ] satisfies (string | null)[]; @@ -143,7 +143,7 @@ export interface EnabledOptions { } /** Construct the state that accepts purchases. */ export function enabled(options: EnabledOptions = {}) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; return (tx: Transaction) => tx.moveCall({ package: packageAddress, module: 'listing', @@ -157,7 +157,7 @@ export interface DisabledOptions { } /** Construct the state that rejects purchases. */ export function disabled(options: DisabledOptions = {}) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; return (tx: Transaction) => tx.moveCall({ package: packageAddress, module: 'listing', @@ -187,7 +187,7 @@ export interface NewOptions { * further configuration before calling `share`. */ export function _new(options: NewOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null, null, @@ -216,7 +216,7 @@ export interface ShareOptions { } /** Share a newly created Listing. */ export function share(options: ShareOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -247,7 +247,7 @@ export interface SetPriceOptions { } /** Change the payment rule using the capability for the bound Pressing. */ export function setPrice(options: SetPriceOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null, null, @@ -280,7 +280,7 @@ export interface SetStateOptions { } /** Enable or disable purchases using the capability for the bound Pressing. */ export function setState(options: SetStateOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null, null, @@ -322,7 +322,7 @@ export interface PurchaseOptions { * transfer or compose the returned Record. */ export function purchase(options: PurchaseOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null, null, @@ -353,7 +353,7 @@ export interface ReleaseIdOptions { } /** Return the release that receives Listing payments. */ export function releaseId(options: ReleaseIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -380,7 +380,7 @@ export interface PressingIdOptions { } /** Return the Pressing sold by this Listing. */ export function pressingId(options: PressingIdOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -407,7 +407,7 @@ export interface PricingOptions { } /** Return this Listing's payment rule. */ export function pricing(options: PricingOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -434,7 +434,7 @@ export interface PriceOptions { } /** Return the amount configured by this Listing's payment rule. */ export function price(options: PriceOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -461,7 +461,7 @@ export interface StateOptions { } /** Return this Listing's current state. */ export function state(options: StateOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -488,7 +488,7 @@ export interface IsEnabledOptions { } /** Return whether this Listing currently accepts purchases. */ export function isEnabled(options: IsEnabledOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -515,7 +515,7 @@ export interface IsDisabledOptions { } /** Return whether this Listing currently rejects purchases. */ export function isDisabled(options: IsDisabledOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -539,7 +539,7 @@ export interface IsFixedOptions { } /** Return whether `pricing` requires an exact payment. */ export function isFixed(options: IsFixedOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -562,7 +562,7 @@ export interface IsFloorOptions { } /** Return whether `pricing` permits payment above its configured floor. */ export function isFloor(options: IsFloorOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ null ] satisfies (string | null)[]; @@ -588,7 +588,7 @@ export interface DeriveAddressOptions { } /** Derive the one Listing address for `(pressing_id, Currency)`. */ export function deriveAddress(options: DeriveAddressOptions) { - const packageAddress = options.package ?? '@local-pkg/miso_record_shop'; + const packageAddress = options.package ?? '@local-pkg/record_shop'; const argumentsTypes = [ '0x2::object::ID' ] satisfies (string | null)[]; diff --git a/packages/protocol/src/contracts/miso_record_shop/witness.ts b/packages/platform/src/contracts/record_shop/witness.ts similarity index 89% rename from packages/protocol/src/contracts/miso_record_shop/witness.ts rename to packages/platform/src/contracts/record_shop/witness.ts index d37f616..5df5bc4 100644 --- a/packages/protocol/src/contracts/miso_record_shop/witness.ts +++ b/packages/platform/src/contracts/record_shop/witness.ts @@ -8,5 +8,5 @@ import { MoveTuple } from '../utils/index.ts'; import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; -const $moduleName = '@local-pkg/miso_record_shop::witness'; +const $moduleName = '@local-pkg/record_shop::witness'; export const Witness = new MoveTuple({ name: `${$moduleName}::Witness`, fields: [bcs.bool()] }); \ No newline at end of file diff --git a/packages/protocol/src/contracts/recording_advisory/recording_advisory.ts b/packages/platform/src/contracts/recording_advisory/recording_advisory.ts similarity index 100% rename from packages/protocol/src/contracts/recording_advisory/recording_advisory.ts rename to packages/platform/src/contracts/recording_advisory/recording_advisory.ts diff --git a/packages/protocol/src/contracts/release_credits/deps/miso_credit/credit.ts b/packages/platform/src/contracts/recording_credits/deps/credit/credit.ts similarity index 96% rename from packages/protocol/src/contracts/release_credits/deps/miso_credit/credit.ts rename to packages/platform/src/contracts/recording_credits/deps/credit/credit.ts index eecbff9..209d893 100644 --- a/packages/protocol/src/contracts/release_credits/deps/miso_credit/credit.ts +++ b/packages/platform/src/contracts/recording_credits/deps/credit/credit.ts @@ -11,7 +11,7 @@ import { type BcsType, bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { MoveStruct } from '../../../utils/index.ts'; -const $moduleName = 'miso_credit::credit'; +const $moduleName = 'credit::credit'; /** * A credit attributing roles to a party on a work. Generic over the role type to * support domain-specific roles. diff --git a/packages/protocol/src/contracts/recording_credits/deps/sui/vec_map.ts b/packages/platform/src/contracts/recording_credits/deps/sui/vec_map.ts similarity index 100% rename from packages/protocol/src/contracts/recording_credits/deps/sui/vec_map.ts rename to packages/platform/src/contracts/recording_credits/deps/sui/vec_map.ts diff --git a/packages/protocol/src/contracts/recording_credits/deps/sui/vec_set.ts b/packages/platform/src/contracts/recording_credits/deps/sui/vec_set.ts similarity index 100% rename from packages/protocol/src/contracts/recording_credits/deps/sui/vec_set.ts rename to packages/platform/src/contracts/recording_credits/deps/sui/vec_set.ts diff --git a/packages/protocol/src/contracts/recording_credits/recording_credits.ts b/packages/platform/src/contracts/recording_credits/recording_credits.ts similarity index 99% rename from packages/protocol/src/contracts/recording_credits/recording_credits.ts rename to packages/platform/src/contracts/recording_credits/recording_credits.ts index d29737e..6fcf40a 100644 --- a/packages/protocol/src/contracts/recording_credits/recording_credits.ts +++ b/packages/platform/src/contracts/recording_credits/recording_credits.ts @@ -13,7 +13,7 @@ * recording's admin and credits survive into any lifecycle state (they may be * attached before or after the recording is published). * - * This is Miso's canonical credits standard; because it is an extension, other + * This is musicos's canonical credits standard; because it is an extension, other * parties may publish their own recording-credits standard against the same * `Recording`. Credits are NOT read by the economics — they are attribution. */ @@ -23,7 +23,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as vec_map from './deps/sui/vec_map.ts'; -import * as credit from './deps/miso_credit/credit.ts'; +import * as credit from './deps/credit/credit.ts'; import * as recording_party_role from './recording_party_role.ts'; import * as vec_set from './deps/sui/vec_set.ts'; const $moduleName = '@local-pkg/recording_credits::recording_credits'; diff --git a/packages/protocol/src/contracts/recording_credits/recording_party_role.ts b/packages/platform/src/contracts/recording_credits/recording_party_role.ts similarity index 99% rename from packages/protocol/src/contracts/recording_credits/recording_party_role.ts rename to packages/platform/src/contracts/recording_credits/recording_party_role.ts index d8bcc75..896886c 100644 --- a/packages/protocol/src/contracts/recording_credits/recording_party_role.ts +++ b/packages/platform/src/contracts/recording_credits/recording_party_role.ts @@ -28,7 +28,7 @@ * the user-supplied string; for `Instrumentalist`, it returns * `"Instrumentalist"` (the instrument itself is read off-chain). * - * These identifiers are Miso's own canonical vocabulary; any overlap with an + * These identifiers are musicos's own canonical vocabulary; any overlap with an * external standard (e.g. DDEX) is coincidental, not a reproduction of it. */ diff --git a/packages/protocol/src/contracts/recording_engine_session/deps/ori/confidentiality.ts b/packages/platform/src/contracts/recording_engine_session/deps/ori/confidentiality.ts similarity index 100% rename from packages/protocol/src/contracts/recording_engine_session/deps/ori/confidentiality.ts rename to packages/platform/src/contracts/recording_engine_session/deps/ori/confidentiality.ts diff --git a/packages/protocol/src/contracts/recording_engine_session/deps/ori/data.ts b/packages/platform/src/contracts/recording_engine_session/deps/ori/data.ts similarity index 100% rename from packages/protocol/src/contracts/recording_engine_session/deps/ori/data.ts rename to packages/platform/src/contracts/recording_engine_session/deps/ori/data.ts diff --git a/packages/protocol/src/contracts/recording_engine_session/recording_engine_session.ts b/packages/platform/src/contracts/recording_engine_session/recording_engine_session.ts similarity index 100% rename from packages/protocol/src/contracts/recording_engine_session/recording_engine_session.ts rename to packages/platform/src/contracts/recording_engine_session/recording_engine_session.ts diff --git a/packages/protocol/src/contracts/recording_genre/recording_genre.ts b/packages/platform/src/contracts/recording_genre/recording_genre.ts similarity index 99% rename from packages/protocol/src/contracts/recording_genre/recording_genre.ts rename to packages/platform/src/contracts/recording_genre/recording_genre.ts index f530d67..a3b438f 100644 --- a/packages/protocol/src/contracts/recording_genre/recording_genre.ts +++ b/packages/platform/src/contracts/recording_genre/recording_genre.ts @@ -15,7 +15,7 @@ * ignores it, and replacing it never disturbs anything else attached to the * recording. * - * **Why the recording, and not the release.** `miso::track`'s module doc is + * **Why the recording, and not the release.** `musicos::track`'s module doc is * explicit that a `Track` embeds only facts "genuinely release-specific and not * derivable from the recording" — title and cover art were excluded for exactly * this reason. Genre is not release-specific: it is a fact about the audio itself, diff --git a/packages/protocol/src/contracts/recording_language/deps/language_code/language_code.ts b/packages/platform/src/contracts/recording_language/deps/language_code/language_code.ts similarity index 100% rename from packages/protocol/src/contracts/recording_language/deps/language_code/language_code.ts rename to packages/platform/src/contracts/recording_language/deps/language_code/language_code.ts diff --git a/packages/protocol/src/contracts/recording_language/recording_language.ts b/packages/platform/src/contracts/recording_language/recording_language.ts similarity index 100% rename from packages/protocol/src/contracts/recording_language/recording_language.ts rename to packages/platform/src/contracts/recording_language/recording_language.ts diff --git a/packages/protocol/src/contracts/recording_master_reference/deps/ori/confidentiality.ts b/packages/platform/src/contracts/recording_master_reference/deps/ori/confidentiality.ts similarity index 100% rename from packages/protocol/src/contracts/recording_master_reference/deps/ori/confidentiality.ts rename to packages/platform/src/contracts/recording_master_reference/deps/ori/confidentiality.ts diff --git a/packages/protocol/src/contracts/recording_master_reference/deps/ori/data.ts b/packages/platform/src/contracts/recording_master_reference/deps/ori/data.ts similarity index 100% rename from packages/protocol/src/contracts/recording_master_reference/deps/ori/data.ts rename to packages/platform/src/contracts/recording_master_reference/deps/ori/data.ts diff --git a/packages/protocol/src/contracts/recording_master_reference/recording_master_reference.ts b/packages/platform/src/contracts/recording_master_reference/recording_master_reference.ts similarity index 95% rename from packages/protocol/src/contracts/recording_master_reference/recording_master_reference.ts rename to packages/platform/src/contracts/recording_master_reference/recording_master_reference.ts index 9ecca0f..109f37d 100644 --- a/packages/protocol/src/contracts/recording_master_reference/recording_master_reference.ts +++ b/packages/platform/src/contracts/recording_master_reference/recording_master_reference.ts @@ -13,10 +13,10 @@ * client-side convention. * * That thinness is the point. Miso's attested path — the Nautilus-verified master - * flow (`audio_ingester` producing a `miso_audio::Audio`), deferred — carries - * channel count, bit depth, sample rate, sample count and a PCM digest, and every - * one of those is backed by a Nautilus enclave signature over the measured audio. - * Until that path is running, stating the same fields _unverified_ would dress an + * flow (`audio_ingester` producing a `audio::Audio`), deferred — carries channel + * count, bit depth, sample rate, sample count and a PCM digest, and every one of + * those is backed by a Nautilus enclave signature over the measured audio. Until + * that path is running, stating the same fields _unverified_ would dress an * assertion up as a measurement. A pointer cannot be mistaken for a measurement. * * # This extension is a holdover diff --git a/packages/protocol/src/contracts/recording_royalty_pool/recording_royalty_pool.ts b/packages/platform/src/contracts/recording_royalty_pool/recording_royalty_pool.ts similarity index 98% rename from packages/protocol/src/contracts/recording_royalty_pool/recording_royalty_pool.ts rename to packages/platform/src/contracts/recording_royalty_pool/recording_royalty_pool.ts index 2096f74..f55dc51 100644 --- a/packages/protocol/src/contracts/recording_royalty_pool/recording_royalty_pool.ts +++ b/packages/platform/src/contracts/recording_royalty_pool/recording_royalty_pool.ts @@ -4,7 +4,7 @@ /** - * Raw-cap, custody-agnostic royalty-pool actions for Miso Recordings. + * Raw-cap, custody-agnostic royalty-pool actions for musicos Recordings. * * Every mutating action requires the Recording's own admin capability. The * canonical pool remains derived from the Recording and is returned unshared. diff --git a/packages/protocol/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts b/packages/platform/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts similarity index 100% rename from packages/protocol/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts rename to packages/platform/src/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts diff --git a/packages/protocol/src/contracts/recording_royalty_pool_plugin/witness.ts b/packages/platform/src/contracts/recording_royalty_pool_plugin/witness.ts similarity index 100% rename from packages/protocol/src/contracts/recording_royalty_pool_plugin/witness.ts rename to packages/platform/src/contracts/recording_royalty_pool_plugin/witness.ts diff --git a/packages/protocol/src/contracts/recording_streaming_transcode/deps/ori/data.ts b/packages/platform/src/contracts/recording_streaming_transcode/deps/ori/data.ts similarity index 100% rename from packages/protocol/src/contracts/recording_streaming_transcode/deps/ori/data.ts rename to packages/platform/src/contracts/recording_streaming_transcode/deps/ori/data.ts diff --git a/packages/protocol/src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts b/packages/platform/src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts similarity index 100% rename from packages/protocol/src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts rename to packages/platform/src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts diff --git a/packages/protocol/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/confidentiality.ts b/packages/platform/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/confidentiality.ts similarity index 100% rename from packages/protocol/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/confidentiality.ts rename to packages/platform/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/confidentiality.ts diff --git a/packages/protocol/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/data.ts b/packages/platform/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/data.ts similarity index 100% rename from packages/protocol/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/data.ts rename to packages/platform/src/contracts/release_cover_art/deps/0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60/data.ts diff --git a/packages/protocol/src/contracts/release_cover_art/deps/cover_art/cover_art.ts b/packages/platform/src/contracts/release_cover_art/deps/cover_art/cover_art.ts similarity index 100% rename from packages/protocol/src/contracts/release_cover_art/deps/cover_art/cover_art.ts rename to packages/platform/src/contracts/release_cover_art/deps/cover_art/cover_art.ts diff --git a/packages/protocol/src/contracts/release_cover_art/deps/per_track/per_track.ts b/packages/platform/src/contracts/release_cover_art/deps/per_track/per_track.ts similarity index 100% rename from packages/protocol/src/contracts/release_cover_art/deps/per_track/per_track.ts rename to packages/platform/src/contracts/release_cover_art/deps/per_track/per_track.ts diff --git a/packages/protocol/src/contracts/release_cover_art/release_cover_art.ts b/packages/platform/src/contracts/release_cover_art/release_cover_art.ts similarity index 100% rename from packages/protocol/src/contracts/release_cover_art/release_cover_art.ts rename to packages/platform/src/contracts/release_cover_art/release_cover_art.ts diff --git a/packages/protocol/src/contracts/recording_credits/deps/miso_credit/credit.ts b/packages/platform/src/contracts/release_credits/deps/credit/credit.ts similarity index 96% rename from packages/protocol/src/contracts/recording_credits/deps/miso_credit/credit.ts rename to packages/platform/src/contracts/release_credits/deps/credit/credit.ts index eecbff9..209d893 100644 --- a/packages/protocol/src/contracts/recording_credits/deps/miso_credit/credit.ts +++ b/packages/platform/src/contracts/release_credits/deps/credit/credit.ts @@ -11,7 +11,7 @@ import { type BcsType, bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { MoveStruct } from '../../../utils/index.ts'; -const $moduleName = 'miso_credit::credit'; +const $moduleName = 'credit::credit'; /** * A credit attributing roles to a party on a work. Generic over the role type to * support domain-specific roles. diff --git a/packages/protocol/src/contracts/release_credits/deps/sui/vec_map.ts b/packages/platform/src/contracts/release_credits/deps/sui/vec_map.ts similarity index 100% rename from packages/protocol/src/contracts/release_credits/deps/sui/vec_map.ts rename to packages/platform/src/contracts/release_credits/deps/sui/vec_map.ts diff --git a/packages/protocol/src/contracts/release_credits/release_credits.ts b/packages/platform/src/contracts/release_credits/release_credits.ts similarity index 97% rename from packages/protocol/src/contracts/release_credits/release_credits.ts rename to packages/platform/src/contracts/release_credits/release_credits.ts index 4a33e8f..53b8a56 100644 --- a/packages/protocol/src/contracts/release_credits/release_credits.ts +++ b/packages/platform/src/contracts/release_credits/release_credits.ts @@ -13,7 +13,7 @@ * survive into any lifecycle state (they may be attached before or after the * release is published). * - * This is Miso's canonical credits standard; because it is an extension, other + * This is musicos's canonical credits standard; because it is an extension, other * parties may publish their own release-credits standard against the same * `Release`. Credits are NOT read by the economics — they are attribution. * @@ -26,7 +26,7 @@ import { bcs } from '@mysten/sui/bcs'; import type {} from "@mysten/bcs"; import { type Transaction, type TransactionArgument } from '@mysten/sui/transactions'; import * as vec_map from './deps/sui/vec_map.ts'; -import * as credit from './deps/miso_credit/credit.ts'; +import * as credit from './deps/credit/credit.ts'; import * as release_party_role from './release_party_role.ts'; const $moduleName = '@local-pkg/release_credits::release_credits'; export const ExtensionKey = new MoveTuple({ name: `${$moduleName}::ExtensionKey`, fields: [bcs.bool()] }); diff --git a/packages/protocol/src/contracts/release_credits/release_party_role.ts b/packages/platform/src/contracts/release_credits/release_party_role.ts similarity index 97% rename from packages/protocol/src/contracts/release_credits/release_party_role.ts rename to packages/platform/src/contracts/release_credits/release_party_role.ts index 052a281..351ca96 100644 --- a/packages/protocol/src/contracts/release_credits/release_party_role.ts +++ b/packages/platform/src/contracts/release_credits/release_party_role.ts @@ -20,7 +20,7 @@ * - **`name()` is the canonical identifier**, returned as a stable PascalCase * token (`"Primary"` / `"Featured"`). * - * These identifiers are Miso's own canonical vocabulary; any overlap with an + * These identifiers are musicos's own canonical vocabulary; any overlap with an * external standard (e.g. DDEX) is coincidental, not a reproduction of it. */ diff --git a/packages/protocol/src/contracts/release_description/release_description.ts b/packages/platform/src/contracts/release_description/release_description.ts similarity index 100% rename from packages/protocol/src/contracts/release_description/release_description.ts rename to packages/platform/src/contracts/release_description/release_description.ts diff --git a/packages/protocol/src/contracts/release_dsp_link/release_dsp_link.ts b/packages/platform/src/contracts/release_dsp_link/release_dsp_link.ts similarity index 99% rename from packages/protocol/src/contracts/release_dsp_link/release_dsp_link.ts rename to packages/platform/src/contracts/release_dsp_link/release_dsp_link.ts index 4fc441f..8965e13 100644 --- a/packages/protocol/src/contracts/release_dsp_link/release_dsp_link.ts +++ b/packages/platform/src/contracts/release_dsp_link/release_dsp_link.ts @@ -4,8 +4,8 @@ /** - * Streaming deep links for a Miso release, one Digital Service Provider (DSP) at a - * time. Attaches `DspLinkData` records to a `miso::release::Release`: an + * Streaming deep links for a musicos release, one Digital Service Provider (DSP) + * at a time. Attaches `DspLinkData` records to a `musicos::release::Release`: an * album-level link per DSP, plus an optional per-track link per DSP. * * `DspLinkData` is a single built-in enum with one variant per DSP, holding that diff --git a/packages/protocol/src/contracts/release_genre/release_genre.ts b/packages/platform/src/contracts/release_genre/release_genre.ts similarity index 98% rename from packages/protocol/src/contracts/release_genre/release_genre.ts rename to packages/platform/src/contracts/release_genre/release_genre.ts index ebfcff6..aa8826f 100644 --- a/packages/protocol/src/contracts/release_genre/release_genre.ts +++ b/packages/platform/src/contracts/release_genre/release_genre.ts @@ -4,8 +4,8 @@ /** - * The genre(s) a Miso `Release` is classified under, stored as a dynamic field on - * the release's UID and written through its cap-gated `uid_mut`. + * The genre(s) a musicos `Release` is classified under, stored as a dynamic field + * on the release's UID and written through its cap-gated `uid_mut`. * * Genre is intrinsic to a recording — a fact about the master, not about any one * product it appears on — so a recording's own genre lives in the sibling package diff --git a/packages/protocol/src/contracts/release_kind/release_kind.ts b/packages/platform/src/contracts/release_kind/release_kind.ts similarity index 100% rename from packages/protocol/src/contracts/release_kind/release_kind.ts rename to packages/platform/src/contracts/release_kind/release_kind.ts diff --git a/packages/protocol/src/contracts/release_revenue_distributor/release_revenue_distributor.ts b/packages/platform/src/contracts/release_revenue_distributor/release_revenue_distributor.ts similarity index 100% rename from packages/protocol/src/contracts/release_revenue_distributor/release_revenue_distributor.ts rename to packages/platform/src/contracts/release_revenue_distributor/release_revenue_distributor.ts diff --git a/packages/protocol/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts b/packages/platform/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts similarity index 100% rename from packages/protocol/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts rename to packages/platform/src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts diff --git a/packages/protocol/src/contracts/release_revenue_distributor_plugin/witness.ts b/packages/platform/src/contracts/release_revenue_distributor_plugin/witness.ts similarity index 100% rename from packages/protocol/src/contracts/release_revenue_distributor_plugin/witness.ts rename to packages/platform/src/contracts/release_revenue_distributor_plugin/witness.ts diff --git a/packages/protocol/src/contracts/routed_stake/deps/royalty_pool/stake.ts b/packages/platform/src/contracts/routed_stake/deps/royalty_pool/stake.ts similarity index 100% rename from packages/protocol/src/contracts/routed_stake/deps/royalty_pool/stake.ts rename to packages/platform/src/contracts/routed_stake/deps/royalty_pool/stake.ts diff --git a/packages/protocol/src/contracts/routed_stake/deps/std/type_name.ts b/packages/platform/src/contracts/routed_stake/deps/std/type_name.ts similarity index 100% rename from packages/protocol/src/contracts/routed_stake/deps/std/type_name.ts rename to packages/platform/src/contracts/routed_stake/deps/std/type_name.ts diff --git a/packages/protocol/src/contracts/routed_stake/deps/sui/balance.ts b/packages/platform/src/contracts/routed_stake/deps/sui/balance.ts similarity index 100% rename from packages/protocol/src/contracts/routed_stake/deps/sui/balance.ts rename to packages/platform/src/contracts/routed_stake/deps/sui/balance.ts diff --git a/packages/protocol/src/contracts/routed_stake/deps/sui/vec_map.ts b/packages/platform/src/contracts/routed_stake/deps/sui/vec_map.ts similarity index 100% rename from packages/protocol/src/contracts/routed_stake/deps/sui/vec_map.ts rename to packages/platform/src/contracts/routed_stake/deps/sui/vec_map.ts diff --git a/packages/protocol/src/contracts/routed_stake/routed_stake.ts b/packages/platform/src/contracts/routed_stake/routed_stake.ts similarity index 100% rename from packages/protocol/src/contracts/routed_stake/routed_stake.ts rename to packages/platform/src/contracts/routed_stake/routed_stake.ts diff --git a/packages/protocol/src/contracts/royalty_pool/deps/std/type_name.ts b/packages/platform/src/contracts/royalty_pool/deps/std/type_name.ts similarity index 100% rename from packages/protocol/src/contracts/royalty_pool/deps/std/type_name.ts rename to packages/platform/src/contracts/royalty_pool/deps/std/type_name.ts diff --git a/packages/protocol/src/contracts/royalty_pool/deps/sui/balance.ts b/packages/platform/src/contracts/royalty_pool/deps/sui/balance.ts similarity index 100% rename from packages/protocol/src/contracts/royalty_pool/deps/sui/balance.ts rename to packages/platform/src/contracts/royalty_pool/deps/sui/balance.ts diff --git a/packages/protocol/src/contracts/royalty_pool/deps/sui/vec_map.ts b/packages/platform/src/contracts/royalty_pool/deps/sui/vec_map.ts similarity index 100% rename from packages/protocol/src/contracts/royalty_pool/deps/sui/vec_map.ts rename to packages/platform/src/contracts/royalty_pool/deps/sui/vec_map.ts diff --git a/packages/protocol/src/contracts/royalty_pool/pool.ts b/packages/platform/src/contracts/royalty_pool/pool.ts similarity index 100% rename from packages/protocol/src/contracts/royalty_pool/pool.ts rename to packages/platform/src/contracts/royalty_pool/pool.ts diff --git a/packages/protocol/src/contracts/royalty_pool/stake.ts b/packages/platform/src/contracts/royalty_pool/stake.ts similarity index 100% rename from packages/protocol/src/contracts/royalty_pool/stake.ts rename to packages/platform/src/contracts/royalty_pool/stake.ts diff --git a/packages/platform/src/contracts/utils/index.ts b/packages/platform/src/contracts/utils/index.ts new file mode 100644 index 0000000..112285f --- /dev/null +++ b/packages/platform/src/contracts/utils/index.ts @@ -0,0 +1,428 @@ + +import { + bcs, + type BcsType, + type TypeTag, + TypeTagSerializer, + BcsStruct, + BcsEnum, + BcsTuple, +} from '@mysten/sui/bcs'; +import { normalizeStructTag, normalizeSuiAddress } from '@mysten/sui/utils'; +import { + type TransactionArgument, + type TransactionObjectArgument, + isArgument, +} from '@mysten/sui/transactions'; +import { type ClientWithCoreApi, type SuiClientTypes } from '@mysten/sui/client'; + +const MOVE_STDLIB_ADDRESS = normalizeSuiAddress('0x1'); +const SUI_FRAMEWORK_ADDRESS = normalizeSuiAddress('0x2'); + +export type RawTransactionArgument = T | TransactionArgument; + +export type GetOptions = {}> = + SuiClientTypes.GetObjectOptions & { client: ClientWithCoreApi }; + +export type GetManyOptions = {}> = + SuiClientTypes.GetObjectsOptions & { client: ClientWithCoreApi }; + +export function getPureBcsSchema(typeTag: string | TypeTag): BcsType | null { + const parsedTag = typeof typeTag === 'string' ? TypeTagSerializer.parseFromStr(typeTag) : typeTag; + + if ('u8' in parsedTag) { + return bcs.U8; + } else if ('u16' in parsedTag) { + return bcs.U16; + } else if ('u32' in parsedTag) { + return bcs.U32; + } else if ('u64' in parsedTag) { + return bcs.U64; + } else if ('u128' in parsedTag) { + return bcs.U128; + } else if ('u256' in parsedTag) { + return bcs.U256; + } else if ('address' in parsedTag) { + return bcs.Address; + } else if ('bool' in parsedTag) { + return bcs.Bool; + } else if ('vector' in parsedTag) { + const type = getPureBcsSchema(parsedTag.vector); + return type ? bcs.vector(type) : null; + } else if ('struct' in parsedTag) { + const structTag = parsedTag.struct; + const pkg = normalizeSuiAddress(structTag.address); + + if (pkg === MOVE_STDLIB_ADDRESS) { + if ( + (structTag.module === 'ascii' || structTag.module === 'string') && + structTag.name === 'String' + ) { + return bcs.String; + } + + if (structTag.module === 'option' && structTag.name === 'Option') { + const inner = structTag.typeParams[0]; + const type = inner ? getPureBcsSchema(inner) : null; + return type ? bcs.option(type) : null; + } + } + + if ( + pkg === SUI_FRAMEWORK_ADDRESS && + structTag.module === 'object' && + (structTag.name === 'ID' || structTag.name === 'UID') + ) { + return bcs.Address; + } + } + + return null; +} + +export function normalizeMoveArguments( + args: unknown[] | object, + argTypes: readonly (string | null)[], + parameterNames?: string[], +) { + const argLen = Array.isArray(args) ? args.length : Object.keys(args).length; + if (parameterNames && argLen !== parameterNames.length) { + throw new Error( + `Invalid number of arguments, expected ${parameterNames.length}, got ${argLen}`, + ); + } + + const normalizedArgs: TransactionArgument[] = []; + + let index = 0; + for (const argType of argTypes) { + if (argType === '0x2::clock::Clock') { + normalizedArgs.push((tx) => tx.object.clock()); + continue; + } + + if (argType === '0x2::random::Random') { + normalizedArgs.push((tx) => tx.object.random()); + continue; + } + + if (argType === '0x2::deny_list::DenyList') { + normalizedArgs.push((tx) => tx.object.denyList()); + continue; + } + + if (argType === '0x2::accumulator::AccumulatorRoot') { + // Chain-wide shared singleton at a fixed address (SUI_ACCUMULATOR_ROOT_OBJECT_ID). + normalizedArgs.push((tx) => tx.object('0xacc')); + continue; + } + + if (argType === '0x3::sui_system::SuiSystemState') { + normalizedArgs.push((tx) => tx.object.system()); + continue; + } + + let arg; + if (Array.isArray(args)) { + if (index >= args.length) { + throw new Error( + `Invalid number of arguments, expected at least ${index + 1}, got ${args.length}`, + ); + } + arg = args[index]; + } else { + if (!parameterNames) { + throw new Error(`Expected arguments to be passed as an array`); + } + const name = parameterNames[index]; + arg = + name !== undefined && Object.prototype.hasOwnProperty.call(args, name) + ? args[name as keyof typeof args] + : undefined; + + if (arg === undefined) { + throw new Error(`Parameter ${name} is required`); + } + } + + index += 1; + + if (typeof arg === 'function' || isArgument(arg)) { + normalizedArgs.push(arg as TransactionArgument); + continue; + } + + const bcsType = argType === null ? null : getPureBcsSchema(argType); + + if (bcsType) { + const bytes = bcsType.serialize(arg as never); + normalizedArgs.push((tx) => tx.pure(bytes)); + continue; + } + + if (typeof arg === 'string') { + normalizedArgs.push((tx) => tx.object(arg)); + continue; + } + + throw new Error(`Invalid argument ${stringify(arg)} for type ${argType}`); + } + + return normalizedArgs; +} + +/* -------------------------- Config-mapped arguments -------------------------- */ + +/** Context passed to config resolver functions. */ +export interface ConfigResolverContext { + /** + * The matched parameter's own instantiated type arguments (not the whole function's type + * argument tuple). Concrete instantiations are canonical type tags; generic positions pass + * the caller's `typeArguments` strings through as provided. + */ + typeArguments: string[]; + /** The package address the generated call will be sent to. */ + packageAddress: string; + /** The Move module of the generated call. */ + moduleName: string; + /** The Move function of the generated call. */ + functionName: string; + /** The Move name of the matched parameter, when the summary includes parameter names. */ + parameterName?: string; + /** The matched parameter's position in the generated function's arguments. */ + parameterIndex: number; +} + +/** + * A non-callback transaction argument in a generated config object, used for keys whose matched + * parameter can't be supplied as an object id string. + */ +export type ConfigObjectValue = Exclude unknown>; + +/** + * A plain value in a generated config object: an object id or a non-callback transaction + * argument. Keys that can resolve multiple bindings are typed as resolver functions instead. + */ +export type ConfigValue = string | ConfigObjectValue; + +/* -------------------------- Move type tags -------------------------- */ + +/** A type argument: a type tag string, or a BCS type whose name is a Move type. */ +export type TypeArgument = string | BcsType; + +export interface TypeTagOptions { + package?: string; + typeArguments?: readonly TypeArgument[]; +} + +/** + * `typeArguments` is required when the type's name contains unfilled + * `phantom X` parameters (at any depth). Everything else — argument arity, + * position contents, and tag validity — is validated at runtime. + */ +type TypeTagParams = Name extends `${string}phantom ${string}` + ? [options: TypeTagOptions & { typeArguments: readonly TypeArgument[] }] + : [options?: TypeTagOptions]; + +type ResolveTypeTagOptions = { client: ClientWithCoreApi } & ( + Name extends `${string}phantom ${string}` + ? TypeTagOptions & { typeArguments: readonly TypeArgument[] } + : TypeTagOptions +); + +const HAS_PHANTOM_REGEX = /phantom [A-Za-z_$][A-Za-z0-9_$]*/; + +function splitTopLevelTypeArgs(inner: string): string[] { + const parts: string[] = []; + let depth = 0; + let current = ''; + for (const char of inner) { + if (char === ',' && depth === 0) { + parts.push(current.trim()); + current = ''; + continue; + } + if (char === '<') depth++; + if (char === '>') depth--; + current += char; + } + if (current) parts.push(current.trim()); + return parts; +} + +function buildTypeTag(name: string, options: TypeTagOptions | undefined): string { + const lt = name.indexOf('<'); + const base = lt === -1 ? name : name.slice(0, lt); + + if (base.split('::').length !== 3) { + throw new Error(`${name} is not a top-level Move type`); + } + + let result = name; + + if (options?.typeArguments) { + const baked = lt === -1 ? [] : splitTopLevelTypeArgs(name.slice(lt + 1, -1)); + const supplied = options.typeArguments.map((arg) => { + if (typeof arg === 'string') { + return arg; + } + if (arg && typeof arg.serialize === 'function' && typeof arg.name === 'string') { + return arg.name; + } + throw new Error(`Invalid type argument ${stringify(arg)}`); + }); + + if (supplied.length !== baked.length) { + throw new Error( + `Expected ${baked.length} type arguments for ${base}, got ${supplied.length}`, + ); + } + + result = supplied.length === 0 ? base : `${base}<${supplied.join(', ')}>`; + } + + if (HAS_PHANTOM_REGEX.test(result)) { + throw new Error( + options?.typeArguments + ? `A type argument contains an unfilled phantom parameter in ${result}` + : `Missing type arguments for ${result}`, + ); + } + + if (options?.package) { + const [, ...rest] = result.split('::'); + result = [options.package, ...rest].join('::'); + } + + // fully validate address-only tags (MVR names can't be parsed as type tags) + if (!HAS_PHANTOM_REGEX.test(result) && !/[@/]/.test(result)) { + TypeTagSerializer.parseFromStr(result); + } + + return result; +} + +async function resolveBuiltTypeTag( + name: string, + options: { client: ClientWithCoreApi } & TypeTagOptions, +): Promise { + const { client, ...rest } = options; + const { type } = await client.core.mvr.resolveType({ + type: buildTypeTag(name, rest), + }); + return normalizeStructTag(type); +} + +export class MoveStruct< + T extends Record>, + const Name extends string = string, +> extends BcsStruct { + /** + * Build the type tag for this struct. + * + * `typeArguments` is the full positional list, in Move declaration order, and + * is required when the struct has unfilled phantom parameters. The result may + * contain MVR names: those are valid in transaction `typeArguments`, but for + * queries or comparisons against on-chain data use `resolveTypeTag` instead. + */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** + * Build the type tag for this struct, then resolve any MVR names through the + * client (using its configured overrides and the MVR API) and return the + * normalized, address-only form suitable for queries and comparisons against + * on-chain data. + */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } + + async get = {}>({ + objectId, + ...options + }: GetOptions): Promise< + SuiClientTypes.Object & { json: BcsStruct['$inferType'] } + > { + const [res] = await this.getMany({ + ...options, + objectIds: [objectId], + }); + + if (!res) { + throw new Error(`No object found for id ${objectId}`); + } + + return res; + } + + async getMany = {}>({ + client, + ...options + }: GetManyOptions): Promise< + Array & { json: BcsStruct['$inferType'] }> + > { + const response = (await client.core.getObjects({ + ...options, + include: { + ...options.include, + content: true, + }, + })) as SuiClientTypes.GetObjectsResponse; + + return response.objects.map((obj) => { + if (obj instanceof Error) { + throw obj; + } + + return { + ...obj, + json: this.parse(obj.content), + }; + }); + } +} + +export class MoveEnum< + T extends Record | null>, + const Name extends string, +> extends BcsEnum { + /** Build the type tag for this enum. See `MoveStruct.typeTag` for semantics. */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** Build and resolve the type tag for this enum. See `MoveStruct.resolveTypeTag`. */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } +} + +export class MoveTuple< + const T extends readonly BcsType[], + const Name extends string, +> extends BcsTuple { + /** Build the type tag for this struct. See `MoveStruct.typeTag` for semantics. */ + typeTag(...args: TypeTagParams): string { + return buildTypeTag(this.name, args[0] as TypeTagOptions | undefined); + } + + /** Build and resolve the type tag for this struct. See `MoveStruct.resolveTypeTag`. */ + async resolveTypeTag(options: ResolveTypeTagOptions): Promise { + return resolveBuiltTypeTag(this.name, options as { client: ClientWithCoreApi } & TypeTagOptions); + } +} + +function stringify(val: unknown) { + if (typeof val === 'object') { + return JSON.stringify(val, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ); + } + if (typeof val === 'bigint') { + return val.toString(); + } + + return val; +} diff --git a/packages/protocol/src/contracts/vault/deps/sui/bag.ts b/packages/platform/src/contracts/vault/deps/sui/bag.ts similarity index 100% rename from packages/protocol/src/contracts/vault/deps/sui/bag.ts rename to packages/platform/src/contracts/vault/deps/sui/bag.ts diff --git a/packages/protocol/src/contracts/vault/deps/sui/borrow.ts b/packages/platform/src/contracts/vault/deps/sui/borrow.ts similarity index 100% rename from packages/protocol/src/contracts/vault/deps/sui/borrow.ts rename to packages/platform/src/contracts/vault/deps/sui/borrow.ts diff --git a/packages/protocol/src/contracts/vault/vault.ts b/packages/platform/src/contracts/vault/vault.ts similarity index 100% rename from packages/protocol/src/contracts/vault/vault.ts rename to packages/platform/src/contracts/vault/vault.ts diff --git a/packages/platform/src/cover.ts b/packages/platform/src/cover.ts index bdd653d..e200beb 100644 --- a/packages/platform/src/cover.ts +++ b/packages/platform/src/cover.ts @@ -21,8 +21,8 @@ import type { Transaction, TransactionObjectArgument } from "@mysten/sui/transac import type { TxThunk } from "./transactions.ts"; import { asU64, directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput, type U64Input } from "./vault.ts"; import { OPTION_NONE, OPTION_SOME, unencryptedWalrusBlob } from "./internal.ts"; -import * as coverArt from "@misofm/protocol/contracts/cover_art/cover_art"; -import * as releaseCoverArt from "@misofm/protocol/contracts/release_cover_art/release_cover_art"; +import * as coverArt from "./contracts/cover_art/cover_art.ts"; +import * as releaseCoverArt from "./contracts/release_cover_art/release_cover_art.ts"; type ReleaseAuthorityInput = | { readonly authority: AdminCapAuthority; readonly releaseAdminCapId?: never } diff --git a/packages/platform/src/credits.ts b/packages/platform/src/credits.ts index 275b62b..0eca664 100644 --- a/packages/platform/src/credits.ts +++ b/packages/platform/src/credits.ts @@ -38,12 +38,12 @@ import type { } from "@mysten/sui/transactions"; import type { TxThunk } from "./transactions.ts"; import { OPTION_NONE, OPTION_SOME } from "./internal.ts"; -import * as compositionCredits from "@misofm/protocol/contracts/composition_credits/composition_credits"; -import * as compositionPartyRole from "@misofm/protocol/contracts/composition_credits/composition_party_role"; -import * as recordingCredits from "@misofm/protocol/contracts/recording_credits/recording_credits"; -import * as recordingPartyRole from "@misofm/protocol/contracts/recording_credits/recording_party_role"; -import * as releaseCredits from "@misofm/protocol/contracts/release_credits/release_credits"; -import * as releasePartyRole from "@misofm/protocol/contracts/release_credits/release_party_role"; +import * as compositionCredits from "./contracts/composition_credits/composition_credits.ts"; +import * as compositionPartyRole from "./contracts/composition_credits/composition_party_role.ts"; +import * as recordingCredits from "./contracts/recording_credits/recording_credits.ts"; +import * as recordingPartyRole from "./contracts/recording_credits/recording_party_role.ts"; +import * as releaseCredits from "./contracts/release_credits/release_credits.ts"; +import * as releasePartyRole from "./contracts/release_credits/release_party_role.ts"; import { directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput } from "./vault.ts"; function object(tx: Transaction, value: ObjectInput): TransactionObjectArgument { diff --git a/packages/platform/src/deployments.ts b/packages/platform/src/deployments.ts index 81e1690..163dc59 100644 --- a/packages/platform/src/deployments.ts +++ b/packages/platform/src/deployments.ts @@ -1,11 +1,54 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import type { MisoDeployment, MisoNetwork } from "@misofm/protocol/deployments"; -import { MISO_DEPLOYMENTS } from "@misofm/protocol/deployments"; -import { normalizeSuiObjectId } from "@mysten/sui/utils"; +import type { MisoDeployment, MisoNetwork } from "@misofm/musicos/deployments"; +import { MISO_DEPLOYMENTS } from "@misofm/musicos/deployments"; +import type { PartyDeployment } from "@misofm/partyos/deployments"; +import { PARTYOS_DEPLOYMENTS } from "@misofm/partyos/deployments"; +import { normalizeSuiAddress, normalizeSuiObjectId } from "@mysten/sui/utils"; import { immutableSnapshot } from "./internal.ts"; +/** Every package address a platform deployment must supply, for canonical-id + * and pairwise-distinctness validation in {@link assertMisoPlatformDeployment}. */ +const REQUIRED_PLATFORM_PACKAGE_KEYS = [ + "minato", + "credit", + "compositionCredits", + "recordingCredits", + "releaseCredits", + "royaltyPool", + "routedStake", + "coverArt", + "releaseCoverArt", + "genre", + "releaseDescription", + "releaseDspLink", + "releaseGenre", + "releaseKind", + "recordingAdvisory", + "recordingLanguage", + "recordingMasterReference", + "ori", + "countryCode", + "languageCode", +] as const; + +/** Every first-party Party extension package (the PartyOS core lives in `@misofm/partyos`). */ +const REQUIRED_PARTY_PACKAGE_KEYS = [ + "partyCta", + "partyGenre", + "partyMedia", + "partyMusic", + "partyPlatformLink", + "partyProLink", + "partyProfile", + "partyRoles", + "partySocial", + "partyTags", + "countryCode", + "languageCode", +] as const; + /** Primary Record sales are unavailable on legacy deployments until both new * immutable packages have been freshly published and verified. */ export type RecordSalesDeployment = @@ -175,20 +218,48 @@ export function requireOperationsDeployment( ]; if (new Set(identities).size !== identities.length) { throw new OperationsUnavailableError( - "the Vault registry object and all nine package IDs must be distinct", + `the Vault registry object and all ${packageEntries.length} package IDs must be distinct`, ); } return deployment; } +/** + * The first-party Party EXTENSION packages (profile, media, roles, tags, + * genres, CTAs, platform links). The Party object model core (`partyos`) is + * owned by `@misofm/partyos` — see {@link MisoPlatformDeployment.partyos}. + * Platform-internal: not re-exported by `@misofm/musicos`, which only ever + * knew the object-model core. + */ +export interface PartyExtensionsDeployment { + readonly partyCta: string; + readonly partyGenre: string; + readonly partyMedia: string; + readonly partyMusic: string; + readonly partyPlatformLink: string; + readonly partyProLink: string; + readonly partyProfile: string; + readonly partyRoles: string; + readonly partySocial: string; + readonly partyTags: string; + /** `country_code` — dependency of `party_profile`. */ + readonly countryCode: string; + /** `language_code` — dependency of `party_profile` (and, via `packages`, `recording_language`). */ + readonly languageCode: string; +} + /** Complete on-chain identity used by the Miso platform SDK on one network. */ export interface MisoPlatformDeployment { /** Network name this immutable deployment set belongs to. */ readonly network: MisoNetwork; /** Full ledger genesis digest used to reject a mislabeled RPC endpoint. */ readonly chainIdentifier: string; - /** The complete protocol and Party deployment this platform build targets. */ + /** The object-model deployment (`@misofm/musicos`) this platform build targets. */ readonly protocol: MisoDeployment; + /** The Party object model deployment (`@misofm/partyos`) this platform build targets. */ + readonly partyos: PartyDeployment; + /** The first-party Party extension packages. */ + readonly party: PartyExtensionsDeployment; readonly recordSales: RecordSalesDeployment; /** Fail-closed, structurally complete Vault/Action/plugin identity set. */ readonly operations: OperationsDeployment; @@ -209,9 +280,6 @@ export interface MisoPlatformDeployment { readonly genre: string; readonly releaseDescription: string; readonly releaseDspLink: string; - // Predates the ordered-list (`vector`, add/set-primary/remove) redesign — - // this id has no `add_genre` and must be replaced by the republished package - // before use. See the no-upgrades policy: misofm never upgrades in place. readonly releaseGenre: string; readonly releaseKind: string; readonly recordingAdvisory: string; @@ -231,6 +299,10 @@ export interface MisoPlatformDeployment { readonly recordSealPolicy?: string; /** External `ori` package (`data::WalrusBlob`, `data::WalrusQuilt`) used by Walrus-backed extensions. */ readonly ori: string; + /** `country_code` — dependency of `party_profile`. */ + readonly countryCode: string; + /** `language_code` — dependency of `recording_language` (and, via `party`, `party_profile`). */ + readonly languageCode: string; }; readonly objects: { readonly releaseRegistry: string; @@ -244,6 +316,110 @@ export interface MisoPlatformDeployment { }; } +export class MisoPlatformDeploymentInvalidError extends Error { + override readonly name = "MisoPlatformDeploymentInvalidError"; +} + +/** + * Validates the `packages` and `party` package-id sets before any Move target + * is constructed from them: every required id must be a canonical 32-byte Sui + * package ID, and no two of them (nor the core `musicos` package) may collide. + * Optional generation fields (`recordingGenre`, `recordingStreamingTranscode`, + * `recordingEngineSession`, `recordSealPolicy`) are validated only when present. + */ +export function assertMisoPlatformDeployment( + deployment: unknown, +): asserts deployment is MisoPlatformDeployment { + if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: deployment must be a complete MisoPlatformDeployment object.", + ); + } + const candidate = deployment as Partial; + const packages = candidate.packages as Partial | undefined; + const party = candidate.party as Partial | undefined; + const partyos = candidate.partyos as Partial | undefined; + if (!packages || typeof packages !== "object") { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: deployment is missing its `packages` section.", + ); + } + if (!party || typeof party !== "object") { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: deployment is missing its `party` section.", + ); + } + if (!partyos || typeof partyos !== "object") { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: deployment is missing its `partyos` section.", + ); + } + + const seenPackageIds = new Set(); + /** `dedupeOk` lets `party.countryCode`/`party.languageCode` intentionally + * repeat `packages.countryCode`/`packages.languageCode` — both sections + * point the same shared dependency package at their own consumers — without + * tripping the distinctness check meant to catch a genuinely misconfigured + * manifest. */ + const checkCanonical = (field: string, packageId: unknown, dedupeOk = false) => { + if (typeof packageId !== "string") { + throw new MisoPlatformDeploymentInvalidError( + `@misofm/platform: deployment is missing package ID for "${field}".`, + ); + } + let normalized: string; + try { + normalized = normalizeSuiAddress(packageId); + } catch { + throw new MisoPlatformDeploymentInvalidError( + `@misofm/platform: deployment package ID for "${field}" is not a valid Sui address.`, + ); + } + if (packageId !== normalized) { + throw new MisoPlatformDeploymentInvalidError( + `@misofm/platform: deployment package ID for "${field}" must be normalized (${normalized}).`, + ); + } + if (!dedupeOk) { + if (seenPackageIds.has(packageId)) { + throw new MisoPlatformDeploymentInvalidError( + `@misofm/platform: deployment package ID for "${field}" duplicates another package. ` + + "Every package in a deployment manifest must have its own address.", + ); + } + seenPackageIds.add(packageId); + } + }; + + for (const key of REQUIRED_PLATFORM_PACKAGE_KEYS) { + checkCanonical(`packages.${key}`, packages[key]); + } + checkCanonical("partyos.partyos", partyos.partyos); + for (const key of REQUIRED_PARTY_PACKAGE_KEYS) { + // `country_code`/`language_code` are shared dependency packages, deliberately + // repeated from `packages.*` above rather than distinct party-only ids. + checkCanonical(`party.${key}`, party[key], key === "countryCode" || key === "languageCode"); + } + if (party.countryCode !== packages.countryCode) { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: party.countryCode must match packages.countryCode (the same shared dependency package).", + ); + } + if (party.languageCode !== packages.languageCode) { + throw new MisoPlatformDeploymentInvalidError( + "@misofm/platform: party.languageCode must match packages.languageCode (the same shared dependency package).", + ); + } +} + +/** Validate and snapshot an untrusted manifest so later caller mutation is inert. */ +export function normalizeMisoPlatformDeployment( + deployment: unknown, +): MisoPlatformDeployment { + assertMisoPlatformDeployment(deployment); + return immutableSnapshot(deployment); +} + /** * Platform deployments bundled with this SDK release. * @@ -255,95 +431,125 @@ export const MISO_PLATFORM_DEPLOYMENTS = immutableSnapshot({ network: "testnet", chainIdentifier: "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD", protocol: MISO_DEPLOYMENTS.testnet, + partyos: PARTYOS_DEPLOYMENTS.testnet, recordSales: { status: "available", recordPackageId: - "0x0ad1ad504c63f8e14f74f0535600eb965217bc9ef3305d6d4bd3dad077bb832f", + "0x8562a4c266b1229871551cec69c1a331f682920b9cad7685e42e7c6a5eba61d5", recordShopPackageId: - "0x6f075182093837a6b9ea00be706c94d896a3cf62567e05a74d902716a7ac8730", + "0x5d1b79c312b5d2a2bc9aa0ce7ea6d41ecedd4a5d8698bc007000ca0c8a3459f9", }, operations: { status: "available", vault: { packageId: - "0x994699b7df4963aa3ad4ca3253d26e7271b8f8e76b50108f00eb69e7aca4637a", + "0xe7a5d1f895d7ca2571c2329a13a5703905d8903433b379db3aa987c7594a0198", registryId: - "0xeaad77ce26cc610340c32c55f8fe0ae6f63639431c4608c7466703dee8450c7a", + "0xebd40980edb30e425b80f7d65378246254cc5787e07d23faa46e6b9a2b9dd14d", }, actions: { compositionRoyaltyPool: - "0x8a55e02198a9ab7997abf2815e7f4b587d976570128741dfed2e152b7c0e9cab", + "0xa4655e8c1319655cbc0ad5da0d8bccbd528372fea2cb767235e89f2be6eb403c", recordingRoyaltyPool: - "0x404089e5da21fe26dd6b9b346f457b3cbe34977bb12c503036e6b625fa34ba41", + "0xff510b24ddba7755dafeb3ceae65204ed6be058a83b482f98da92b3d19eefcd2", partyWallet: - "0x0bdd7e78d46834546b54d6c02697cf67b37d36a2c6fdbb19e290729529fdb3a9", + "0x493fa265fd7c8066cd80f644d22086fe36b2c8e26bd160d8a6f6f44743c42acc", compositionRoutedStake: - "0xf5138fd64c16a43bead17e56fe7ac6900c214cd55233c069f47b13174d0a9ff8", + "0xff5b7a4e1791210c36f2ea071a87e2e6212ddbe2acc922d324cf5436f8ca0b47", releaseRevenueDistributor: - "0x24656bb1feafc1a3d0ec30b8f10b364983c7a9c12febb5b7f6acd007f013a54b", + "0x72b175f79cdb1df5d597cdb07007ac309994e6c3f99823b04400a48565dd3989", }, plugins: { compositionRoyaltyPool: - "0x8934a96dd9cbdc64040d74810556deef0a39d28ea7033f633d76d7b560e9191c", + "0x57b58eb53ade40a7e1e6e4be4bdd0fecdd57f709efe8021830b2e3519e800254", recordingRoyaltyPool: - "0x92b8b21483241e39ddab66a67fb1fc99f553eef9b53f47747cb59515540c8dec", + "0x69c859aba359ca8fbeb1df4cff3de53cca3b4ce5ffaa1a7516bc9eb3e016cf71", releaseRevenueDistributor: - "0xbebed662371093a75c0d24a2c34a161339a2e4ab981594de8aa7157c555c620e", + "0x875a764569360ec7f4e676bcf5e30a50037229f33f2bae6d2acd205c0570e8b2", }, }, packages: { minato: "0xcdf58ed7e4580118a6a3f2a8077abffe633c551b2f19e95ce01685d42f90b8d9", credit: - "0xdcbe495da81859e16540e2df44e86123217930ac9066a8aecebf479cbe73006f", + "0xd77981b6872d975ccebeac1c639eebbcd1f7916f468df0242d4b26d9bf7293c1", compositionCredits: - "0x54cac9dde365a08eff983af16d74d798e6c0a14264bf6cc8a24790708498e37b", + "0x924a5e87230cf1218be23484e4914d9c5515528c8ef032b1796ebdc71dd3830c", recordingCredits: - "0x0ca2c727c3eb8d3f889d9b29ce1118bc60518c6990e26e9ab278a7ffe635bf1e", + "0x982d946a34bb342913c81441be1c702b29f2a83bc801c4fce9ed4f9d7833b8b1", releaseCredits: - "0x3a05d0c863ca0b5210f90f69cf87da59791b127c9eaaa10633961aee229a3ed5", + "0xdc854106cb76733bb012db2369e0a39319fb79b0587e2a887814c7cb67134d77", royaltyPool: - "0xa49e297e4ed8c29ea9bd5941b3ac7d41327f97c85fc35e11bda466f09fce1943", + "0xf7be632d74f71574c2aa5ac2790a51e770b2cfbcf80233b875bdbe24ffe0b3b9", routedStake: - "0xc920af18421cd11c315fa8d0cdd56056854cf453f1ced1fa1b32d555bca2955a", + "0xfe3f0c0008823330d2e465a300b34eec50ab3c7444e655602ed2efc517c91824", coverArt: - "0xb7c82b0435e6f577cdc334a9b2d8940b869dacb846d72ddb3ee566b5e8c63b52", + "0xc0b1421b32e287559ec29f6d7c71dd83778234cb62d4fe9d296577710411f6d4", releaseCoverArt: - "0xb289dfb58ccf2af3722f7bc93b072426a791d05a401115f2901d130f12d8e4b2", + "0x74c1708ff6016b5244f8d31559210ae6e5a2b5e52cb3c7996e1f630a7d4a87b2", genre: - "0x6ae4aefdd9d147db6f04a14e4f6149944c485fc7d87d8201dcdb0dee15f0f296", + "0xeea93dd140ee2133d1baeeb71281846658b104d53403d4f89e75f65ade38f931", releaseDescription: - "0x60a8bc11b7d41d594a2c54c8dee4534dcf94d3be20054100c7774026a543ec6f", + "0x8d59667b5e9476df33125153494246107a1384ecbea8862fa1b5c256588789fa", releaseDspLink: - "0xbeffd79f656ce89d3595c9fb36dac42a169b72a5e7090504c48ba6c2425961a6", - // Predates the ordered-list (`vector`, add/set-primary/remove) redesign - // of release_genre — this id has no `add_genre` and must be replaced by - // the republished package before use. + "0xebcf0515a35c765ca89208162510bdddd2a9225866b9c66f33dc3cfa1e07bf8a", releaseGenre: - "0x111dd8bff35a1779067d7c75f8a514691342f91f67d2fcdae392768ba6db26f2", + "0xc7269a52efa400b80009f8c9f7e31591a26c50c0ae925be31edaef8889e91c83", releaseKind: - "0x90bf2633b45699d424da616869ecb3d824a88d78d8f8d2ccf5b711b18a734bf1", + "0x48f1651e00572525fb2e59ebb37c5367d45258d62486984825c972a8ab2d0ce3", recordingAdvisory: - "0x48a16738e6af6548e50d8f76dce9b9de247f365ef2a07c171f732bdc9c88b235", + "0x4149faba212e3aca5440e5ea15bde535dca453f2701125a996d89188b0000ed0", recordingLanguage: - "0x677cef9c766b17f87fdf9d6623f1adca0d2e244ee228c86d82c23599ad9a828a", + "0x78e48de29be0b9dca4921dd0740cc12c88cb02599ee8554854b4e423a689fd19", + recordingGenre: + "0x3c017256c66c7d48f4c23ae40dd6d5a0470a4f85a750aadef388ef9ac8e71b36", recordingMasterReference: - "0x2b1ca393e8d41eba5b0d3cabf6df1e207a349bba0486ca9ea3a31734d3445971", + "0x2638edd3c9fec5650eda561e77fb390add0110088ad54c470e83ea079108bb1a", // Stems generation (Session V1 blob + Stem { digest, data } vector). // 0x2fcb9ab9… was the retired single-blob generation. recordingEngineSession: - "0x55ca06d2ac044247e55165a9cb661219d47cee94789cc289a6e1e91ac8159a76", + "0xa3057f47e31683c1eba0afba56aa38af6734f6b9fc3fb679c2681a21ccfe3c24", recordingStreamingTranscode: - "0x45ee95108ddc6f90c1f4c73fb8aff5f0b7047d4982e6f997e2c9afe02f908a05", + "0x67bde09257865521b221aebf83bc95e5a9ae9d38f2c77223772436bc9c70e8ec", recordSealPolicy: - "0x3af51d7c48b32d37a1a3bd31f06cdd873032fca8663cb3f9d9cf516bc4ee08ba", + "0x7e1921715dbda4fbb73227d0764f4c0de55fdcf3892ddadc4ef1bf895453b2a2", ori: "0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60", + countryCode: + "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", + languageCode: + "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", + }, + party: { + partyCta: + "0x310bd64b4d32b547ad52128df9839702a3b74e144e920d04d00bb2bd488b2036", + partyGenre: + "0x6b66b793a13c899e41d812a3feccafc9078184f0bd74c4af474d23ef7fd35ec5", + partyMedia: + "0xb4479afae1f14c4cf7064908c29ccf698b5e10f2e48a31d63f861131476594c5", + partyMusic: + "0xe583826d1610a252769eddb4fa81e81040d40468fc99eec4bf72c52456ff7f79", + partyPlatformLink: + "0xe0990445758d27732928759a33e73bcddd2b06604dc0c552b958b6b0220b060b", + partyProLink: + "0xdbf8f3d1d12cf15e435d74bfb910e88eae7094064176661a85bc79a76e6ce674", + partyProfile: + "0x0cb11099892f9a583077408369a1d838ec888660ed31af7f7bd52a3794f147a6", + partyRoles: + "0x53227d2c8c36b6bae17e2cdfaff50cf7e52261c40cbda65a11fe9f9486cc6104", + partySocial: + "0xb691711983ad484e8caecc3091872a163a4cf6f93bfab3d3974ad94bb4b63d2f", + partyTags: + "0xcc0a53bfa1310758c02607a9ee95fa1553ef3d9431e3df25f38a16e09c3466c5", + countryCode: + "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", + languageCode: + "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", }, objects: { releaseRegistry: - "0xbb947ba51420df59a726487e008adaf352b7c58c1dbfe1e5e6b19131a98cc43c", + "0xb0506d287b50a134a2773a5d4e0c9c4e3ef4aa3806cad6ff93d8b28be063c6df", genreRegistry: - "0x63cfb245a15871b7a4c33b66487a3f4de0a7bd6431e3f0434cad4176c25e6eb5", + "0x9a5a7ce36906a0581e6e38a0dd6abc24093acc6ffd7c0d6a7f419c57b069d00e", }, legacy: { releaseCoverArtPackages: [], diff --git a/packages/protocol/src/events.ts b/packages/platform/src/events.ts similarity index 85% rename from packages/protocol/src/events.ts rename to packages/platform/src/events.ts index b7adedc..25328cf 100644 --- a/packages/protocol/src/events.ts +++ b/packages/platform/src/events.ts @@ -2,12 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 /** - * BCS decoders for all current execution events outside the three legacy core - * convenience parsers. Each decoder preserves the generated Move field names and - * nested layouts exactly, which is the safe indexer boundary for independently - * versioned extension packages. + * BCS decoders for extension and generic-primitive execution events. Core + * object-model events (release registry creation, …) decode through + * `@misofm/musicos`'s own `eventParsers`; this module covers everything Miso + * layers on top: work extensions, and the royalty-pool / routed-stake + * primitives. Each decoder preserves the generated Move field names and + * nested layouts exactly, which is the safe indexer boundary for + * independently versioned extension packages. */ +import { decodeEvent } from "@misofm/musicos/events"; +import type { BcsParser } from "@misofm/musicos/queries"; + import * as compositionCredits from "./contracts/composition_credits/composition_credits.ts"; import * as recordingAdvisory from "./contracts/recording_advisory/recording_advisory.ts"; import * as recordingCredits from "./contracts/recording_credits/recording_credits.ts"; @@ -20,30 +26,21 @@ import * as releaseDescription from "./contracts/release_description/release_des import * as releaseDspLink from "./contracts/release_dsp_link/release_dsp_link.ts"; import * as releaseGenre from "./contracts/release_genre/release_genre.ts"; import * as releaseKind from "./contracts/release_kind/release_kind.ts"; -import * as release from "./contracts/miso/release.ts"; import * as royaltyPool from "./contracts/royalty_pool/pool.ts"; import * as royaltyStake from "./contracts/royalty_pool/stake.ts"; import * as routedStake from "./contracts/routed_stake/routed_stake.ts"; -import type { BcsParser } from "./queries.ts"; - -/** Decode raw event BCS with the corresponding generated codec. */ -export function decodeEvent(codec: BcsParser, bytes: Uint8Array): T { - return codec.parse(bytes); -} function decoder(codec: BcsParser) { return (bytes: Uint8Array): T => decodeEvent(codec, bytes); } /** - * Event decoder registry grouped by package responsibility. It contains each - * event emitted by the current extension/primitive Move sources. Core - * publication events retain camel-case wrappers in `parsers.ts`. + * Platform event decoder registry, grouped by package responsibility: + * `extensions` (work extensions) and `primitives` (royalty pool / routed + * stake). Merge with `@misofm/musicos`'s `eventParsers.core` at the call site + * when a single indexer needs both. */ -export const eventParsers = { - core: { - releaseRegistryCreated: decoder(release.ReleaseRegistryCreatedEvent), - }, +export const platformEventParsers = { extensions: { compositionCredits: { creditAdded: decoder(compositionCredits.CreditAddedEvent), diff --git a/packages/platform/src/execute.ts b/packages/platform/src/execute.ts index d7fee73..91a8c0e 100644 --- a/packages/platform/src/execute.ts +++ b/packages/platform/src/execute.ts @@ -5,18 +5,18 @@ // to this package's opinionated publish flows (batched, non-idempotent share- // currency publishing in `share.ts`). Everything else — `buildTx`, // `signAndExecute`, `toExecResult`, the object-change extractors — is generic -// PTB execution plumbing that stays in `@misofm/protocol`'s `execute.ts` and is +// PTB execution plumbing that stays in `@misofm/musicos`'s `execute.ts` and is // re-exported from there; this module builds on top of it. import type { ParallelTransactionExecutor } from "@mysten/sui/transactions"; import type { SuiClientTypes } from "@mysten/sui/client"; -import { buildTx, toExecResult, FULL_INCLUDE, type ExecResult } from "@misofm/protocol"; +import { buildTx, toExecResult, FULL_INCLUDE, type ExecResult } from "@misofm/musicos"; import type { TxThunk } from "./transactions.ts"; // Consumers of the platform SDK should not need a second direct dependency on // the protocol SDK just to execute a composed PTB. Keep the generic execution // surface available through this platform-layer entry point as well. -export * from "@misofm/protocol/execute"; +export * from "@misofm/musicos/execute"; /** Publication execution also needs lifecycle events to associate same-typed * objects (notably several new Parties and Vaults) with their manifest refs. */ diff --git a/packages/platform/src/genre.ts b/packages/platform/src/genre.ts index 96676c8..4cae23f 100644 --- a/packages/platform/src/genre.ts +++ b/packages/platform/src/genre.ts @@ -48,9 +48,9 @@ import type { TxThunk } from "./transactions.ts"; import { directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput } from "./vault.ts"; import type { ReleaseExtensionTarget } from "./release-extensions.ts"; import type { RecordingExtensionTarget } from "./recording-extensions.ts"; -import * as genre from "@misofm/protocol/contracts/genre/genre"; -import * as releaseGenre from "@misofm/protocol/contracts/release_genre/release_genre"; -import * as recordingGenre from "@misofm/protocol/contracts/recording_genre/recording_genre"; +import * as genre from "./contracts/genre/genre.ts"; +import * as releaseGenre from "./contracts/release_genre/release_genre.ts"; +import * as recordingGenre from "./contracts/recording_genre/recording_genre.ts"; // ── Vocabulary helpers ─────────────────────────────────────────────────────── diff --git a/packages/platform/src/index.ts b/packages/platform/src/index.ts index d981179..76dc974 100644 --- a/packages/platform/src/index.ts +++ b/packages/platform/src/index.ts @@ -6,7 +6,7 @@ // Miso's own product sits on top of the permissionless protocol, and the two // ship as separate packages because they have different promises: // -// @misofm/protocol PROTOCOL — Composition, Recording, Release. The open layer +// @misofm/musicos PROTOCOL — Composition, Recording, Release. The open layer // anyone can build on, and nobody needs Miso's permission to use. // @misofm/platform PLATFORM — Pressing, Listing, Record, and the first-party // EXTENSIONS. How MISO sells copies of a release, and the @@ -53,6 +53,14 @@ export * from "./walrus-ids.ts"; export * from "./streaming.ts"; export * from "./walrus.ts"; +// Re-homed from the pre-split protocol package: extension/primitive package +// bindings, event decoders, and the generic royalty-pool / routed-stake +// queries and PTB builders (see the boundary rule above — these are all +// things Miso offers on top of the object model, not the model itself). +export * from "./packages.ts"; +export { platformEventParsers } from "./events.ts"; +export * from "./royalty.ts"; + // High-level, JSON-safe platform projections used directly by clients and by // the thin HTTP read service. Namespaced because these composed view types are // intentionally distinct from the lower-level contract views above. diff --git a/packages/platform/src/internal.ts b/packages/platform/src/internal.ts index 2ef96e6..3955188 100644 --- a/packages/platform/src/internal.ts +++ b/packages/platform/src/internal.ts @@ -9,8 +9,8 @@ import type { Transaction, TransactionArgument } from "@mysten/sui/transactions" // call needs an `Option` argument built inline. Consumed by `./cover` and // `./credits`. // -// These came over from `@misonetwork/sdk`'s own private `internal.ts` when the -// extension surface moved here. They were NOT promoted to that package's public +// These came over from the object-model package's own private `internal.ts` +// when the extension surface moved here. They were NOT promoted to that package's public // API to make them reachable: they are two literal Move function targets from the // standard library, so re-deriving them costs nothing, whereas exporting them // would widen the protocol SDK's public surface — and therefore its compatibility diff --git a/packages/protocol/src/packages.ts b/packages/platform/src/packages.ts similarity index 62% rename from packages/protocol/src/packages.ts rename to packages/platform/src/packages.ts index 64a5b5e..81b35b6 100644 --- a/packages/protocol/src/packages.ts +++ b/packages/platform/src/packages.ts @@ -2,19 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Package-bound generated APIs. + * Package-bound generated APIs for the platform layer: extensions, generic + * royalty/routed-stake primitives, and Party. The object-model core (composition, + * recording, release, track) has its own `MisoPackageBindings` in + * `@misofm/musicos/packages` — this class covers everything else. * * `@mysten/codegen` intentionally emits `@local-pkg/*` source labels. This - * adapter replaces those labels with the exact addresses from a complete Miso - * deployment manifest, so applications never have to hand-write a Move target. + * adapter replaces those labels with the exact addresses from a complete + * platform deployment manifest, so applications never have to hand-write a + * Move target. */ -import { normalizeMisoDeployment, type MisoDeployment } from "./deployments.ts"; - -import * as composition from "./contracts/miso/composition.ts"; -import * as recording from "./contracts/miso/recording.ts"; -import * as release from "./contracts/miso/release.ts"; -import * as track from "./contracts/miso/track.ts"; +import { bindModulePackage } from "@misofm/musicos/packages"; +// The Party core's generated bindings live in `@misofm/partyos` now (this +// package's own generated `partyos` tree is gone). Binding `call.party.core`/ +// `bcs.party.core` from partyos's curated barrel — rather than dropping them — +// keeps this package's single `MisoPlatformPackageBindings` facade covering +// every package a platform deployment addresses, core included, which is what +// `packages.test.ts` exercises. +import { party as partyCoreContracts } from "@misofm/partyos/contracts"; +import { + normalizeMisoPlatformDeployment, + type MisoPlatformDeployment, +} from "./deployments.ts"; import * as compositionCredits from "./contracts/composition_credits/composition_credits.ts"; import * as compositionPartyRole from "./contracts/composition_credits/composition_party_role.ts"; @@ -35,7 +45,6 @@ import * as royaltyPool from "./contracts/royalty_pool/pool.ts"; import * as royaltyStake from "./contracts/royalty_pool/stake.ts"; import * as routedStake from "./contracts/routed_stake/routed_stake.ts"; -import * as party from "./contracts/miso_party/party.ts"; import * as partyCta from "./contracts/party_cta/party_cta.ts"; import * as partyGenre from "./contracts/party_genre/party_genre.ts"; import * as partyMedia from "./contracts/party_media/party_media.ts"; @@ -47,14 +56,6 @@ import * as partyRoles from "./contracts/party_roles/party_roles.ts"; import * as partySocial from "./contracts/party_social/party_social.ts"; import * as partyTags from "./contracts/party_tags/party_tags.ts"; -type BoundMoveFunction = F extends (options: infer Options) => infer Result - ? Options extends { package?: unknown } - ? (options: Omit) => Result - : F - : F; -type BoundModule = { - [Key in Exclude]: BoundMoveFunction; -}; /** Generated modules mix BCS constants with transaction-builder functions. */ type CodecModule = { [Key in keyof M as M[Key] extends (...args: never[]) => unknown ? never : Key]: M[Key]; @@ -67,39 +68,12 @@ function codecsOnly(mod: M): CodecModule { ) as CodecModule; } -/** Defaults a generated module's optional `package` field to `packageId`. */ -export function bindModulePackage< - M extends object, - K extends readonly (keyof M)[] = readonly [], ->( - mod: M, - packageId: string, - unavailable: K = [] as unknown as K, -): BoundModule { - const bound: Record = {}; - for (const [key, value] of Object.entries(mod)) { - if ((unavailable as readonly string[]).includes(key)) continue; - bound[key] = - typeof value === "function" - ? (options: { package?: string }) => - (value as (input: unknown) => unknown)({ - ...options, - package: packageId, - }) - : value; - } - return bound as BoundModule; -} - /** * Public Move functions that return references. A PTB can borrow internally, * but a Move-call command result cannot carry a reference to a later command. * Keep them out of `call`; use object/dynamic-field BCS queries instead. */ export const REF_RETURNING_CALLS = { - composition: ["title", "uid", "uidMut"], - recording: ["uid", "uidMut"], - release: ["title", "tracks", "uid", "uidMut"], compositionCredits: ["credits"], recordingCredits: ["credits", "primaryArtistIds", "featuredArtistIds"], recordingMasterReference: ["masterReference"], @@ -119,100 +93,99 @@ export const REF_RETURNING_CALLS = { } as const; /** - * All generated functions and BCS codecs bound to one full, verified deployment. - * - * The `call` namespaces construct only caller-owned PTB commands. They neither - * set gas nor sign/execute. Query dynamic fields directly rather than using a - * Move function whose return type is a reference: PTB command outputs cannot - * carry references. + * All platform generated functions and BCS codecs bound to one full, verified + * deployment. The `call` namespaces construct only caller-owned PTB commands. + * They neither set gas nor sign/execute. Query dynamic fields directly rather + * than using a Move function whose return type is a reference: PTB command + * outputs cannot carry references. */ -export class MisoPackageBindings { - readonly deployment: MisoDeployment; +export class MisoPlatformPackageBindings { + readonly deployment: MisoPlatformDeployment; - constructor(deployment: MisoDeployment) { - this.deployment = normalizeMisoDeployment(deployment); + constructor(deployment: MisoPlatformDeployment) { + this.deployment = normalizeMisoPlatformDeployment(deployment); } get call() { - const d = this.deployment; + const p = this.deployment.packages; + const party_ = this.deployment.party; + const partyosPkg = this.deployment.partyos.partyos; return { - core: { - composition: bindModulePackage(composition, d.miso, REF_RETURNING_CALLS.composition), - recording: bindModulePackage(recording, d.miso, REF_RETURNING_CALLS.recording), - release: bindModulePackage(release, d.miso, REF_RETURNING_CALLS.release), - track: bindModulePackage(track, d.miso), - }, extensions: { compositionCredits: { compositionCredits: bindModulePackage( compositionCredits, - d.compositionCredits, + p.compositionCredits, REF_RETURNING_CALLS.compositionCredits, ), compositionPartyRole: bindModulePackage( compositionPartyRole, - d.compositionCredits, + p.compositionCredits, ), }, - recordingAdvisory: bindModulePackage(recordingAdvisory, d.recordingAdvisory), + recordingAdvisory: bindModulePackage(recordingAdvisory, p.recordingAdvisory), recordingCredits: { recordingCredits: bindModulePackage( recordingCredits, - d.recordingCredits, + p.recordingCredits, REF_RETURNING_CALLS.recordingCredits, ), recordingPartyRole: bindModulePackage( recordingPartyRole, - d.recordingCredits, + p.recordingCredits, ), }, - recordingLanguage: bindModulePackage(recordingLanguage, d.recordingLanguage), + recordingLanguage: bindModulePackage(recordingLanguage, p.recordingLanguage), recordingMasterReference: bindModulePackage( recordingMasterReference, - d.recordingMasterReference, + p.recordingMasterReference, REF_RETURNING_CALLS.recordingMasterReference, ), releaseCoverArt: bindModulePackage( releaseCoverArt, - d.releaseCoverArt, + p.releaseCoverArt, REF_RETURNING_CALLS.releaseCoverArt, ), releaseCredits: { releaseCredits: bindModulePackage( releaseCredits, - d.releaseCredits, + p.releaseCredits, REF_RETURNING_CALLS.releaseCredits, ), - releasePartyRole: bindModulePackage(releasePartyRole, d.releaseCredits), + releasePartyRole: bindModulePackage(releasePartyRole, p.releaseCredits), }, releaseDescription: bindModulePackage( releaseDescription, - d.releaseDescription, + p.releaseDescription, REF_RETURNING_CALLS.releaseDescription, ), - releaseDspLink: bindModulePackage(releaseDspLink, d.releaseDspLink), - releaseGenre: bindModulePackage(releaseGenre, d.releaseGenre), - releaseKind: bindModulePackage(releaseKind, d.releaseKind), + releaseDspLink: bindModulePackage(releaseDspLink, p.releaseDspLink), + releaseGenre: bindModulePackage(releaseGenre, p.releaseGenre), + releaseKind: bindModulePackage(releaseKind, p.releaseKind), }, primitives: { royaltyPool: { - pool: bindModulePackage(royaltyPool, d.royaltyPool, REF_RETURNING_CALLS.royaltyPool), - stake: bindModulePackage(royaltyStake, d.royaltyPool, REF_RETURNING_CALLS.royaltyStake), + pool: bindModulePackage(royaltyPool, p.royaltyPool, REF_RETURNING_CALLS.royaltyPool), + stake: bindModulePackage(royaltyStake, p.royaltyPool, REF_RETURNING_CALLS.royaltyStake), }, - routedStake: bindModulePackage(routedStake, d.routedStake, REF_RETURNING_CALLS.routedStake), + routedStake: bindModulePackage(routedStake, p.routedStake, REF_RETURNING_CALLS.routedStake), }, party: { - core: bindModulePackage(party, d.misoParty, REF_RETURNING_CALLS.party), - cta: bindModulePackage(partyCta, d.partyCta), - genre: bindModulePackage(partyGenre, d.partyGenre), - media: bindModulePackage(partyMedia, d.partyMedia), - music: bindModulePackage(partyMusic, d.partyMusic), - platformLink: bindModulePackage(partyPlatformLink, d.partyPlatformLink), - proLink: bindModulePackage(partyProLink, d.partyProLink), - profile: bindModulePackage(partyProfile, d.partyProfile, REF_RETURNING_CALLS.partyProfile), - roles: bindModulePackage(partyRoles, d.partyRoles), - social: bindModulePackage(partySocial, d.partySocial), - tags: bindModulePackage(partyTags, d.partyTags), + // Party core: `@misofm/partyos`'s curated `contracts.party` barrel is + // already stripped of reference-returning calls (its own + // PARTY_REF_RETURNING_CALLS, the same set as REF_RETURNING_CALLS.party + // below), so no `unavailable` list is passed here. + core: bindModulePackage(partyCoreContracts, partyosPkg), + cta: bindModulePackage(partyCta, party_.partyCta), + genre: bindModulePackage(partyGenre, party_.partyGenre), + media: bindModulePackage(partyMedia, party_.partyMedia), + music: bindModulePackage(partyMusic, party_.partyMusic), + platformLink: bindModulePackage(partyPlatformLink, party_.partyPlatformLink), + proLink: bindModulePackage(partyProLink, party_.partyProLink), + profile: bindModulePackage(partyProfile, party_.partyProfile, REF_RETURNING_CALLS.partyProfile), + roles: bindModulePackage(partyRoles, party_.partyRoles), + social: bindModulePackage(partySocial, party_.partySocial), + tags: bindModulePackage(partyTags, party_.partyTags), }, }; } @@ -220,12 +193,6 @@ export class MisoPackageBindings { /** Generated BCS codecs, grouped with the same ownership boundary as `call`. */ get bcs() { return { - core: { - composition: codecsOnly(composition), - recording: codecsOnly(recording), - release: codecsOnly(release), - track: codecsOnly(track), - }, extensions: { compositionCredits: { compositionCredits: codecsOnly(compositionCredits), @@ -253,7 +220,7 @@ export class MisoPackageBindings { routedStake: codecsOnly(routedStake), }, party: { - core: codecsOnly(party), + core: codecsOnly(partyCoreContracts), cta: codecsOnly(partyCta), genre: codecsOnly(partyGenre), media: codecsOnly(partyMedia), @@ -269,7 +236,9 @@ export class MisoPackageBindings { } } -/** Construct complete package bindings from an explicit verified manifest. */ -export function misoPackages(deployment: MisoDeployment): MisoPackageBindings { - return new MisoPackageBindings(deployment); +/** Construct complete platform package bindings from an explicit verified manifest. */ +export function misoPlatformPackages( + deployment: MisoPlatformDeployment, +): MisoPlatformPackageBindings { + return new MisoPlatformPackageBindings(deployment); } diff --git a/packages/protocol/src/party/client.ts b/packages/platform/src/party/client.ts similarity index 69% rename from packages/protocol/src/party/client.ts rename to packages/platform/src/party/client.ts index d992580..3a7c959 100644 --- a/packages/protocol/src/party/client.ts +++ b/packages/platform/src/party/client.ts @@ -1,14 +1,14 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// Party queries and transaction builders bound to the same complete deployment -// as `MisoProtocolClient`. Consumers reach this through `client.miso.party`. +// Party EXTENSIONS bound to one `PartyExtensionsDeployment`, wrapping a +// `PartyosClient` for the Party core. Consumers reach this through +// `client.miso.party`. import type { ClientWithCoreApi } from "@mysten/sui/client"; -import type { MisoDeployment } from "../deployments.ts"; -import type { TxThunk } from "./transactions.ts"; +import type { Party, PartyosClient } from "@misofm/partyos"; +import type { PartyExtensionsDeployment } from "../deployments.ts"; import * as queries from "./queries.ts"; -import * as transactions from "./transactions.ts"; import * as profileExt from "./extensions/profile.ts"; import * as mediaExt from "./extensions/media.ts"; import * as rolesExt from "./extensions/roles.ts"; @@ -16,8 +16,7 @@ import * as tagsExt from "./extensions/tags.ts"; import * as genresExt from "./extensions/genres.ts"; import * as ctasExt from "./extensions/ctas.ts"; import * as linksExt from "./extensions/links.ts"; -import type { Cta, Media, Party, PlatformKey, PlatformLink, Profile } from "./types.ts"; -import * as partyMod from "../contracts/miso_party/party.ts"; +import type { Cta, Media, PlatformKey, PlatformLink, Profile } from "./types.ts"; import * as profileMod from "../contracts/party_profile/party_profile.ts"; import * as mediaMod from "../contracts/party_media/party_media.ts"; import * as rolesMod from "../contracts/party_roles/party_roles.ts"; @@ -55,10 +54,15 @@ function bindModulePackage( return out as BoundModule; } -/** Package-bound Party client exposed at `client.miso.party`. */ -export class PartyProtocolClient { +/** + * Package-bound Party client exposed at `client.miso.party`. Wraps a + * `PartyosClient` for the Party core (identity, admin caps, group membership) + * and adds the first-party extensions (profile, media, roles, tags, genres, + * CTAs, platform links) this package owns. + */ +export class PartyPlatformClient { #client: ClientWithCoreApi; - #pkg: string; + #core: PartyosClient; #profilePkg: string; #countryCodePkg: string; #languageCodePkg: string; @@ -71,47 +75,59 @@ export class PartyProtocolClient { #socialPkg: string; #musicPkg: string; #proLinkPkg: string; - #genrePkg: string; - constructor(client: ClientWithCoreApi, o: MisoDeployment) { + constructor(client: ClientWithCoreApi, core: PartyosClient, extensions: PartyExtensionsDeployment) { this.#client = client; - this.#pkg = o.misoParty; - this.#profilePkg = o.partyProfile; - this.#countryCodePkg = o.countryCode; - this.#languageCodePkg = o.languageCode; - this.#mediaPkg = o.partyMedia; - this.#rolesPkg = o.partyRoles; - this.#tagsPkg = o.partyTags; - this.#partyGenrePkg = o.partyGenre; - this.#ctaPkg = o.partyCta; - this.#platformLinkPkg = o.partyPlatformLink; - this.#socialPkg = o.partySocial; - this.#musicPkg = o.partyMusic; - this.#proLinkPkg = o.partyProLink; - this.#genrePkg = o.genre; + this.#core = core; + this.#profilePkg = extensions.partyProfile; + this.#countryCodePkg = extensions.countryCode; + this.#languageCodePkg = extensions.languageCode; + this.#mediaPkg = extensions.partyMedia; + this.#rolesPkg = extensions.partyRoles; + this.#tagsPkg = extensions.partyTags; + this.#partyGenrePkg = extensions.partyGenre; + this.#ctaPkg = extensions.partyCta; + this.#platformLinkPkg = extensions.partyPlatformLink; + this.#socialPkg = extensions.partySocial; + this.#musicPkg = extensions.partyMusic; + this.#proLinkPkg = extensions.partyProLink; } - /** - * The `genre` vocabulary package id. Callers resolve `Genre` objects from it to - * pass to `tx.addGenre` (which takes a genre object id, not this package). - */ - get genrePackageId(): string { - return this.#genrePkg; + /** The wrapped Party-core client (identity, admin caps, group membership). */ + get core(): PartyosClient { + return this.#core; } - // === Queries === + // === Core (delegated to `PartyosClient`) === async getPartyById(partyId: string): Promise { - return queries.getPartyById(this.#client, partyId, this.#pkg); + return this.#core.getPartyById(partyId); } - async getPartiesByIds( - partyIds: readonly string[], - ): Promise>> { - return queries.getPartiesByIds(this.#client, partyIds, this.#pkg); + async getPartiesByIds(partyIds: readonly string[]): Promise>> { + return this.#core.getPartiesByIds(partyIds); } derivePartyAdminCapId(partyId: string): string { - return queries.derivePartyAdminCapId(partyId, this.#pkg); + return this.#core.derivePartyAdminCapId(partyId); } + /** Group ids a party belongs to (member-side membership records). */ + async getMemberships(partyId: string): Promise { + return this.#core.getMemberships(partyId); + } + /** Member ids invited to a group but not yet accepted. */ + async getPendingInvites(groupId: string): Promise { + return this.#core.getPendingInvites(groupId); + } + /** Group ids that have invited this party but are awaiting its response. */ + async getPendingMemberships(partyId: string): Promise { + return this.#core.getPendingMemberships(partyId); + } + /** Whether a party is a member of a group. */ + async isMember(memberId: string, groupId: string): Promise { + return this.#core.isMember(memberId, groupId); + } + + // === Extension queries === + async getProfile(partyId: string): Promise { return queries.getProfile(this.#client, partyId, this.#profilePkg); } @@ -138,27 +154,10 @@ export class PartyProtocolClient { async getLinks(partyId: string): Promise { return queries.getLinks(this.#client, partyId); } - /** Group ids a party belongs to (member-side membership records). */ - async getMemberships(partyId: string): Promise { - return queries.getMemberships(this.#client, partyId); - } - /** Member ids invited to a group but not yet accepted. */ - async getPendingInvites(groupId: string): Promise { - return queries.getPendingInvites(this.#client, groupId); - } - /** Group ids that have invited this party but are awaiting its response. */ - async getPendingMemberships(partyId: string): Promise { - return queries.getPendingMemberships(this.#client, partyId); - } - /** Whether a party is a member of a group. */ - async isMember(memberId: string, groupId: string): Promise { - return queries.isMember(this.#client, memberId, groupId, this.#pkg); - } // === Transaction builders (thunks; package ids bound from the client) === get tx() { - const pkg = this.#pkg; const profilePkg = this.#profilePkg; const cc = this.#countryCodePkg; const lc = this.#languageCodePkg; @@ -174,69 +173,53 @@ export class PartyProtocolClient { partyProLinkPackageId: this.#proLinkPkg, }; return { - createIndividualParty: (p: Omit): TxThunk => - transactions.createIndividualParty({ ...p, partyPackageId: pkg }), - createGroupParty: (p: Omit): TxThunk => - transactions.createGroupParty({ ...p, partyPackageId: pkg }), - setName: (p: Omit): TxThunk => - transactions.setName({ ...p, partyPackageId: pkg }), - inviteParty: (p: Omit): TxThunk => - transactions.inviteParty({ ...p, partyPackageId: pkg }), - acceptInvite: (p: Omit): TxThunk => - transactions.acceptInvite({ ...p, partyPackageId: pkg }), - declineInvite: (p: Omit): TxThunk => - transactions.declineInvite({ ...p, partyPackageId: pkg }), - revokeInvite: (p: Omit): TxThunk => - transactions.revokeInvite({ ...p, partyPackageId: pkg }), - leaveGroup: (p: Omit): TxThunk => - transactions.leaveGroup({ ...p, partyPackageId: pkg }), - removeMember: (p: Omit): TxThunk => - transactions.removeMember({ ...p, partyPackageId: pkg }), + // Party core builders — delegated straight from `PartyosClient`. + ...this.#core.tx, setProfile: ( p: Omit, - ): TxThunk => + ) => profileExt.setProfile({ ...p, partyProfilePackageId: profilePkg, countryCodePackageId: cc, languageCodePackageId: lc, }), - clearProfile: (p: Omit): TxThunk => + clearProfile: (p: Omit) => profileExt.clearProfile({ ...p, partyProfilePackageId: profilePkg }), - setMedia: (p: Omit): TxThunk => + setMedia: (p: Omit) => mediaExt.setMedia({ ...p, partyMediaPackageId: mediaPkg }), - clearMedia: (p: Omit): TxThunk => + clearMedia: (p: Omit) => mediaExt.clearMedia({ ...p, partyMediaPackageId: mediaPkg }), // Roles - addRole: (p: Omit): TxThunk => + addRole: (p: Omit) => rolesExt.addRole({ ...p, partyRolesPackageId: rolesPkg }), - removeRole: (p: Omit): TxThunk => + removeRole: (p: Omit) => rolesExt.removeRole({ ...p, partyRolesPackageId: rolesPkg }), - clearRoles: (p: Omit): TxThunk => + clearRoles: (p: Omit) => rolesExt.clearRoles({ ...p, partyRolesPackageId: rolesPkg }), // Tags - addTag: (p: Omit): TxThunk => + addTag: (p: Omit) => tagsExt.addTag({ ...p, partyTagsPackageId: tagsPkg }), - removeTag: (p: Omit): TxThunk => + removeTag: (p: Omit) => tagsExt.removeTag({ ...p, partyTagsPackageId: tagsPkg }), - clearTags: (p: Omit): TxThunk => + clearTags: (p: Omit) => tagsExt.clearTags({ ...p, partyTagsPackageId: tagsPkg }), // Genres - addGenre: (p: Omit): TxThunk => + addGenre: (p: Omit) => genresExt.addGenre({ ...p, partyGenrePackageId: partyGenrePkg }), - removeGenre: (p: Omit): TxThunk => + removeGenre: (p: Omit) => genresExt.removeGenre({ ...p, partyGenrePackageId: partyGenrePkg }), - clearGenres: (p: Omit): TxThunk => + clearGenres: (p: Omit) => genresExt.clearGenres({ ...p, partyGenrePackageId: partyGenrePkg }), // CTAs - setCtas: (p: Omit): TxThunk => + setCtas: (p: Omit) => ctasExt.setCtas({ ...p, partyCtaPackageId: ctaPkg }), - clearCtas: (p: Omit): TxThunk => + clearCtas: (p: Omit) => ctasExt.clearCtas({ ...p, partyCtaPackageId: ctaPkg }), // Platform links — generic (any platform) plus per-platform `set…`/`clear…`. - setLink: (platform: PlatformKey, p: linksExt.BoundSetLinkParams): TxThunk => + setLink: (platform: PlatformKey, p: linksExt.BoundSetLinkParams) => linksExt.setLink(platform, { ...p, ...linkIds }), - clearLink: (platform: PlatformKey, p: linksExt.BoundClearLinkParams): TxThunk => + clearLink: (platform: PlatformKey, p: linksExt.BoundClearLinkParams) => linksExt.clearLink(platform, { ...p, ...linkIds }), ...linksExt.linkTxBuilders(linkIds), }; @@ -246,7 +229,7 @@ export class PartyProtocolClient { get call() { return { - party: bindModulePackage(partyMod, this.#pkg, ["groupMembers", "uid", "uidMut"] as const), + party: this.#core.call.party, profile: bindModulePackage(profileMod, this.#profilePkg, ["profile"] as const), media: bindModulePackage(mediaMod, this.#mediaPkg), roles: bindModulePackage(rolesMod, this.#rolesPkg), @@ -264,8 +247,7 @@ export class PartyProtocolClient { get bcs() { return { - Party: partyMod.Party, - PartyAdminCap: partyMod.PartyAdminCap, + ...this.#core.bcs, Profile: profileMod.Profile, ProfileSetEvent: profileMod.ProfileSetEvent, Media: mediaMod.Media, @@ -281,3 +263,6 @@ export class PartyProtocolClient { }; } } + +/** @deprecated Use `PartyPlatformClient`. Retained for callers migrating from the pre-split class. */ +export { PartyPlatformClient as PartyProtocolClient }; diff --git a/packages/protocol/src/party/extensions/ctas.ts b/packages/platform/src/party/extensions/ctas.ts similarity index 96% rename from packages/protocol/src/party/extensions/ctas.ts rename to packages/platform/src/party/extensions/ctas.ts index 90d2477..96e623a 100644 --- a/packages/protocol/src/party/extensions/ctas.ts +++ b/packages/platform/src/party/extensions/ctas.ts @@ -7,7 +7,7 @@ // and passes the vector to `set_ctas`. import * as ctaMod from "../../contracts/party_cta/party_cta.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; import type { Cta } from "../types.ts"; export interface SetCtasParams { diff --git a/packages/protocol/src/party/extensions/genres.ts b/packages/platform/src/party/extensions/genres.ts similarity index 97% rename from packages/protocol/src/party/extensions/genres.ts rename to packages/platform/src/party/extensions/genres.ts index d0fdf1c..b849612 100644 --- a/packages/protocol/src/party/extensions/genres.ts +++ b/packages/platform/src/party/extensions/genres.ts @@ -6,7 +6,7 @@ // entry), so the builder passes `tx.object(genreId)`; removal is by id. import * as genreMod from "../../contracts/party_genre/party_genre.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; export interface AddGenreParams { partyId: string; diff --git a/packages/protocol/src/party/extensions/links.ts b/packages/platform/src/party/extensions/links.ts similarity index 99% rename from packages/protocol/src/party/extensions/links.ts rename to packages/platform/src/party/extensions/links.ts index 842034c..c23b46b 100644 --- a/packages/protocol/src/party/extensions/links.ts +++ b/packages/platform/src/party/extensions/links.ts @@ -11,7 +11,7 @@ // Only the native identifier is stored on-chain; the public URL is rebuilt from it. import * as platformLinkMod from "../../contracts/party_platform_link/party_platform_link.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; import type { PlatformKey, PlatformLink } from "../types.ts"; /** Which payload package a platform's `Data` type + constructor live in. */ diff --git a/packages/protocol/src/party/extensions/media.ts b/packages/platform/src/party/extensions/media.ts similarity index 98% rename from packages/protocol/src/party/extensions/media.ts rename to packages/platform/src/party/extensions/media.ts index 769b219..ba05d61 100644 --- a/packages/protocol/src/party/extensions/media.ts +++ b/packages/platform/src/party/extensions/media.ts @@ -10,7 +10,7 @@ import { b64UrlToU256 } from "../internal.ts"; import * as mediaMod from "../../contracts/party_media/party_media.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; import type { Media } from "../types.ts"; /** diff --git a/packages/protocol/src/party/extensions/profile.ts b/packages/platform/src/party/extensions/profile.ts similarity index 97% rename from packages/protocol/src/party/extensions/profile.ts rename to packages/platform/src/party/extensions/profile.ts index 65c012e..0ad7d7a 100644 --- a/packages/protocol/src/party/extensions/profile.ts +++ b/packages/platform/src/party/extensions/profile.ts @@ -6,7 +6,7 @@ // `CountryCode` / `LanguageCode` values from raw ISO codes. import * as profileMod from "../../contracts/party_profile/party_profile.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; const OPTION_NONE = "0x1::option::none"; const OPTION_SOME = "0x1::option::some"; diff --git a/packages/protocol/src/party/extensions/roles.ts b/packages/platform/src/party/extensions/roles.ts similarity index 98% rename from packages/protocol/src/party/extensions/roles.ts rename to packages/platform/src/party/extensions/roles.ts index a5b8fa4..1cb96d2 100644 --- a/packages/protocol/src/party/extensions/roles.ts +++ b/packages/platform/src/party/extensions/roles.ts @@ -7,7 +7,7 @@ // `ArtistRole` value via its constructor move-call, then add/remove it. import * as rolesMod from "../../contracts/party_roles/party_roles.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; /** The canonical role variants (a fixed, typo-free set the frontend can render). */ export type CanonicalRole = diff --git a/packages/protocol/src/party/extensions/tags.ts b/packages/platform/src/party/extensions/tags.ts similarity index 96% rename from packages/protocol/src/party/extensions/tags.ts rename to packages/platform/src/party/extensions/tags.ts index 746f2a3..0f8f20d 100644 --- a/packages/protocol/src/party/extensions/tags.ts +++ b/packages/platform/src/party/extensions/tags.ts @@ -6,7 +6,7 @@ // concern. import * as tagsMod from "../../contracts/party_tags/party_tags.ts"; -import type { TxThunk } from "../transactions.ts"; +import type { TxThunk } from "@misofm/partyos"; export interface AddTagParams { partyId: string; diff --git a/packages/platform/src/party/index.ts b/packages/platform/src/party/index.ts new file mode 100644 index 0000000..64895b2 --- /dev/null +++ b/packages/platform/src/party/index.ts @@ -0,0 +1,40 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./types.ts"; +export * from "./queries.ts"; +export * from "./client.ts"; +export * from "./extensions/profile.ts"; +export * from "./extensions/media.ts"; +export * from "./extensions/roles.ts"; +export * from "./extensions/tags.ts"; +export * from "./extensions/genres.ts"; +export * from "./extensions/ctas.ts"; +export * from "./extensions/links.ts"; + +// === Party core — owned by `@misofm/partyos` === +// Re-exported here so `@misofm/platform/party` remains a one-stop import for +// Party work. The Party object model (identity, admin caps, group membership) +// itself lives in `@misofm/partyos`; this package only owns the extensions. +export { + createIndividualParty, + createGroupParty, + setName, + inviteParty, + acceptInvite, + declineInvite, + revokeInvite, + leaveGroup, + removeMember, + type Party, + type PartyKind, + type TxThunk, + type CreatePartyParams, + type SetNameParams, + type InvitePartyParams, + type AcceptInviteParams, + type DeclineInviteParams, + type RevokeInviteParams, + type LeaveGroupParams, + type RemoveMemberParams, +} from "@misofm/partyos"; diff --git a/packages/protocol/src/party/internal.ts b/packages/platform/src/party/internal.ts similarity index 81% rename from packages/protocol/src/party/internal.ts rename to packages/platform/src/party/internal.ts index bc49de7..b170f10 100644 --- a/packages/protocol/src/party/internal.ts +++ b/packages/platform/src/party/internal.ts @@ -6,7 +6,7 @@ import { bcs } from "@mysten/sui/bcs"; import { fromBase64, toBase64 } from "@mysten/sui/utils"; -import type { Cta, Media, Party, Profile } from "./types.ts"; +import type { Cta, Media, Profile } from "./types.ts"; const u256 = bcs.u256(); @@ -100,20 +100,3 @@ export function mapGenres(d: any): string[] { export function mapCtas(d: any): Cta[] { return (Array.isArray(d) ? d : []).map((c: any) => ({ label: String(c.label), url: String(c.url) })); } - -// deno-lint-ignore no-explicit-any -- generated parse output is loosely typed -export function mapParty(id: string, d: any): Party { - const kind = d.kind; - const createdAtMs = Number(d.created_at_ms); - // PartyKind is `Individual | Group(VecSet)`. The enum parses to - // `{ $kind, Individual? , Group? }`; Group's payload is a VecSet `{ contents }`. - const groupPayload = kind?.Group ?? (kind?.$kind === "Group" ? kind.value : undefined); - if (groupPayload !== undefined) { - const contents = groupPayload?.contents ?? groupPayload ?? []; - const members = (Array.isArray(contents) ? contents : []).map((m: unknown) => - typeof m === "string" ? m : (m as { id?: string })?.id ?? String(m), - ); - return { id, kind: "group", name: d.name, members, createdAtMs }; - } - return { id, kind: "individual", name: d.name, createdAtMs }; -} diff --git a/packages/platform/src/party/queries.ts b/packages/platform/src/party/queries.ts new file mode 100644 index 0000000..5f8a04d --- /dev/null +++ b/packages/platform/src/party/queries.ts @@ -0,0 +1,163 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Typed reads for the Party EXTENSIONS: fetch an on-chain dynamic field via the +// Core API, BCS-parse it through the generated struct, and map to the public +// camelCase types. Party core reads (`getPartyById`, group membership, …) live +// in `@misofm/partyos` — this file only covers profile/media/roles/tags/genres/ +// ctas/links. + +import type { ClientWithCoreApi } from "@mysten/sui/client"; +import { deriveDynamicFieldID } from "@mysten/sui/utils"; +import { bcs } from "@mysten/sui/bcs"; +import { getObjectContent } from "@misofm/partyos"; +import { Profile as ProfileBcs, ProfileKey as ProfileKeyBcs } from "../contracts/party_profile/party_profile.ts"; +import { Media as MediaBcs, MediaKey as MediaKeyBcs } from "../contracts/party_media/party_media.ts"; +import { ArtistRole as ArtistRoleBcs, RolesKey as RolesKeyBcs } from "../contracts/party_roles/party_roles.ts"; +import { TagsKey as TagsKeyBcs } from "../contracts/party_tags/party_tags.ts"; +import { GenresKey as GenresKeyBcs } from "../contracts/party_genre/party_genre.ts"; +import { Cta as CtaBcs, CtasKey as CtasKeyBcs } from "../contracts/party_cta/party_cta.ts"; +import { mapCtas, mapGenres, mapMedia, mapProfile, mapRoles, mapTags } from "./internal.ts"; +import { buildLink, platformForDataType } from "./extensions/links.ts"; +import type { Cta, Media, PlatformLink, Profile } from "./types.ts"; + +// The profile is a dynamic field on the party UID under an empty `ProfileKey()` +// (Move's implicit `dummy_field: bool` → one zero byte). +const ProfileField = bcs.struct("Field", { id: bcs.Address, name: ProfileKeyBcs, value: ProfileBcs }); +const PROFILE_KEY_BYTES = ProfileKeyBcs.serialize([false]).toBytes(); + +/** Fetches a party's profile, or null if none is set. */ +export async function getProfile( + client: ClientWithCoreApi, + partyId: string, + partyProfilePackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID( + partyId, + `${partyProfilePackageId}::party_profile::ProfileKey`, + PROFILE_KEY_BYTES, + ); + const content = await getObjectContent(client, fieldId); + if (!content) return null; + return mapProfile(partyId, ProfileField.parse(content).value); +} + +// Media is a dynamic field on the party UID under an empty `MediaKey()` (same +// implicit `dummy_field: bool` → one zero byte as the profile key). +const MediaField = bcs.struct("Field", { id: bcs.Address, name: MediaKeyBcs, value: MediaBcs }); +const MEDIA_KEY_BYTES = MediaKeyBcs.serialize([false]).toBytes(); + +/** Fetches a party's media (avatar + header), or null if none is set. */ +export async function getMedia( + client: ClientWithCoreApi, + partyId: string, + partyMediaPackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID( + partyId, + `${partyMediaPackageId}::party_media::MediaKey`, + MEDIA_KEY_BYTES, + ); + const content = await getObjectContent(client, fieldId); + if (!content) return null; + return mapMedia(partyId, MediaField.parse(content).value); +} + +// === Collection extension reads (roles / tags / genres / ctas) === +// +// Each is a single dynamic field on the party UID under an empty positional key +// (Move's implicit `dummy_field: bool` → one zero byte, same as the profile key). + +const ROLES_KEY_BYTES = RolesKeyBcs.serialize([false]).toBytes(); +const RolesField = bcs.struct("Field", { id: bcs.Address, name: RolesKeyBcs, value: bcs.vector(ArtistRoleBcs) }); + +/** The party's artist-type roles (display names), or `[]` if none are set. */ +export async function getRoles( + client: ClientWithCoreApi, + partyId: string, + partyRolesPackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID(partyId, `${partyRolesPackageId}::party_roles::RolesKey`, ROLES_KEY_BYTES); + const content = await getObjectContent(client, fieldId); + if (!content) return []; + return mapRoles(RolesField.parse(content).value); +} + +const TAGS_KEY_BYTES = TagsKeyBcs.serialize([false]).toBytes(); +const TagsField = bcs.struct("Field", { id: bcs.Address, name: TagsKeyBcs, value: bcs.vector(bcs.string()) }); + +/** The party's free-form tags, or `[]` if none are set. */ +export async function getTags( + client: ClientWithCoreApi, + partyId: string, + partyTagsPackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID(partyId, `${partyTagsPackageId}::party_tags::TagsKey`, TAGS_KEY_BYTES); + const content = await getObjectContent(client, fieldId); + if (!content) return []; + return mapTags(TagsField.parse(content).value); +} + +const GENRES_KEY_BYTES = GenresKeyBcs.serialize([false]).toBytes(); +const GenresField = bcs.struct("Field", { id: bcs.Address, name: GenresKeyBcs, value: bcs.vector(bcs.Address) }); + +/** The party's genre object ids, or `[]` if none are set. */ +export async function getGenres( + client: ClientWithCoreApi, + partyId: string, + partyGenrePackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID(partyId, `${partyGenrePackageId}::party_genre::GenresKey`, GENRES_KEY_BYTES); + const content = await getObjectContent(client, fieldId); + if (!content) return []; + return mapGenres(GenresField.parse(content).value); +} + +const CTAS_KEY_BYTES = CtasKeyBcs.serialize([false]).toBytes(); +// The CTA field's value is a bare `vector` (not a wrapper struct). +const CtasField = bcs.struct("Field", { id: bcs.Address, name: CtasKeyBcs, value: bcs.vector(CtaBcs) }); + +/** The party's ordered CTA list (position is priority), or `[]` if none is set. */ +export async function getCtas( + client: ClientWithCoreApi, + partyId: string, + partyCtaPackageId: string, +): Promise { + const fieldId = deriveDynamicFieldID(partyId, `${partyCtaPackageId}::party_cta::CtasKey`, CTAS_KEY_BYTES); + const content = await getObjectContent(client, fieldId); + if (!content) return []; + return mapCtas(CtasField.parse(content).value); +} + +// === Platform-link reads === +// +// Each platform is an independent `PlatformLink` dynamic field keyed by +// `platform_link::PlatformLinkKey`. There is no single key to derive, so +// enumerate the party's dynamic fields and pick out the platform-link ones, +// identifying the platform from the `Data` type argument. Every payload `Data` +// type is a single-`String` newtype, so `PlatformLink` serializes exactly +// as one BCS string — parse the field value as such. +const PlatformLinkField = bcs.struct("Field", { id: bcs.Address, name: bcs.bool(), value: bcs.string() }); +const PLATFORM_LINK_KEY_RE = /::platform_link::PlatformLinkKey<(.+)>$/; + +/** All external-platform links attached to a party (social, music, professional). */ +export async function getLinks(client: ClientWithCoreApi, partyId: string): Promise { + const links: PlatformLink[] = []; + let cursor: string | null | undefined; + do { + const page = await client.core.listDynamicFields({ parentId: partyId, cursor: cursor ?? undefined }); + for (const f of page.dynamicFields) { + if (typeof f.name?.type !== "string") continue; + const match = f.name.type.match(PLATFORM_LINK_KEY_RE); + const dataType = match?.[1]; + if (!dataType) continue; + const platform = platformForDataType(dataType); + if (!platform) continue; + const content = await getObjectContent(client, f.fieldId); + if (!content) continue; + links.push(buildLink(platform, PlatformLinkField.parse(content).value)); + } + cursor = page.hasNextPage ? page.cursor : null; + } while (cursor); + return links; +} diff --git a/packages/protocol/src/party/types.ts b/packages/platform/src/party/types.ts similarity index 81% rename from packages/protocol/src/party/types.ts rename to packages/platform/src/party/types.ts index 26d451b..1f33f67 100644 --- a/packages/protocol/src/party/types.ts +++ b/packages/platform/src/party/types.ts @@ -1,21 +1,10 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// Public, camelCase result types. The mappers in ./internal.ts turn the generated -// (snake_case, Move-shaped) parse output into these. - -export type PartyKind = "individual" | "group"; - -export interface Party { - id: string; - kind: PartyKind; - /** Human-readable name (not verified). */ - name: string; - /** Member party ids — present only when `kind === "group"`. */ - members?: string[]; - /** Unix ms when the party was created. */ - createdAtMs: number; -} +// Public, camelCase result types for the Party EXTENSIONS. The mappers in +// ./internal.ts turn the generated (snake_case, Move-shaped) parse output into +// these. The Party object model itself (`Party`, `PartyKind`) is owned by +// `@misofm/partyos` — import it from there. export interface Profile { partyId: string; diff --git a/packages/platform/src/pressing.ts b/packages/platform/src/pressing.ts index 704a04d..da303b5 100644 --- a/packages/platform/src/pressing.ts +++ b/packages/platform/src/pressing.ts @@ -1,8 +1,8 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// Primary Record sales. `miso_record` owns concrete Records and edition-scoped -// Pressings; immutable `miso_record_shop` owns per-currency Listings and payment. +// Primary Record sales. `record` owns concrete Records and edition-scoped +// Pressings; immutable `record_shop` owns per-currency Listings and payment. import type { ClientWithCoreApi } from "@mysten/sui/client"; import { @@ -14,9 +14,9 @@ import { import type { TxThunk } from "./transactions.ts"; import { isNotFound } from "./queries.ts"; import { asU64, type U64Input } from "./vault.ts"; -import * as pressingContract from "@misofm/protocol/contracts/miso_record/pressing"; -import * as recordContract from "@misofm/protocol/contracts/miso_record/record"; -import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing"; +import * as pressingContract from "./contracts/record/pressing.ts"; +import * as recordContract from "./contracts/record/record.ts"; +import * as listingContract from "./contracts/record_shop/listing.ts"; const MAX_U16 = 0xffff; const MAX_U32 = 0xffff_ffff; diff --git a/packages/platform/src/publication.ts b/packages/platform/src/publication.ts index 658b907..b950721 100644 --- a/packages/platform/src/publication.ts +++ b/packages/platform/src/publication.ts @@ -21,8 +21,9 @@ import { deriveCompositionAdminCapId, deriveRecordingAdminCapId, deriveReleaseAdminCapId, -} from "@misofm/protocol"; -import { derivePartyAdminCapId } from "@misofm/protocol/party"; +} from "@misofm/musicos"; +import { party } from "@misofm/partyos/contracts"; +import { derivePartyAdminCapId } from "@misofm/partyos"; import { requireOperationsDeployment, type AvailableOperationsDeployment, @@ -88,10 +89,10 @@ import { } from "./pressing.ts"; import { requireRecordSalesDeployment } from "./deployments.ts"; import { allCreatedByType, createdByExactType, type PlatformExecResult } from "./execute.ts"; -import * as pressingContract from "@misofm/protocol/contracts/miso_record/pressing"; -import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing"; +import * as pressingContract from "./contracts/record/pressing.ts"; +import * as listingContract from "./contracts/record_shop/listing.ts"; -const { composition, recording, release, track, party } = protocolContracts; +const { composition, recording, release, track } = protocolContracts; export const MAX_ATOMIC_PUBLICATION_COMMANDS = 900; export const MAX_ATOMIC_PUBLICATION_INPUTS = 2048; @@ -274,19 +275,19 @@ function requiredAt(items: readonly T[], index: number, description: string): } function partyCapType(p: AtomicPublicationParams): string { - return `${p.deployment.protocol.misoParty}::party::PartyAdminCap`; + return `${p.deployment.partyos.partyos}::party::PartyAdminCap`; } function compositionCapType(p: AtomicPublicationParams, shareType: string): string { - return `${p.deployment.protocol.miso}::composition::CompositionAdminCap<${shareType}>`; + return `${p.deployment.protocol.musicos}::composition::CompositionAdminCap<${shareType}>`; } function recordingCapType(p: AtomicPublicationParams, shareType: string): string { - return `${p.deployment.protocol.miso}::recording::RecordingAdminCap<${shareType}>`; + return `${p.deployment.protocol.musicos}::recording::RecordingAdminCap<${shareType}>`; } function releaseCapType(p: AtomicPublicationParams): string { - return `${p.deployment.protocol.miso}::release::ReleaseAdminCap`; + return `${p.deployment.protocol.musicos}::release::ReleaseAdminCap`; } function pressingCapType(p: AtomicPublicationParams): string { @@ -339,11 +340,11 @@ function partyByRef( function languageVector(tx: Transaction, p: AtomicPublicationParams, codes: string[]) { const values = codes.map((code) => tx.moveCall({ - target: `${p.deployment.protocol.languageCode}::language_code::new`, + target: `${p.deployment.packages.languageCode}::language_code::new`, arguments: [tx.pure.string(code)], })); return tx.makeMoveVec({ - type: `${p.deployment.protocol.languageCode}::language_code::LanguageCode`, + type: `${p.deployment.packages.languageCode}::language_code::LanguageCode`, elements: values, }); } @@ -455,7 +456,7 @@ function publishComposition( const typeArguments: [string] = [node.shareType]; if (node.custody.kind === "direct") { tx.add(composition.publish({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, typeArguments, arguments: [parts.work, parts.adminCap], })); @@ -502,7 +503,7 @@ function publishComposition( capType: compositionCapType(p, node.shareType), vaultPackageId: available!.vault.packageId, }, { - target: `${p.deployment.protocol.miso}::composition::publish`, + target: `${p.deployment.protocol.musicos}::composition::publish`, typeArguments, arguments: [parts.work, tx.object.clock()], adminCapIndex: 1, @@ -524,7 +525,7 @@ function publishRecording( const stakes = allocatePublicationShares(tx, p, node, parts.balance); if (node.custody.kind === "direct") { tx.add(recording.publish({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, typeArguments, arguments: [parts.work, parts.adminCap], })); @@ -604,7 +605,7 @@ function publishRecording( capType: recordingCapType(p, node.shareType), vaultPackageId: available!.vault.packageId, }, { - target: `${p.deployment.protocol.miso}::recording::publish`, + target: `${p.deployment.protocol.musicos}::recording::publish`, typeArguments, arguments: [parts.work, tx.object.clock()], adminCapIndex: 1, @@ -624,7 +625,7 @@ function publishReleaseObject( const available = node.custody.kind === "vault" ? operations(p) : undefined; if (node.custody.kind === "direct") { tx.add(release.publish({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, arguments: [releaseObject, adminCap], })); disposeNewAdminCap(tx, adminCap, custody(p, node.custody, releaseCapType(p))); @@ -648,7 +649,7 @@ function publishReleaseObject( capType: releaseCapType(p), vaultPackageId: available!.vault.packageId, }, { - target: `${p.deployment.protocol.miso}::release::publish`, + target: `${p.deployment.protocol.musicos}::release::publish`, arguments: [releaseObject, tx.object.clock()], adminCapIndex: 1, }); @@ -725,10 +726,10 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { continue; } const kind = tx.add(node.create === "group" - ? party.newGroupKind({ package: p.deployment.protocol.misoParty }) - : party.newIndividualKind({ package: p.deployment.protocol.misoParty })); + ? party.newGroupKind({ package: p.deployment.partyos.partyos }) + : party.newIndividualKind({ package: p.deployment.partyos.partyos })); const created = tx.add(party._new({ - package: p.deployment.protocol.misoParty, + package: p.deployment.partyos.partyos, arguments: [kind, node.name], })); const parts = { @@ -741,7 +742,7 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { const compositions: WorkParts[] = p.compositions.map((node) => { const created = tx.add(composition._new({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, typeArguments: [node.shareType], arguments: [node.title, node.royaltyRateBps, node.shareCurrencyId, node.shareTreasuryCapId], })); @@ -757,7 +758,7 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { ? requiredAt(compositions, node.parentCompositionIndex, "parent composition").work : tx.object(node.parentCompositionId); const created = tx.add(recording._new({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, typeArguments: [node.shareType, node.compositionShareType], arguments: [parent, node.shareCurrencyId, node.shareTreasuryCapId], })); @@ -774,12 +775,12 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { const ids = p.release.tracks.map((node) => "recordingIndex" in node ? tx.moveCall({ target: "0x2::object::id", - typeArguments: [`${p.deployment.protocol.miso}::recording::Recording<${p.recordings[node.recordingIndex]!.shareType},${p.recordings[node.recordingIndex]!.compositionShareType}>`], + typeArguments: [`${p.deployment.protocol.musicos}::recording::Recording<${p.recordings[node.recordingIndex]!.shareType},${p.recordings[node.recordingIndex]!.compositionShareType}>`], arguments: [requiredAt(recordings, node.recordingIndex, "fresh recording").work], }) : tx.pure.id(node.recordingId)); const targetReleaseId = tx.moveCall({ - target: `${p.deployment.protocol.miso}::release::derive_target_release_id`, + target: `${p.deployment.protocol.musicos}::release::derive_target_release_id`, arguments: [ tx.object(p.deployment.objects.releaseRegistry), tx.makeMoveVec({ type: "0x2::object::ID", elements: ids }), @@ -792,24 +793,24 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { const recNode = requiredAt(p.recordings, node.recordingIndex, "fresh recording"); const rec = requiredAt(recordings, node.recordingIndex, "fresh recording"); return tx.add(track._new({ - package: p.deployment.protocol.miso, + package: p.deployment.protocol.musicos, typeArguments: [recNode.shareType, recNode.compositionShareType], arguments: [rec.adminCap, rec.work, targetReleaseId, node.splitBps], })); } return invokeWithAdminCap(tx, node.authority, { - target: `${p.deployment.protocol.miso}::track::new`, + target: `${p.deployment.protocol.musicos}::track::new`, typeArguments: [node.recordingShareType, node.compositionShareType], arguments: [tx.object(node.recordingId), targetReleaseId, tx.pure.u16(node.splitBps)], adminCapIndex: 0, }); }); const created = tx.moveCall({ - target: `${p.deployment.protocol.miso}::release::new`, + target: `${p.deployment.protocol.musicos}::release::new`, arguments: [ tx.object(p.deployment.objects.releaseRegistry), tx.pure.string(p.release.title), - tx.makeMoveVec({ type: `${p.deployment.protocol.miso}::track::Track`, elements: tracks }), + tx.makeMoveVec({ type: `${p.deployment.protocol.musicos}::track::Track`, elements: tracks }), tx.pure.u256(BigInt(p.release.nonce)), ], }); @@ -1035,7 +1036,7 @@ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk { if ("id" in node) continue; const parts = createdParties.get(node.ref)!; tx.add(party.share({ - package: p.deployment.protocol.misoParty, + package: p.deployment.partyos.partyos, arguments: [parts.party, parts.adminCap], })); disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, partyCapType(p))); @@ -1164,15 +1165,15 @@ export function parseAtomicPublicationResult( if ("id" in node) { parties[node.ref] = { id: node.id, - adminCapId: derivePartyAdminCapId(node.id, p.deployment.protocol.misoParty), + adminCapId: derivePartyAdminCapId(node.id, p.deployment.partyos.partyos), created: false, }; } } createdPartyNodes.forEach((node, index) => { const event = partyEvents[index]!; - const parsed = protocolContracts.party.PartyCreatedEvent.parse(event.bcs); - const adminCapId = derivePartyAdminCapId(parsed.party_id, p.deployment.protocol.misoParty); + const parsed = party.PartyCreatedEvent.parse(event.bcs); + const adminCapId = derivePartyAdminCapId(parsed.party_id, p.deployment.partyos.partyos); parties[node.ref] = { id: parsed.party_id, adminCapId, @@ -1184,7 +1185,7 @@ export function parseAtomicPublicationResult( const compositions: AtomicPublicationResult["compositions"] = {}; p.compositions.forEach((node) => { const id = findByShareType(createdCompositions, node.shareType, "Composition"); - const adminCapId = deriveCompositionAdminCapId(id, p.deployment.protocol.miso); + const adminCapId = deriveCompositionAdminCapId(id, p.deployment.protocol.musicos); compositions[node.ref] = { id, adminCapId, @@ -1197,7 +1198,7 @@ export function parseAtomicPublicationResult( const recordings: AtomicPublicationResult["recordings"] = {}; p.recordings.forEach((node) => { const id = findByShareType(createdRecordings, node.shareType, "Recording"); - const adminCapId = deriveRecordingAdminCapId(id, p.deployment.protocol.miso); + const adminCapId = deriveRecordingAdminCapId(id, p.deployment.protocol.musicos); recordings[node.ref] = { id, adminCapId, @@ -1216,8 +1217,8 @@ export function parseAtomicPublicationResult( let releaseOut: AtomicPublicationResult["release"]; if (p.release) { - const id = createdByExactType(result, `${p.deployment.protocol.miso}::release::Release`); - const adminCapId = deriveReleaseAdminCapId(id, p.deployment.protocol.miso); + const id = createdByExactType(result, `${p.deployment.protocol.musicos}::release::Release`); + const adminCapId = deriveReleaseAdminCapId(id, p.deployment.protocol.musicos); releaseOut = { id, adminCapId, diff --git a/packages/platform/src/queries.ts b/packages/platform/src/queries.ts index 3a55ce4..8ec3b17 100644 --- a/packages/platform/src/queries.ts +++ b/packages/platform/src/queries.ts @@ -5,5 +5,5 @@ // `isNotFound` is the protocol SDK's — the not-found taxonomy is a property of // the Sui transports, not of any one package's objects, so it is defined once in -// `@misofm/protocol` and re-exported here for the platform readers. -export { isNotFound } from "@misofm/protocol"; +// `@misofm/musicos` and re-exported here for the platform readers. +export { isNotFound } from "@misofm/musicos"; diff --git a/packages/platform/src/read/artist.ts b/packages/platform/src/read/artist.ts index 1834685..22b5c8a 100644 --- a/packages/platform/src/read/artist.ts +++ b/packages/platform/src/read/artist.ts @@ -41,7 +41,7 @@ export async function getArtistProfile( options: GetArtistOptions = {}, ): Promise { const include = new Set(options.include ?? []); - const { party } = client.sui.miso; + const { party } = client; const [profile, ctas, genreIds, links, roles, tags, entity] = await Promise.all([ @@ -95,7 +95,7 @@ async function resolveMembers( memberIds: readonly string[], ): Promise { if (memberIds.length === 0) return []; - const parties = await client.sui.miso.party + const parties = await client.party .getPartiesByIds([...memberIds]) .catch(() => ({}) as Record); return memberIds.flatMap((id) => { @@ -110,7 +110,7 @@ export async function getPartySummaries( ids: readonly string[], ): Promise { if (ids.length === 0) return []; - const parties = await client.sui.miso.party.getPartiesByIds([...ids]); + const parties = await client.party.getPartiesByIds([...ids]); return ids.flatMap((id) => { const p = parties[id]; return p ? [{ id, name: p.name, kind: p.kind }] : []; diff --git a/packages/platform/src/read/catalog.ts b/packages/platform/src/read/catalog.ts index fd43f85..ba1f707 100644 --- a/packages/platform/src/read/catalog.ts +++ b/packages/platform/src/read/catalog.ts @@ -44,8 +44,8 @@ import { type RecordingEngineSessionView, } from "../recording-extensions.ts"; import { getTrackCreditsByRecordingIds } from "../catalog.ts"; -import { getReleaseById, getReleasesByIds, isNotFound } from "@misofm/protocol"; -import type { Release } from "@misofm/protocol"; +import { getReleaseById, getReleasesByIds, isNotFound } from "@misofm/musicos"; +import type { Release } from "@misofm/musicos"; import type { MisoClient } from "./client.ts"; import { getRecordingTitles, parseReleaseObject } from "./works.ts"; import { int } from "./internal/scalars.ts"; @@ -398,7 +398,7 @@ export async function getReleaseDetail( client.protocol, client.graphql, recordingIds, - client.config.deployment.miso, + client.config.deployment.musicos, ), getRecordingMasterReferencesByIds( client.protocol, @@ -455,7 +455,7 @@ async function getTrackCreditsForRecordingIds( client.graphql, recordingIds, { - misoPackageId: client.config.deployment.miso, + misoPackageId: client.config.deployment.musicos, compositionCreditsPackageId: compositionCredits, recordingCreditsPackageId: recordingCredits, }, diff --git a/packages/platform/src/read/client.ts b/packages/platform/src/read/client.ts index 98c8ccf..a0856b0 100644 --- a/packages/platform/src/read/client.ts +++ b/packages/platform/src/read/client.ts @@ -18,7 +18,9 @@ import { SuiGrpcClient } from "@mysten/sui/grpc"; import { SuiGraphQLClient } from "@mysten/sui/graphql"; -import { miso, type MisoProtocolClient } from "@misofm/protocol/client"; +import { miso, type MisoProtocolClient } from "@misofm/musicos/client"; +import { PartyosClient } from "@misofm/partyos"; +import { PartyPlatformClient } from "../party/index.ts"; import { misoConfig, networkFrom, type MisoConfig, type MisoConfigOverrides, type Network } from "./config.ts"; function definedOverrides(value: T): Partial { @@ -31,14 +33,14 @@ function definedOverrides(value: T): Partial { * same shape from our side so the one cast in `createMisoClient` is explicit * rather than an `any` smeared across the codebase. */ -export type ProtocolClient = Parameters[0]; +export type ProtocolClient = Parameters[0]; /** A GraphQL client in the shape the protocol SDK's type-discovery queries want. */ export type ProtocolGraphQLClient = SuiGraphQLClient; export interface MisoClient { config: MisoConfig; - /** gRPC data plane, including Party at `sui.miso.party`. */ + /** gRPC data plane (object-model core only; Party is `client.party`). */ sui: SuiGrpcClient & { miso: MisoProtocolClient }; /** The same client, typed for the protocol SDK's read helpers. */ protocol: ProtocolClient; @@ -46,6 +48,8 @@ export interface MisoClient { graphql: ProtocolGraphQLClient; /** The raw GraphQL client, for this SDK's own queries (the pruned-transaction read). */ graphqlRaw: SuiGraphQLClient; + /** Party identity and profile reads/builders, bound to `config.partyos`/`config.party`. */ + party: PartyPlatformClient; } export interface CreateMisoClientOptions extends MisoConfigOverrides { @@ -76,6 +80,7 @@ export function createMisoClient(options: CreateMisoClientOptions = {}): MisoCli const graphqlRaw = new SuiGraphQLClient({ url: config.graphqlUrl, network: config.network }); const sui = grpc.$extend(miso({ deployment: config.deployment })); + const party = new PartyPlatformClient(sui, new PartyosClient(sui, config.partyos), config.party); return { config, @@ -83,5 +88,6 @@ export function createMisoClient(options: CreateMisoClientOptions = {}): MisoCli protocol: sui as unknown as ProtocolClient, graphql: graphqlRaw as unknown as ProtocolGraphQLClient, graphqlRaw, + party, }; } diff --git a/packages/platform/src/read/config.ts b/packages/platform/src/read/config.ts index 1e7e9c8..6644dfc 100644 --- a/packages/platform/src/read/config.ts +++ b/packages/platform/src/read/config.ts @@ -11,8 +11,9 @@ // by the platform SDK. Mainnet remains deliberately unavailable until a complete // deployment is bundled for it. -import type { MisoDeployment } from "@misofm/protocol/deployments"; -import { getMisoPlatformDeployment, type RecordSalesDeployment } from "../deployments.ts"; +import type { MisoDeployment } from "@misofm/musicos/deployments"; +import type { PartyDeployment } from "@misofm/partyos/deployments"; +import { getMisoPlatformDeployment, type PartyExtensionsDeployment, type RecordSalesDeployment } from "../deployments.ts"; import { walrusAggregatorUrl } from "../walrus.ts"; export type Network = "testnet" | "mainnet"; @@ -58,6 +59,10 @@ export interface MisoConfig { /** Complete package set used by the integrated `client.miso` SDK. */ deployment: MisoDeployment; protocol: ProtocolIds; + /** The Party object model deployment (`@misofm/partyos`). */ + partyos: PartyDeployment; + /** First-party Party extension package ids. */ + party: PartyExtensionsDeployment; /** Final Record/Record Shop deployment, or an explicit unavailable legacy state. */ recordSales: RecordSalesDeployment; money: MoneyIds; @@ -108,6 +113,8 @@ export function misoConfig(network: Network, overrides: MisoConfigOverrides = {} const config: MisoConfig = { network, deployment: platform.protocol, + partyos: platform.partyos, + party: platform.party, recordSales: platform.recordSales, protocol: { // Read-only legacy type discovery does not make this package an diff --git a/packages/platform/src/read/index.ts b/packages/platform/src/read/index.ts index 9ae01f0..42afbbb 100644 --- a/packages/platform/src/read/index.ts +++ b/packages/platform/src/read/index.ts @@ -5,7 +5,7 @@ // // One place that knows how to turn Sui objects into the things Miso talks about: // a pressing, a release, an artist, a library, a receipt. It composes -// @misofm/protocol, owns the per-network id manifest, and returns view types that +// @misofm/musicos, owns the per-network id manifest, and returns view types that // survive JSON. // // Browser- and server-compatible. The HTTP API is a thin cached transport over diff --git a/packages/platform/src/read/receipts.ts b/packages/platform/src/read/receipts.ts index b7d62e7..3f0c2e1 100644 --- a/packages/platform/src/read/receipts.ts +++ b/packages/platform/src/read/receipts.ts @@ -20,11 +20,11 @@ // read walks. We show the buyer that arithmetic, in the same truncating integer // math the chain does. -import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing"; +import * as listingContract from "../contracts/record_shop/listing.ts"; import { extractTypeParams2, getCompositionsByIds, -} from "@misofm/protocol"; +} from "@misofm/musicos"; import { normalizeStructTag, normalizeSuiAddress } from "@mysten/sui/utils"; import { getPressingDetail } from "./catalog.ts"; import type { MisoClient } from "./client.ts"; @@ -36,7 +36,7 @@ import { getWorkAddressesByShareTypes } from "./works.ts"; const BPS = 10_000n; const U64_MAX = (1n << 64n) - 1n; -/** `miso_record_shop::listing::RecordSoldEvent`. */ +/** `record_shop::listing::RecordSoldEvent`. */ const RECORD_SOLD_EVENT_NAME = "listing::RecordSoldEvent"; /** @@ -378,7 +378,7 @@ async function compositionRatesByRecording(client: MisoClient, recordingIds: str const addresses = await getWorkAddressesByShareTypes( client.graphql, { compositions: shareTypes, recordings: [] }, - client.config.deployment.miso, + client.config.deployment.musicos, ); const compositionIds = Object.values(addresses.compositions).filter((id): id is string => !!id); const compositions = await getCompositionsByIds(client.protocol, compositionIds); diff --git a/packages/platform/src/read/types.ts b/packages/platform/src/read/types.ts index b485246..b652148 100644 --- a/packages/platform/src/read/types.ts +++ b/packages/platform/src/read/types.ts @@ -294,7 +294,7 @@ export interface PartySummary { export interface OwnedRecord { /** Canonical object id — the `recordId` route param. */ id: string; - /** Full on-chain type, e.g. `::record::Record`. */ + /** Full on-chain type, e.g. `::record::Record`. */ type: string; releaseId: string; pressingId: string; diff --git a/packages/platform/src/read/wallet.ts b/packages/platform/src/read/wallet.ts index 12603b8..14ffa76 100644 --- a/packages/platform/src/read/wallet.ts +++ b/packages/platform/src/read/wallet.ts @@ -18,11 +18,12 @@ import { getRecordingByShareType, getReleaseById, isNotFound, -} from "@misofm/protocol"; +} from "@misofm/musicos"; +import { party as partyContracts } from "@misofm/partyos/contracts"; import type { MisoClient } from "./client.ts"; import { int, u64 } from "./internal/scalars.ts"; -import * as vaultContract from "@misofm/protocol/contracts/vault/vault"; -import * as recordContract from "@misofm/protocol/contracts/miso_record/record"; +import * as vaultContract from "../contracts/vault/vault.ts"; +import * as recordContract from "../contracts/record/record.ts"; import { deriveRecordId } from "../pressing.ts"; import { requireRecordSalesDeployment } from "../deployments.ts"; import type { @@ -157,7 +158,7 @@ function isCanonicalRecordType( /** * The records `owner` holds. Ownership is DIRECT — a record is an address-owned - * `::record::Record` with no pressing/license/receipt intermediary. + * `::record::Record` with no pressing/license/receipt intermediary. * The server-side type filter and exact local check deliberately exclude records * from any retired package namespace. */ @@ -220,7 +221,7 @@ export async function getOwnedRecords(client: MisoClient, owner: string): Promis * authoritative in a way that remembering created parties client-side is not. */ export async function getOwnedParties(client: MisoClient, owner: string): Promise { - const capType = `${client.config.deployment.misoParty}::party::PartyAdminCap`; + const capType = `${client.config.partyos.partyos}::party::PartyAdminCap`; // One page of 50 caps is plenty for launch-scale artists; paginate if labels // ever start hitting the cap. @@ -233,7 +234,7 @@ export async function getOwnedParties(client: MisoClient, owner: string): Promis const caps = objects.flatMap((obj) => { try { - const cap = networkContracts.party.PartyAdminCap.parse(obj.content); + const cap = partyContracts.PartyAdminCap.parse(obj.content); return [{ capId: obj.objectId, partyId: cap.party_id }]; } catch { return []; @@ -241,7 +242,7 @@ export async function getOwnedParties(client: MisoClient, owner: string): Promis }); if (caps.length === 0) return []; - const parties = await client.sui.miso.party.getPartiesByIds(caps.map((c) => c.partyId)); + const parties = await client.party.getPartiesByIds(caps.map((c) => c.partyId)); return caps.flatMap(({ capId, partyId }) => { const p = parties[partyId]; return p ? [{ partyId, capId, name: p.name, kind: p.kind }] : []; @@ -267,13 +268,13 @@ export async function getPendingMemberships( const invitations = await Promise.all( individuals.map(async (member) => ({ member, - groupIds: await client.sui.miso.party.getPendingMemberships(member.partyId), + groupIds: await client.party.getPendingMemberships(member.partyId), })), ); const groupIds = [...new Set(invitations.flatMap(({ groupIds }) => groupIds))]; if (groupIds.length === 0) return []; - const groups = await client.sui.miso.party.getPartiesByIds(groupIds); + const groups = await client.party.getPartiesByIds(groupIds); return invitations.flatMap(({ member, groupIds }) => groupIds.flatMap((groupId): PendingMembership[] => { const group = groups[groupId]; @@ -321,7 +322,7 @@ async function ownedVaultedWorkCaps(client: MisoClient, owner: string) { releases: VaultedReleaseCap[]; } = { compositions: [], recordings: [], releases: [] }; const vaultPackageId = client.config.protocol.vault; - const misoPackageId = client.config.deployment.miso; + const misoPackageId = client.config.deployment.musicos; const capType = `${vaultPackageId}::vault::VaultAdminCap`; let cursor: string | null = null; @@ -369,7 +370,7 @@ async function resolveVaultedReleaseCaps( caps: readonly VaultedReleaseCap[], ): Promise<{ id: string; releaseId: string }[]> { if (caps.length === 0) return []; - const releaseCapType = `${client.config.deployment.miso}::release::ReleaseAdminCap`; + const releaseCapType = `${client.config.deployment.musicos}::release::ReleaseAdminCap`; const vaultType = normalizeStructTag( `${client.config.protocol.vault}::vault::Vault<${releaseCapType}>`, ); @@ -409,7 +410,7 @@ async function resolveVaultedReleaseCaps( * `release_id` directly. */ export async function getOwnedWorks(client: MisoClient, owner: string): Promise { - const miso = client.config.deployment.miso; + const miso = client.config.deployment.musicos; const [directCompCaps, directRecCaps, directRelCaps, vaulted] = await Promise.all([ ownedGenericCaps(client, owner, `${miso}::composition::CompositionAdminCap`), @@ -468,7 +469,7 @@ export async function getOwnedWorks(client: MisoClient, owner: string): Promise< /** Classify a direct or vaulted cap by its on-chain type, then resolve its work. */ export async function getWorkByCap(client: MisoClient, capId: string): Promise { - const miso = client.config.deployment.miso; + const miso = client.config.deployment.musicos; let type: string; let json: Record | null; @@ -664,7 +665,7 @@ function isAddressOwner(owner: unknown, address: string): boolean { * stays hidden rather than being offered and then rejected on submit. */ export async function ownsParty(client: MisoClient, address: string, partyId: string): Promise { - const capId = client.sui.miso.party.derivePartyAdminCapId(partyId); + const capId = client.party.derivePartyAdminCapId(partyId); const isOwner = await client.protocol.core .getObject({ objectId: capId }) .then(({ object }) => isAddressOwner(object?.owner, address)) diff --git a/packages/platform/src/read/works.ts b/packages/platform/src/read/works.ts index 59d912c..70853c8 100644 --- a/packages/platform/src/read/works.ts +++ b/packages/platform/src/read/works.ts @@ -12,7 +12,7 @@ import { type Recording, type Release, type TrackState, -} from "@misofm/protocol"; +} from "@misofm/musicos"; export interface WorkShareTypes { compositions: readonly string[]; diff --git a/packages/platform/src/recording-extensions.ts b/packages/platform/src/recording-extensions.ts index a878b2f..13b5421 100644 --- a/packages/platform/src/recording-extensions.ts +++ b/packages/platform/src/recording-extensions.ts @@ -7,15 +7,15 @@ import { bcs } from "@mysten/sui/bcs"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import type { Transaction, TransactionArgument, TransactionObjectArgument } from "@mysten/sui/transactions"; import { deriveDynamicFieldID, fromHex, toHex } from "@mysten/sui/utils"; -import { isNotFound } from "@misofm/protocol"; +import { isNotFound } from "@misofm/musicos"; import type { TxThunk } from "./transactions.ts"; import { invokeWithAdminCap, type AdminCapAuthority, type ObjectInput } from "./vault.ts"; -import * as advisory from "@misofm/protocol/contracts/recording_advisory/recording_advisory"; -import * as language from "@misofm/protocol/contracts/recording_language/recording_language"; -import * as walrusData from "@misofm/protocol/contracts/recording_master_reference/deps/ori/data"; -import * as masterReference from "@misofm/protocol/contracts/recording_master_reference/recording_master_reference"; -import * as engineSession from "@misofm/protocol/contracts/recording_engine_session/recording_engine_session"; -import * as streamingTranscode from "@misofm/protocol/contracts/recording_streaming_transcode/recording_streaming_transcode"; +import * as advisory from "./contracts/recording_advisory/recording_advisory.ts"; +import * as language from "./contracts/recording_language/recording_language.ts"; +import * as walrusData from "./contracts/recording_master_reference/deps/ori/data.ts"; +import * as masterReference from "./contracts/recording_master_reference/recording_master_reference.ts"; +import * as engineSession from "./contracts/recording_engine_session/recording_engine_session.ts"; +import * as streamingTranscode from "./contracts/recording_streaming_transcode/recording_streaming_transcode.ts"; import { unencryptedWalrusBlob } from "./internal.ts"; export interface RecordingExtensionTarget { diff --git a/packages/platform/src/release-extensions.ts b/packages/platform/src/release-extensions.ts index 7536cc6..971ccd3 100644 --- a/packages/platform/src/release-extensions.ts +++ b/packages/platform/src/release-extensions.ts @@ -14,9 +14,9 @@ import type { TransactionObjectArgument, } from "@mysten/sui/transactions"; import type { TxThunk } from "./transactions.ts"; -import * as releaseDescription from "@misofm/protocol/contracts/release_description/release_description"; -import * as releaseDspLink from "@misofm/protocol/contracts/release_dsp_link/release_dsp_link"; -import * as releaseKind from "@misofm/protocol/contracts/release_kind/release_kind"; +import * as releaseDescription from "./contracts/release_description/release_description.ts"; +import * as releaseDspLink from "./contracts/release_dsp_link/release_dsp_link.ts"; +import * as releaseKind from "./contracts/release_kind/release_kind.ts"; import { asU64, directAdminCap, invokeWithAdminCap, type AdminCapAuthority, type ObjectInput, type U64Input } from "./vault.ts"; function object(tx: Transaction, value: ObjectInput): TransactionObjectArgument { diff --git a/packages/platform/src/release-graph.ts b/packages/platform/src/release-graph.ts index c2bf8b9..46c7a05 100644 --- a/packages/platform/src/release-graph.ts +++ b/packages/platform/src/release-graph.ts @@ -18,14 +18,14 @@ // PTB, and existing recordings authorized by their admin cap. // // This is the whole-graph orchestration half of the opinionated publish flow — -// it composes `@misofm/protocol`'s bare `composition`/`recording`/`track`/ +// it composes `@misofm/musicos`'s bare `composition`/`recording`/`track`/ // `release` core call bindings with its // own `disperseShares`/`finalizeRelease` helpers into one PTB. Royalty pools are // installed later through an admin-cap Vault plugin; the retired raw-cap helper // is intentionally not used here. import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions"; -import { contracts } from "@misofm/protocol"; +import { contracts } from "@misofm/musicos"; import { custodyOf, disperseShares, finalizeRelease, recordingAuthorityOf, requiredCommandResult, type AdminCustodyInput, type RecordingAuthorityInput, type ShareRecipient } from "./transactions.ts"; import { disposeNewAdminCap, invokeWithAdminCap } from "./vault.ts"; diff --git a/packages/platform/src/royalty.ts b/packages/platform/src/royalty.ts new file mode 100644 index 0000000..1344963 --- /dev/null +++ b/packages/platform/src/royalty.ts @@ -0,0 +1,167 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Generic royalty-pool / stake / routed-stake reads and PTB builders. These are +// the raw, caller-owned primitives against the bare `royalty_pool` and +// `routed_stake` packages — Vault-gated crank operations (composition/recording +// royalty pool plugins, routed-stake Actions bound to a VaultAdminCap) live in +// `./vault.ts` instead; this module does not duplicate those. + +import type { ClientWithCoreApi } from "@mysten/sui/client"; +import { Transaction, type TransactionArgument, type TransactionObjectArgument } from "@mysten/sui/transactions"; +import { deriveObjectID } from "@mysten/sui/utils"; +import { getObjectByBcs } from "@misofm/musicos/queries"; + +import { RoyaltyPool as RoyaltyPoolBcs } from "./contracts/royalty_pool/pool.ts"; +import { Stake as RoyaltyStakeBcs } from "./contracts/royalty_pool/stake.ts"; +import { RoutedStake as RoutedStakeBcs } from "./contracts/routed_stake/routed_stake.ts"; +import * as royaltyPool from "./contracts/royalty_pool/pool.ts"; +import * as royaltyStake from "./contracts/royalty_pool/stake.ts"; +import * as routedStake from "./contracts/routed_stake/routed_stake.ts"; + +/** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */ +const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]); + +// ============================================================================ +// Reads +// ============================================================================ + +/** Parse a royalty pool by object ID. Phantom type arguments do not affect BCS. */ +export async function getRoyaltyPoolById( + client: ClientWithCoreApi, + poolId: string, +) { + return getObjectByBcs(client, poolId, RoyaltyPoolBcs); +} + +/** Parse an owned `Stake` by object ID. */ +export async function getRoyaltyStakeById( + client: ClientWithCoreApi, + stakeId: string, +) { + return getObjectByBcs(client, stakeId, RoyaltyStakeBcs); +} + +/** Parse a shared routed stake by object ID. */ +export async function getRoutedStakeById( + client: ClientWithCoreApi, + routedStakeId: string, +) { + return getObjectByBcs(client, routedStakeId, RoutedStakeBcs); +} + +/** Deterministically derive the royalty-pool ID for a parent and type pair. */ +export function deriveRoyaltyPoolId( + parentId: string, + shareType: string, + currencyType: string, + royaltyPoolPackageId: string, +): string { + return deriveObjectID( + parentId, + `${royaltyPoolPackageId}::pool::RoyaltyPoolKey<${shareType}, ${currencyType}>`, + UNIT_STRUCT_KEY_BYTES, + ); +} + +/** Deterministically derive the routed-stake ID for a parent and stake share. */ +export function deriveRoutedStakeId( + parentId: string, + stakeShareType: string, + routedStakePackageId: string, +): string { + return deriveObjectID( + parentId, + `${routedStakePackageId}::routed_stake::RoutedStakeKey<${stakeShareType}>`, + UNIT_STRUCT_KEY_BYTES, + ); +} + +// ============================================================================ +// Generic royalty primitives (PTB builders) +// ============================================================================ + +/** Create an owned `Stake` from a by-value `Balance`. */ +export function createRoyaltyStake( + tx: Transaction, + params: { + balance: TransactionArgument; + shareType: string; + royaltyPoolPackageId: string; + }, +): TransactionObjectArgument { + return tx.add( + royaltyStake._new({ + package: params.royaltyPoolPackageId, + typeArguments: [params.shareType], + arguments: [params.balance], + }), + ); +} + +/** Destroy an unregistered stake and return its principal `Balance`. */ +export function destroyRoyaltyStake( + tx: Transaction, + params: { + stake: TransactionObjectArgument; + shareType: string; + royaltyPoolPackageId: string; + }, +): TransactionObjectArgument { + return tx.add( + royaltyStake.destroy({ + package: params.royaltyPoolPackageId, + typeArguments: [params.shareType], + arguments: [params.stake], + }), + ); +} + +/** Register an owned stake against a shared royalty pool. */ +export function registerRoyaltyStake( + tx: Transaction, + params: { + poolId: string; + stakeId?: string; + /** A `Stake` created earlier in this same PTB. */ + stake?: TransactionObjectArgument; + shareType: string; + currencyType: string; + royaltyPoolPackageId: string; + }, +): void { + if (!params.stake && !params.stakeId) { + throw new Error("registerRoyaltyStake: stakeId or stake required"); + } + tx.add( + royaltyPool.registerStake({ + package: params.royaltyPoolPackageId, + typeArguments: [params.shareType, params.currencyType], + arguments: [tx.object(params.poolId), params.stake ?? tx.object(params.stakeId!)], + }), + ); +} + +/** Claim rewards. The returned `Balance` must be consumed in this PTB. */ +export function claimRoyaltyRewards( + tx: Transaction, + params: { + poolId: string; + stakeId: string; + shareType: string; + currencyType: string; + royaltyPoolPackageId: string; + }, +): TransactionObjectArgument { + return tx.add( + royaltyPool.claimRewards({ + package: params.royaltyPoolPackageId, + typeArguments: [params.shareType, params.currencyType], + arguments: [tx.object(params.poolId), tx.object(params.stakeId)], + }), + ); +} + +// `sweepRoutedStake` — the permissionless routed-stake reward sweep — already +// lives in `./vault.ts` and is exported from the package root from there; this +// module does not duplicate it. diff --git a/packages/platform/src/share.ts b/packages/platform/src/share.ts index 1c8a727..bcb7a5a 100644 --- a/packages/platform/src/share.ts +++ b/packages/platform/src/share.ts @@ -32,7 +32,7 @@ import { createdByType, allCreatedByType, type ExecResult, -} from "@misofm/protocol"; +} from "@misofm/musicos"; import { publishShareCurrency, initializeShareCurrency, type PackageBytecode } from "./transactions.ts"; import { SHARE_TEMPLATE } from "./share-template.ts"; diff --git a/packages/platform/src/transactions.ts b/packages/platform/src/transactions.ts index 956e2fc..9747b80 100644 --- a/packages/platform/src/transactions.ts +++ b/packages/platform/src/transactions.ts @@ -6,7 +6,7 @@ // `Transaction`, so a platform call composes with protocol calls and anything // else in the same PTB. // -// This module holds the OPINIONATED publish flows. `@misofm/protocol` does the +// This module holds the OPINIONATED publish flows. `@misofm/musicos` does the // minimum the Move semantics FORCE and returns anything a caller could // legitimately route elsewhere; deciding where those values GO is the // platform-layer call that lives here: @@ -27,7 +27,7 @@ // constraint, not a same-function one, which is exactly what makes this split // possible. // -// Composes with `@misofm/protocol`'s calls in the same PTB (the +// Composes with `@misofm/musicos`'s calls in the same PTB (the // transaction-thunk composition pattern, just crossing a package boundary now). import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions"; @@ -41,10 +41,10 @@ import { type RecordingParts, type ShareCurrencyBinding, type TxThunk, -} from "@misofm/protocol"; +} from "@misofm/musicos"; import { asU64, directAdminCap, disposeNewAdminCap, invokeWithAdminCap, type AdminCapAuthority, type AdminCapCustody, type U64Input } from "./vault.ts"; -import * as royaltyPool from "@misofm/protocol/contracts/royalty_pool/pool"; -import * as royaltyPoolStake from "@misofm/protocol/contracts/royalty_pool/stake"; +import * as royaltyPool from "./contracts/royalty_pool/pool.ts"; +import * as royaltyPoolStake from "./contracts/royalty_pool/stake.ts"; const { track, release } = contracts; @@ -284,7 +284,7 @@ export type FinalizeCompositionParams = FinalizeCompositionParamsBase & AdminCus * The opinionated finish for a composition: disperse its share supply to * `shareRecipients`, publish (share) it, and transfer its admin cap to * `adminAddress`. Consumers that want different economics skip this and act on - * the `CompositionParts` from `@misofm/protocol`'s `createComposition` directly. + * the `CompositionParts` from `@misofm/musicos`'s `createComposition` directly. */ export function finalizeComposition(tx: Transaction, params: FinalizeCompositionParams): void { disperseShares(tx, params.minatoPackageId, params.shareType, params.balance, params.shareRecipients); diff --git a/packages/platform/src/vault.ts b/packages/platform/src/vault.ts index 928699a..72c8cea 100644 --- a/packages/platform/src/vault.ts +++ b/packages/platform/src/vault.ts @@ -20,12 +20,12 @@ import type { TransactionObjectArgument, } from "@mysten/sui/transactions"; import { deriveObjectID, normalizeStructTag } from "@mysten/sui/utils"; -import * as vault from "@misofm/protocol/contracts/vault/vault"; -import * as releaseRevenueDistributor from "@misofm/protocol/contracts/release_revenue_distributor/release_revenue_distributor"; -import * as compositionRoyaltyPoolPlugin from "@misofm/protocol/contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin"; -import * as recordingRoyaltyPoolPlugin from "@misofm/protocol/contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin"; -import * as releaseRevenueDistributorPlugin from "@misofm/protocol/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin"; -import * as routedStake from "@misofm/protocol/contracts/routed_stake/routed_stake"; +import * as vault from "./contracts/vault/vault.ts"; +import * as releaseRevenueDistributor from "./contracts/release_revenue_distributor/release_revenue_distributor.ts"; +import * as compositionRoyaltyPoolPlugin from "./contracts/composition_royalty_pool_plugin/composition_royalty_pool_plugin.ts"; +import * as recordingRoyaltyPoolPlugin from "./contracts/recording_royalty_pool_plugin/recording_royalty_pool_plugin.ts"; +import * as releaseRevenueDistributorPlugin from "./contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts"; +import * as routedStake from "./contracts/routed_stake/routed_stake.ts"; /** A legacy work whose raw protocol admin cap is still address-owned. */ /** An object id resolved lazily when a transaction thunk is applied. */ diff --git a/packages/platform/sui-codegen.config.ts b/packages/platform/sui-codegen.config.ts deleted file mode 100644 index d9e5b4e..0000000 --- a/packages/platform/sui-codegen.config.ts +++ /dev/null @@ -1,153 +0,0 @@ -import type { SuiCodegenConfig } from "@mysten/codegen"; -import { resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -// Generates type-safe BCS structs + Move-call bindings from the live Move source, -// so the generated layer is always in lockstep with the on-chain ABI. -// -// PLATFORM + DATA-EXTENSION packages. The protocol CORE (`miso` — Composition, -// Recording, Release, Track) generates into `@misofm/protocol` instead, and -// this package depends on it for those bindings; adding the core here to save an -// import is how the split this package exists to enforce gets undone. -// -// Extensions add persistent data to a protocol work. Actions are public, -// custody-agnostic business logic. Vault plugins are permissionless automation -// adapters for the subset of Actions that is safe to crank without an admin. -// -// Paths resolve against sibling checkouts. For an isolated SDK worktree, set -// MISO_SDK_CODEGEN_SOURCE_ROOT to a copied checkout root containing both -// `misofm/` and `misonetwork/`; codegen then never writes summaries or locks into -// a developer's live sibling sources. Regenerating requires: -// ~/Documents/GitHub/misofm/{sdk, record, record-shop, vault, vault-plugins, -// protocol-extensions} -// ~/Documents/GitHub/misonetwork/{party-actions,protocol,protocol-actions, -// protocol-extensions,royalty-pool,routed-stake,share} -const sourceRoot = - process.env.MISO_SDK_CODEGEN_SOURCE_ROOT ?? - fileURLToPath(new URL("../../../..", import.meta.url)); -const source = (path: string) => resolve(sourceRoot, path); - -const config: SuiCodegenConfig = { - output: "./src/contracts", - packages: [ - // Record identity and edition-scoped Pressings are separate from the - // primary-sale mechanics supplied by Record Shop. - { package: "@local-pkg/miso_record", path: source("misofm/record") }, - { - package: "@local-pkg/miso_record_shop", - path: source("misofm/record-shop"), - }, - - // Royalty pools — accumulator-based distribution bound to a work. - { - package: "@local-pkg/royalty_pool", - path: source("misonetwork/royalty-pool"), - }, - - // Capability custody plus raw-cap Actions. - { package: "@local-pkg/vault", path: source("misofm/vault") }, - { - package: "@local-pkg/composition_royalty_pool", - path: source("misonetwork/protocol-actions/composition_royalty_pool"), - }, - { - package: "@local-pkg/recording_royalty_pool", - path: source("misonetwork/protocol-actions/recording_royalty_pool"), - }, - { - package: "@local-pkg/party_wallet", - path: source("misonetwork/party-actions/party_wallet"), - }, - { - package: "@local-pkg/composition_routed_stake", - path: source("misonetwork/protocol-actions/composition_routed_stake"), - }, - { - package: "@local-pkg/release_revenue_distributor", - path: source("misonetwork/protocol-actions/release_revenue_distributor"), - }, - { - package: "@local-pkg/routed_stake", - path: source("misonetwork/routed-stake"), - }, - - // The deployed cover_art binding remains frozen under src/contracts until - // its current source ABI is published and selected in deployments.ts. - { - package: "@local-pkg/release_cover_art", - path: source("misonetwork/protocol-extensions/release_cover_art"), - }, - - // Release presentation + discovery metadata. These are independent - // cap-gated extensions over the core Release; the publish intent aggregates - // them, but each package stays separately deployable. - { package: "@local-pkg/genre", path: source("misonetwork/genre") }, - { - package: "@local-pkg/release_description", - path: source("misonetwork/protocol-extensions/release_description"), - }, - { - package: "@local-pkg/release_dsp_link", - path: source("misofm/protocol-extensions/release_dsp_link"), - }, - { - package: "@local-pkg/release_genre", - path: source("misonetwork/protocol-extensions/release_genre"), - }, - { - package: "@local-pkg/release_kind", - path: source("misonetwork/protocol-extensions/release_kind"), - }, - - // Recording metadata is data-only; its cap-gated writers work with either a - // legacy direct cap or a Vault loan through the SDK authority helpers. - { - package: "@local-pkg/recording_advisory", - path: source("misonetwork/protocol-extensions/recording_advisory"), - }, - { - package: "@local-pkg/recording_language", - path: source("misonetwork/protocol-extensions/recording_language"), - }, - // The deployed recording_master_reference binding likewise remains frozen - // until a deployment explicitly replaces it. - { - package: "@local-pkg/recording_streaming_transcode", - path: source("misofm/protocol-extensions/recording_streaming_transcode"), - }, - - // Permissionless automation adapters. Party wallet and routed stake have no - // plugin because their useful operations return caller-controlled assets. - { - package: "@local-pkg/composition_royalty_pool_plugin", - path: source("misofm/vault-plugins/composition_royalty_pool_plugin"), - }, - { - package: "@local-pkg/recording_royalty_pool_plugin", - path: source("misofm/vault-plugins/recording_royalty_pool_plugin"), - }, - { - package: "@local-pkg/release_revenue_distributor_plugin", - path: source("misofm/vault-plugins/release_revenue_distributor_plugin"), - }, - - // Credits — contributor attribution (display name + roles) attached to a - // work via a dynamic field, gated by the work's admin cap. The shared - // `miso_credit::credit::Credit` type is pulled in as a dep. Drives the - // credits CLI flow and the app's contributor render. - { - package: "@local-pkg/composition_credits", - path: source("misonetwork/protocol-extensions/composition_credits"), - }, - { - package: "@local-pkg/recording_credits", - path: source("misonetwork/protocol-extensions/recording_credits"), - }, - { - package: "@local-pkg/release_credits", - path: source("misonetwork/protocol-extensions/release_credits"), - }, - ], -}; - -export default config; diff --git a/packages/platform/tests/batching.test.ts b/packages/platform/tests/batching.test.ts index dc9715c..81e9b8a 100644 --- a/packages/platform/tests/batching.test.ts +++ b/packages/platform/tests/batching.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from "bun:test"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import type { SuiGraphQLClient } from "@mysten/sui/graphql"; -import { contracts } from "@misofm/protocol"; +import { contracts } from "@misofm/musicos"; import { getTrackCreditsByRecordingIds } from "../src/catalog.ts"; import { getReleaseCoversByIds } from "../src/cover.ts"; import { diff --git a/packages/platform/tests/client.test.ts b/packages/platform/tests/client.test.ts index 46109cd..cec39f5 100644 --- a/packages/platform/tests/client.test.ts +++ b/packages/platform/tests/client.test.ts @@ -26,10 +26,17 @@ import { type OperationsDeployment, } from "../src/deployments.ts"; import { - MISO_PACKAGE_NAMES, + MISO_DEPLOYMENTS as PROTOCOL_MISO_DEPLOYMENTS, type MisoDeployment, -} from "@misofm/protocol/deployments"; +} from "@misofm/musicos/deployments"; import { networkFrom } from "../src/read/config.ts"; +import type { PartyExtensionsDeployment } from "../src/deployments.ts"; +import { + PARTYOS_DEPLOYMENTS as PROTOCOL_PARTYOS_DEPLOYMENTS, + type PartyDeployment, +} from "@misofm/partyos/deployments"; +import { PartyosClient } from "@misofm/partyos"; +import { party as partyCoreContracts } from "@misofm/partyos/contracts"; const RECORD = `0x${"12".repeat(32)}`; const SHOP = `0x${"13".repeat(32)}`; @@ -69,17 +76,31 @@ const OPERATIONS = { }, } as const satisfies OperationsDeployment; -const NETWORK_DEPLOYMENT = Object.fromEntries( - MISO_PACKAGE_NAMES.map((name, index) => [ - name, - name === "miso" ? MISO : `0x${(index + 1).toString(16).padStart(64, "0")}`, - ]), -) as MisoDeployment; +const NETWORK_DEPLOYMENT: MisoDeployment = { musicos: MISO }; + +const PARTYOS_DEPLOYMENT: PartyDeployment = { partyos: id(801) }; + +const PARTY_DEPLOYMENT: PartyExtensionsDeployment = { + partyCta: id(802), + partyGenre: id(803), + partyMedia: id(804), + partyMusic: id(805), + partyPlatformLink: id(806), + partyProLink: id(807), + partyProfile: id(808), + partyRoles: id(809), + partySocial: id(810), + partyTags: id(811), + countryCode: id(812), + languageCode: id(813), +}; const DEPLOYMENT = { network: "testnet", chainIdentifier: "testnet-chain-identifier", protocol: NETWORK_DEPLOYMENT, + partyos: PARTYOS_DEPLOYMENT, + party: PARTY_DEPLOYMENT, recordSales: { status: "available", recordPackageId: RECORD, @@ -279,8 +300,8 @@ test("bundled Testnet deployment exposes the verified sales and operations ABIs" expect(MISO_PLATFORM_DEPLOYMENTS.testnet.recordSales.status).toBe("available"); expect(MISO_PLATFORM_DEPLOYMENTS.testnet.operations.status).toBe("available"); - expect(client.miso.deployment?.protocol.miso).toBe( - MISO_PLATFORM_DEPLOYMENTS.testnet.protocol.miso, + expect(client.miso.deployment?.protocol.musicos).toBe( + MISO_PLATFORM_DEPLOYMENTS.testnet.protocol.musicos, ); expect(client.miso.recordPackageId).toBe( MISO_PLATFORM_DEPLOYMENTS.testnet.recordSales.recordPackageId, @@ -313,99 +334,94 @@ test("bundled Testnet deployment exactly matches the verified immutable export", expect(MISO_PLATFORM_DEPLOYMENTS.testnet).toEqual({ network: "testnet", chainIdentifier: "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD", - protocol: { - miso: "0x95bb43d650fe582caba27d4ead2c3f9939c38125943f8716c5baf5af528bc671", - compositionCredits: "0x54cac9dde365a08eff983af16d74d798e6c0a14264bf6cc8a24790708498e37b", - recordingAdvisory: "0x48a16738e6af6548e50d8f76dce9b9de247f365ef2a07c171f732bdc9c88b235", - recordingCredits: "0x0ca2c727c3eb8d3f889d9b29ce1118bc60518c6990e26e9ab278a7ffe635bf1e", - recordingLanguage: "0x677cef9c766b17f87fdf9d6623f1adca0d2e244ee228c86d82c23599ad9a828a", - recordingMasterReference: "0x2b1ca393e8d41eba5b0d3cabf6df1e207a349bba0486ca9ea3a31734d3445971", - releaseCoverArt: "0xb289dfb58ccf2af3722f7bc93b072426a791d05a401115f2901d130f12d8e4b2", - releaseCredits: "0x3a05d0c863ca0b5210f90f69cf87da59791b127c9eaaa10633961aee229a3ed5", - releaseDescription: "0x60a8bc11b7d41d594a2c54c8dee4534dcf94d3be20054100c7774026a543ec6f", - releaseDspLink: "0xbeffd79f656ce89d3595c9fb36dac42a169b72a5e7090504c48ba6c2425961a6", - releaseGenre: "0x111dd8bff35a1779067d7c75f8a514691342f91f67d2fcdae392768ba6db26f2", - releaseKind: "0x90bf2633b45699d424da616869ecb3d824a88d78d8f8d2ccf5b711b18a734bf1", - royaltyPool: "0xa49e297e4ed8c29ea9bd5941b3ac7d41327f97c85fc35e11bda466f09fce1943", - routedStake: "0xc920af18421cd11c315fa8d0cdd56056854cf453f1ced1fa1b32d555bca2955a", - misoParty: "0xc9fc5d918da992b7c6499880fc509635def384d8529948b28f58494daedf8ec8", - partyCta: "0x25bbdb3dc3fa9482be90294a2b3e328ab4fc2658f1258f1cdf4fe73b13a8d7c5", - partyGenre: "0xa02cffae5be2820e8cda8ecd4f10ae4149a40800aee1a78c74736a409aa515b9", - partyMedia: "0xcafdbe4be3dd6bfade79e546f7faf08972ed864fdb1d49157ad420d71f927783", - partyMusic: "0x76405c486eccde051a621d91a9fd05a911fdc88a3282ef58a3c58eeaad200008", - partyPlatformLink: "0x1d555a8ed265f4a76119c5c01be3646807b91b2cec588e33db016671bdcc259c", - partyProLink: "0xbe6f66e75c6f41afcbf88b166e11cf0a849337379e2f12673598ff976c9f327b", - partyProfile: "0xa5b333defcf07ad3c59feac4b831f4d6775776b1734c9fb1145f590c3028e789", - partyRoles: "0x7a3ab86bfe19b3d291a18d7c0582a1248cf9d74f981f901e9dc75dce0dc3ced9", - partySocial: "0xaaa1af3282370b3f1cd376b2e6f1e0c3b457498eae8ba6150b86e7fe56285392", - partyTags: "0x68a90de97c82f90f22eaa8f2e2b2bfcc3bcfef4847d62e8540474a494f1198d9", + // The platform manifest's `protocol` section must be exactly the musicos + // package's own bundled testnet deployment — checked against the live + // constant (not a hardcoded copy) so this test tracks musicos, not a + // snapshot of it. + protocol: PROTOCOL_MISO_DEPLOYMENTS.testnet, + // Checked against the live constant (not a hardcoded copy), same reasoning + // as `protocol` above: this test tracks partyos, not a snapshot of it. + partyos: PROTOCOL_PARTYOS_DEPLOYMENTS.testnet, + party: { + partyCta: "0x310bd64b4d32b547ad52128df9839702a3b74e144e920d04d00bb2bd488b2036", + partyGenre: "0x6b66b793a13c899e41d812a3feccafc9078184f0bd74c4af474d23ef7fd35ec5", + partyMedia: "0xb4479afae1f14c4cf7064908c29ccf698b5e10f2e48a31d63f861131476594c5", + partyMusic: "0xe583826d1610a252769eddb4fa81e81040d40468fc99eec4bf72c52456ff7f79", + partyPlatformLink: "0xe0990445758d27732928759a33e73bcddd2b06604dc0c552b958b6b0220b060b", + partyProLink: "0xdbf8f3d1d12cf15e435d74bfb910e88eae7094064176661a85bc79a76e6ce674", + partyProfile: "0x0cb11099892f9a583077408369a1d838ec888660ed31af7f7bd52a3794f147a6", + partyRoles: "0x53227d2c8c36b6bae17e2cdfaff50cf7e52261c40cbda65a11fe9f9486cc6104", + partySocial: "0xb691711983ad484e8caecc3091872a163a4cf6f93bfab3d3974ad94bb4b63d2f", + partyTags: "0xcc0a53bfa1310758c02607a9ee95fa1553ef3d9431e3df25f38a16e09c3466c5", countryCode: "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", languageCode: "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", - genre: "0x6ae4aefdd9d147db6f04a14e4f6149944c485fc7d87d8201dcdb0dee15f0f296", }, recordSales: { status: "available", recordPackageId: - "0x0ad1ad504c63f8e14f74f0535600eb965217bc9ef3305d6d4bd3dad077bb832f", + "0x8562a4c266b1229871551cec69c1a331f682920b9cad7685e42e7c6a5eba61d5", recordShopPackageId: - "0x6f075182093837a6b9ea00be706c94d896a3cf62567e05a74d902716a7ac8730", + "0x5d1b79c312b5d2a2bc9aa0ce7ea6d41ecedd4a5d8698bc007000ca0c8a3459f9", }, operations: { status: "available", vault: { packageId: - "0x994699b7df4963aa3ad4ca3253d26e7271b8f8e76b50108f00eb69e7aca4637a", + "0xe7a5d1f895d7ca2571c2329a13a5703905d8903433b379db3aa987c7594a0198", registryId: - "0xeaad77ce26cc610340c32c55f8fe0ae6f63639431c4608c7466703dee8450c7a", + "0xebd40980edb30e425b80f7d65378246254cc5787e07d23faa46e6b9a2b9dd14d", }, actions: { compositionRoyaltyPool: - "0x8a55e02198a9ab7997abf2815e7f4b587d976570128741dfed2e152b7c0e9cab", + "0xa4655e8c1319655cbc0ad5da0d8bccbd528372fea2cb767235e89f2be6eb403c", recordingRoyaltyPool: - "0x404089e5da21fe26dd6b9b346f457b3cbe34977bb12c503036e6b625fa34ba41", + "0xff510b24ddba7755dafeb3ceae65204ed6be058a83b482f98da92b3d19eefcd2", partyWallet: - "0x0bdd7e78d46834546b54d6c02697cf67b37d36a2c6fdbb19e290729529fdb3a9", + "0x493fa265fd7c8066cd80f644d22086fe36b2c8e26bd160d8a6f6f44743c42acc", compositionRoutedStake: - "0xf5138fd64c16a43bead17e56fe7ac6900c214cd55233c069f47b13174d0a9ff8", + "0xff5b7a4e1791210c36f2ea071a87e2e6212ddbe2acc922d324cf5436f8ca0b47", releaseRevenueDistributor: - "0x24656bb1feafc1a3d0ec30b8f10b364983c7a9c12febb5b7f6acd007f013a54b", + "0x72b175f79cdb1df5d597cdb07007ac309994e6c3f99823b04400a48565dd3989", }, plugins: { compositionRoyaltyPool: - "0x8934a96dd9cbdc64040d74810556deef0a39d28ea7033f633d76d7b560e9191c", + "0x57b58eb53ade40a7e1e6e4be4bdd0fecdd57f709efe8021830b2e3519e800254", recordingRoyaltyPool: - "0x92b8b21483241e39ddab66a67fb1fc99f553eef9b53f47747cb59515540c8dec", + "0x69c859aba359ca8fbeb1df4cff3de53cca3b4ce5ffaa1a7516bc9eb3e016cf71", releaseRevenueDistributor: - "0xbebed662371093a75c0d24a2c34a161339a2e4ab981594de8aa7157c555c620e", + "0x875a764569360ec7f4e676bcf5e30a50037229f33f2bae6d2acd205c0570e8b2", }, }, packages: { minato: "0xcdf58ed7e4580118a6a3f2a8077abffe633c551b2f19e95ce01685d42f90b8d9", - credit: "0xdcbe495da81859e16540e2df44e86123217930ac9066a8aecebf479cbe73006f", - compositionCredits: "0x54cac9dde365a08eff983af16d74d798e6c0a14264bf6cc8a24790708498e37b", - recordingCredits: "0x0ca2c727c3eb8d3f889d9b29ce1118bc60518c6990e26e9ab278a7ffe635bf1e", - releaseCredits: "0x3a05d0c863ca0b5210f90f69cf87da59791b127c9eaaa10633961aee229a3ed5", - royaltyPool: "0xa49e297e4ed8c29ea9bd5941b3ac7d41327f97c85fc35e11bda466f09fce1943", - routedStake: "0xc920af18421cd11c315fa8d0cdd56056854cf453f1ced1fa1b32d555bca2955a", - coverArt: "0xb7c82b0435e6f577cdc334a9b2d8940b869dacb846d72ddb3ee566b5e8c63b52", - releaseCoverArt: "0xb289dfb58ccf2af3722f7bc93b072426a791d05a401115f2901d130f12d8e4b2", - genre: "0x6ae4aefdd9d147db6f04a14e4f6149944c485fc7d87d8201dcdb0dee15f0f296", - releaseDescription: "0x60a8bc11b7d41d594a2c54c8dee4534dcf94d3be20054100c7774026a543ec6f", - releaseDspLink: "0xbeffd79f656ce89d3595c9fb36dac42a169b72a5e7090504c48ba6c2425961a6", - releaseGenre: "0x111dd8bff35a1779067d7c75f8a514691342f91f67d2fcdae392768ba6db26f2", - releaseKind: "0x90bf2633b45699d424da616869ecb3d824a88d78d8f8d2ccf5b711b18a734bf1", - recordingAdvisory: "0x48a16738e6af6548e50d8f76dce9b9de247f365ef2a07c171f732bdc9c88b235", - recordingLanguage: "0x677cef9c766b17f87fdf9d6623f1adca0d2e244ee228c86d82c23599ad9a828a", - recordingMasterReference: "0x2b1ca393e8d41eba5b0d3cabf6df1e207a349bba0486ca9ea3a31734d3445971", - recordingEngineSession: "0x55ca06d2ac044247e55165a9cb661219d47cee94789cc289a6e1e91ac8159a76", + credit: "0xd77981b6872d975ccebeac1c639eebbcd1f7916f468df0242d4b26d9bf7293c1", + compositionCredits: "0x924a5e87230cf1218be23484e4914d9c5515528c8ef032b1796ebdc71dd3830c", + recordingCredits: "0x982d946a34bb342913c81441be1c702b29f2a83bc801c4fce9ed4f9d7833b8b1", + releaseCredits: "0xdc854106cb76733bb012db2369e0a39319fb79b0587e2a887814c7cb67134d77", + royaltyPool: "0xf7be632d74f71574c2aa5ac2790a51e770b2cfbcf80233b875bdbe24ffe0b3b9", + routedStake: "0xfe3f0c0008823330d2e465a300b34eec50ab3c7444e655602ed2efc517c91824", + coverArt: "0xc0b1421b32e287559ec29f6d7c71dd83778234cb62d4fe9d296577710411f6d4", + releaseCoverArt: "0x74c1708ff6016b5244f8d31559210ae6e5a2b5e52cb3c7996e1f630a7d4a87b2", + genre: "0xeea93dd140ee2133d1baeeb71281846658b104d53403d4f89e75f65ade38f931", + releaseDescription: "0x8d59667b5e9476df33125153494246107a1384ecbea8862fa1b5c256588789fa", + releaseDspLink: "0xebcf0515a35c765ca89208162510bdddd2a9225866b9c66f33dc3cfa1e07bf8a", + releaseGenre: "0xc7269a52efa400b80009f8c9f7e31591a26c50c0ae925be31edaef8889e91c83", + releaseKind: "0x48f1651e00572525fb2e59ebb37c5367d45258d62486984825c972a8ab2d0ce3", + recordingAdvisory: "0x4149faba212e3aca5440e5ea15bde535dca453f2701125a996d89188b0000ed0", + recordingLanguage: "0x78e48de29be0b9dca4921dd0740cc12c88cb02599ee8554854b4e423a689fd19", + recordingGenre: "0x3c017256c66c7d48f4c23ae40dd6d5a0470a4f85a750aadef388ef9ac8e71b36", + recordingMasterReference: "0x2638edd3c9fec5650eda561e77fb390add0110088ad54c470e83ea079108bb1a", + recordingEngineSession: "0xa3057f47e31683c1eba0afba56aa38af6734f6b9fc3fb679c2681a21ccfe3c24", recordingStreamingTranscode: - "0x45ee95108ddc6f90c1f4c73fb8aff5f0b7047d4982e6f997e2c9afe02f908a05", - recordSealPolicy: "0x3af51d7c48b32d37a1a3bd31f06cdd873032fca8663cb3f9d9cf516bc4ee08ba", + "0x67bde09257865521b221aebf83bc95e5a9ae9d38f2c77223772436bc9c70e8ec", + recordSealPolicy: "0x7e1921715dbda4fbb73227d0764f4c0de55fdcf3892ddadc4ef1bf895453b2a2", ori: "0x51792b9adb9a5d05d7c4d74d7d0cb5aefc5639afa80c0089399cab8b99752e60", + countryCode: "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", + languageCode: "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", }, objects: { - releaseRegistry: "0xbb947ba51420df59a726487e008adaf352b7c58c1dbfe1e5e6b19131a98cc43c", - genreRegistry: "0x63cfb245a15871b7a4c33b66487a3f4de0a7bd6431e3f0434cad4176c25e6eb5", + releaseRegistry: "0xb0506d287b50a134a2773a5d4e0c9c4e3ef4aa3806cad6ff93d8b28be063c6df", + genreRegistry: "0x9a5a7ce36906a0581e6e38a0dd6abc24093acc6ffd7c0d6a7f419c57b069d00e", }, legacy: { releaseCoverArtPackages: [] }, }); @@ -414,6 +430,7 @@ test("bundled Testnet deployment exactly matches the verified immutable export", const identities = [ deployment.chainIdentifier, ...Object.values(deployment.protocol), + ...Object.values(deployment.partyos), deployment.recordSales.recordPackageId, deployment.recordSales.recordShopPackageId, deployment.operations.vault.packageId, @@ -421,9 +438,13 @@ test("bundled Testnet deployment exactly matches the verified immutable export", ...Object.values(deployment.operations.actions), ...Object.values(deployment.operations.plugins), ...Object.values(deployment.packages), + ...Object.values(deployment.party), ...Object.values(deployment.objects), ]; - expect(identities).toHaveLength(64); + // `party.countryCode`/`party.languageCode` intentionally repeat + // `packages.countryCode`/`packages.languageCode` (see + // assertMisoPlatformDeployment), so this sanity list has duplicates by design. + expect(identities).toHaveLength(53); }); test("bundled deployment and every nested container are frozen", () => { @@ -450,7 +471,7 @@ test("custom deployment registration snapshots nested targets before readiness", expect(Object.isFrozen(custom)).toBeFalse(); expect(Object.isFrozen(custom.operations)).toBeFalse(); custom.chainIdentifier = "mutated-before-ready"; - custom.protocol.miso = id(701); + custom.protocol.musicos = id(701); if (custom.recordSales.status !== "available") throw new Error("test fixture"); custom.recordSales.recordPackageId = id(702); custom.recordSales.recordShopPackageId = id(703); @@ -485,10 +506,10 @@ test("custom deployment registration snapshots nested targets before readiness", expected.operations.status === "available" ? expected.operations.actions.partyWallet : "", - expected.protocol.miso, + expected.protocol.musicos, ]); - custom.protocol.miso = id(710); + custom.protocol.musicos = id(710); custom.recordSales.recordPackageId = id(711); custom.operations.actions.partyWallet = id(712); custom.legacy.releaseCoverArtPackages.push(id(713)); @@ -660,7 +681,7 @@ test("protocol and nested Party surfaces cannot read or build before readiness", expect(() => client.miso.protocol!.getReleaseById(A)).toThrow( MisoClientNotReadyError, ); - expect(() => client.miso.protocol!.party.getPartyById(A)).toThrow( + expect(() => client.miso.party.getPartyById(A)).toThrow( MisoClientNotReadyError, ); expect(() => @@ -678,7 +699,7 @@ test("protocol and nested Party surfaces cannot read or build before readiness", ), ).toThrow(MisoClientNotReadyError); expect(() => - tx.add(client.miso.protocol!.party.call.party.newIndividualKind({})), + tx.add(client.miso.party.call.party.newIndividualKind({})), ).toThrow(MisoClientNotReadyError); expect(tx.getData().commands).toHaveLength(0); expect(tx.getData().inputs).toHaveLength(0); @@ -695,17 +716,17 @@ test("protocol and nested Party surfaces cannot read or build before readiness", arguments: [A], }), ); - tx.add(client.miso.protocol!.party.call.party.newIndividualKind({})); + tx.add(client.miso.party.call.party.newIndividualKind({})); expect(moveCalls(tx).map((call) => call.package)).toEqual([ MISO, MISO, - NETWORK_DEPLOYMENT.misoParty, + PARTYOS_DEPLOYMENT.partyos, ]); await expect(client.miso.protocol!.getReleaseById(A)).rejects.toThrow( /Release not found/, ); - await expect(client.miso.protocol!.party.getPartyById(A)).rejects.toThrow( + await expect(client.miso.party.getPartyById(A)).rejects.toThrow( /Party not found/, ); expect(objectReads).toBe(2); @@ -713,6 +734,46 @@ test("protocol and nested Party surfaces cannot read or build before readiness", expect(chainReads).toBe(1); }); +test("MisoPlatformClient.party delegates core reads to PartyosClient and exposes extension tx builders", async () => { + const base = new SuiGrpcClient({ + network: "testnet", + baseUrl: "https://fullnode.testnet.sui.io:443", + }); + Object.defineProperty(base.core, "getChainIdentifier", { + configurable: true, + value: async () => ({ chainIdentifier: DEPLOYMENT.chainIdentifier }), + }); + Object.defineProperty(base.core, "getObject", { + configurable: true, + value: async ({ objectId }: { objectId: string }) => ({ + object: { + type: `${PARTYOS_DEPLOYMENT.partyos}::party::Party`, + content: partyCoreContracts.Party.serialize({ + id: objectId, + kind: { Individual: true }, + name: "Test Party", + created_at_ms: 1n, + }).toBytes(), + }, + }), + }); + const client = base.$extend(miso({ deployment: DEPLOYMENT })); + await client.miso.ready(); + + // Core read: `client.miso.party` delegates straight to a `PartyosClient`. + expect(client.miso.party.core).toBeInstanceOf(PartyosClient); + const party = await client.miso.party.getPartyById(A); + expect(party).toMatchObject({ id: A, kind: "individual", name: "Test Party" }); + + // Extension builder: `tx.setProfile` is not part of the Party core. + const tx = new Transaction(); + tx.add(client.miso.party.tx.setProfile({ partyId: A, capId: A, bioShort: "hi" })); + expect(moveCalls(tx).find((call) => call.module === "party_profile")).toMatchObject({ + package: PARTY_DEPLOYMENT.partyProfile, + function: "set_profile", + }); +}); + test("deprecated misoPlatform protocol access uses the same explicit readiness gate", async () => { const base = new SuiGrpcClient({ network: "testnet", diff --git a/packages/platform/tests/codegen-format.test.ts b/packages/platform/tests/codegen-format.test.ts index 0b4dc52..e71e576 100644 --- a/packages/platform/tests/codegen-format.test.ts +++ b/packages/platform/tests/codegen-format.test.ts @@ -1,25 +1,21 @@ // Copyright (c) Miso Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +// Codegen normalisation now lives at the monorepo root (`scripts/codegen.ts`, +// via `scripts/codegen-output.ts`), which runs against both generated trees. +// This test only asserts the platform tree's own output stays normalised and +// carries the addresses this SDK depends on — it derives from the same +// `normalizeBcsAnchor` the generator itself uses rather than re-implementing +// the formatting rule. import { expect, test } from "bun:test"; - -const suiBcsImport = /^import .* from '@mysten\/sui\/bcs';$/m; -const directBcsAnchor = 'import type {} from "@mysten/bcs";'; +import { normalizeBcsAnchor } from "../../../scripts/codegen-output.ts"; test("generated BCS modules carry exactly one direct declaration anchor", async () => { const generated = new Bun.Glob("src/contracts/**/*.ts"); - for await (const path of generated.scan({ - cwd: import.meta.dir + "/../../protocol", - })) { - const source = await Bun.file( - import.meta.dir + "/../../protocol/" + path, - ).text(); - const anchorCount = source - .split("\n") - .filter((line) => line === directBcsAnchor).length; - - expect(anchorCount, path).toBe(suiBcsImport.test(source) ? 1 : 0); + for await (const path of generated.scan({ cwd: import.meta.dir + "/.." })) { + const source = await Bun.file(import.meta.dir + "/../" + path).text(); + expect(normalizeBcsAnchor(source), path).toBe(source); } }); @@ -34,13 +30,9 @@ test("release cover bindings use the verified transitive Ori address", async () const files: string[] = []; let sources = ""; - for await (const path of generated.scan({ - cwd: import.meta.dir + "/../../protocol", - })) { + for await (const path of generated.scan({ cwd: import.meta.dir + "/.." })) { files.push(path); - sources += await Bun.file( - import.meta.dir + "/../../protocol/" + path, - ).text(); + sources += await Bun.file(import.meta.dir + "/../" + path).text(); } expect(files).toContain( diff --git a/packages/platform/tests/dependency-contract.test.ts b/packages/platform/tests/dependency-contract.test.ts index 3fcd9d8..b3557dd 100644 --- a/packages/platform/tests/dependency-contract.test.ts +++ b/packages/platform/tests/dependency-contract.test.ts @@ -27,8 +27,11 @@ test("Sui uses one exact development and peer dependency contract", async () => ); expect(Object.keys(consumer.dependencies ?? {})).toEqual([ + "@misofm/musicos", + "@misofm/partyos", "@misofm/platform", - "@misofm/protocol", + "@misofm/streaming", + "@misofm/transcoding", "typescript", ]); diff --git a/packages/platform/tests/events.test.ts b/packages/platform/tests/events.test.ts new file mode 100644 index 0000000..db274da --- /dev/null +++ b/packages/platform/tests/events.test.ts @@ -0,0 +1,110 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Covers `platformEventParsers` — the extension and generic-primitive event +// decoders that used to live on the pre-split protocol package's combined `eventParsers` +// before the musicos/platform split (issue misofm/sdks#26). Core object-model +// event decoding (`eventParsers.core`) is tested in `@misofm/musicos` itself. + +import { expect, test } from "bun:test"; +import { platformEventParsers } from "../src/events.ts"; +import { DescriptionSetEvent } from "../src/contracts/release_description/release_description.ts"; +import { RoyaltyClaimedEvent } from "../src/contracts/royalty_pool/pool.ts"; +import { RoutedStakeSweptEvent } from "../src/contracts/routed_stake/routed_stake.ts"; +import { + GenreAddedEvent as ReleaseGenreAddedEvent, + GenreRemovedEvent as ReleaseGenreRemovedEvent, + GenresClearedEvent as ReleaseGenresClearedEvent, +} from "../src/contracts/release_genre/release_genre.ts"; +import { + GenreAddedEvent as RecordingGenreAddedEvent, + GenreRemovedEvent as RecordingGenreRemovedEvent, + GenresClearedEvent as RecordingGenresClearedEvent, +} from "../src/contracts/recording_genre/recording_genre.ts"; + +const EVENTS_A = "0x" + "12".repeat(32); +const EVENTS_B = "0x" + "34".repeat(32); + +test("extension event decoders preserve current generated BCS fields", () => { + const bytes = DescriptionSetEvent.serialize({ + release_id: EVENTS_A, + description: "Liner notes", + }).toBytes(); + + expect(platformEventParsers.extensions.releaseDescription.descriptionSet(bytes)).toEqual({ + release_id: EVENTS_A, + description: "Liner notes", + }); +}); + +test("release_genre and recording_genre event decoders round-trip the current ABI", () => { + const releaseAdded = ReleaseGenreAddedEvent.serialize({ + release_id: EVENTS_A, + genre_id: EVENTS_B, + }).toBytes(); + const releaseRemoved = ReleaseGenreRemovedEvent.serialize({ + release_id: EVENTS_A, + genre_id: EVENTS_B, + }).toBytes(); + const releaseCleared = ReleaseGenresClearedEvent.serialize({ + release_id: EVENTS_A, + }).toBytes(); + const recordingAdded = RecordingGenreAddedEvent.serialize({ + recording_id: EVENTS_A, + genre_id: EVENTS_B, + }).toBytes(); + const recordingRemoved = RecordingGenreRemovedEvent.serialize({ + recording_id: EVENTS_A, + genre_id: EVENTS_B, + }).toBytes(); + const recordingCleared = RecordingGenresClearedEvent.serialize({ + recording_id: EVENTS_A, + }).toBytes(); + + expect(platformEventParsers.extensions.releaseGenre.genreAdded(releaseAdded)).toEqual({ + release_id: EVENTS_A, + genre_id: EVENTS_B, + }); + expect(platformEventParsers.extensions.releaseGenre.genreRemoved(releaseRemoved)).toEqual({ + release_id: EVENTS_A, + genre_id: EVENTS_B, + }); + expect(platformEventParsers.extensions.releaseGenre.genresCleared(releaseCleared)).toEqual({ + release_id: EVENTS_A, + }); + expect(platformEventParsers.extensions.recordingGenre.genreAdded(recordingAdded)).toEqual({ + recording_id: EVENTS_A, + genre_id: EVENTS_B, + }); + expect(platformEventParsers.extensions.recordingGenre.genreRemoved(recordingRemoved)).toEqual({ + recording_id: EVENTS_A, + genre_id: EVENTS_B, + }); + expect(platformEventParsers.extensions.recordingGenre.genresCleared(recordingCleared)).toEqual({ + recording_id: EVENTS_A, + }); +}); + +test("royalty pool and routed stake primitive event decoders round-trip the current ABI", () => { + const claimed = RoyaltyClaimedEvent.serialize({ + pool_id: EVENTS_A, + stake_id: EVENTS_B, + reward_amount: "42", + }).toBytes(); + const swept = RoutedStakeSweptEvent.serialize({ + routed_stake_id: EVENTS_A, + parent_id: EVENTS_B, + value: "99", + }).toBytes(); + + expect(platformEventParsers.primitives.royaltyPool.royaltyClaimed(claimed)).toEqual({ + pool_id: EVENTS_A, + stake_id: EVENTS_B, + reward_amount: "42", + }); + expect(platformEventParsers.primitives.routedStake.swept(swept)).toEqual({ + routed_stake_id: EVENTS_A, + parent_id: EVENTS_B, + value: "99", + }); +}); diff --git a/packages/platform/tests/fixtures/.gitignore b/packages/platform/tests/fixtures/.gitignore new file mode 100644 index 0000000..aa1ec1e --- /dev/null +++ b/packages/platform/tests/fixtures/.gitignore @@ -0,0 +1 @@ +*.tgz diff --git a/packages/platform/tests/fixtures/isolated-consumer/.gitignore b/packages/platform/tests/fixtures/isolated-consumer/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/packages/platform/tests/fixtures/isolated-consumer/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/packages/platform/tests/fixtures/isolated-consumer/imports.ts b/packages/platform/tests/fixtures/isolated-consumer/imports.ts index e23b63d..9231bfd 100644 --- a/packages/platform/tests/fixtures/isolated-consumer/imports.ts +++ b/packages/platform/tests/fixtures/isolated-consumer/imports.ts @@ -1,9 +1,23 @@ -// Proves the published `.d.ts` for @misofm/platform and @misofm/protocol are -// self-contained when installed as real tarballs (see ../../../../scripts or -// the isolated-consumer check driving this fixture). Every subpath below is -// used for both a value and a type so tsc cannot elide the import — if any -// packaged declaration file is missing, malformed, or has a broken relative -// path, this file fails to typecheck with `skipLibCheck: false`. +// Proves the published `.d.ts` for @misofm/musicos, @misofm/platform, +// @misofm/streaming, and @misofm/transcoding are self-contained when +// installed as real tarballs (see ../../../../scripts/check-consumer.ts, or +// `bun run test:consumer` at the repo root, driving this fixture). Every +// subpath below is used for both a value and a type so tsc cannot elide the +// import — if any packaged declaration file is missing, malformed, or has a +// broken relative path, this file fails to typecheck with `skipLibCheck: false`. + +import { MisoClient as MusicosRootClient, type MisoOptions as MusicosRootOptions } from "@misofm/musicos"; +import { MisoProtocolClient, type MisoProtocolClientOptions } from "@misofm/musicos/client"; +import { isNotFound, type BcsParser } from "@misofm/musicos/queries"; +import { composition as musicosComposition } from "@misofm/musicos/contracts"; + +// The raw wildcard subpath (`./contracts/*`): a deep module not exposed +// through the curated barrel. +import { Composition as CompositionStruct } from "@misofm/musicos/contracts/musicos/composition"; + +import { PartyosClient, type Party as PartyosParty } from "@misofm/partyos"; +import { getPartyDeployment, type PartyDeployment } from "@misofm/partyos/deployments"; +import { Party as PartyStruct } from "@misofm/partyos/contracts/partyos/party"; import { MisoClient as PlatformRootClient, type MisoOptions as PlatformRootOptions } from "@misofm/platform"; import { MisoPlatformClient, type MisoPlatformConfig } from "@misofm/platform/client"; @@ -11,69 +25,90 @@ import { derivePressingId, type OpenPressingParams } from "@misofm/platform/pres import { directAdminCap, type AdminCapAuthority } from "@misofm/platform/vault"; import { misoConfig, type MisoConfig } from "@misofm/platform/read"; import { attachCompositionCredit, type CompositionRole } from "@misofm/platform/credits"; - -import { MisoClient as ProtocolRootClient, type MisoOptions as ProtocolRootOptions } from "@misofm/protocol"; -import { MisoProtocolClient, type MisoProtocolClientOptions } from "@misofm/protocol/client"; -import { PartyProtocolClient, type Party } from "@misofm/protocol/party"; -import { isNotFound, type BcsParser } from "@misofm/protocol/queries"; -import { party as contractsParty } from "@misofm/protocol/contracts"; +import { PartyPlatformClient, type Profile } from "@misofm/platform/party"; +import { record as platformRecord } from "@misofm/platform/contracts"; // The raw wildcard subpath (`./contracts/*`): a deep module and a nested // `deps/*` module, neither of which is exposed through the curated barrel. -import { Record as MisoRecordStruct } from "@misofm/protocol/contracts/miso_record/record"; -import { LanguageCode as LanguageCodeTuple } from "@misofm/protocol/contracts/recording_language/deps/language_code/language_code"; +import { Record as RecordStruct } from "@misofm/platform/contracts/record/record"; +import { LanguageCode as LanguageCodeTuple } from "@misofm/platform/contracts/recording_language/deps/language_code/language_code"; + +import { HLS_CONTRACT, type Rendition } from "@misofm/streaming"; +import { chooseSegmentTargetMs, type TranscodeRequest } from "@misofm/transcoding"; // Type-position uses of every imported type. Each is either a directly -// exported interface/type alias, or (for the curated `contracts` barrel and +// exported interface/type alias, or (for the curated `contracts` barrels and // the raw BCS codecs, which expose no standalone type export) derived from // the value itself so it still proves the underlying declaration resolves. +type _MusicosRootOptions = MusicosRootOptions; +type _MusicosClientOptions = MisoProtocolClientOptions; +type _MusicosBcsParser = BcsParser; +type _MusicosContractsCompositionNewOptions = Parameters[0]; +type _Composition = ReturnType; + +type _PartyosParty = PartyosParty; +type _PartyosDeployment = PartyDeployment; +type _PartyStruct = ReturnType; + type _PlatformRootOptions = PlatformRootOptions; type _PlatformClientConfig = MisoPlatformConfig; type _PlatformPressingParams = OpenPressingParams; type _PlatformVaultAuthority = AdminCapAuthority; type _PlatformReadConfig = MisoConfig; type _PlatformCreditRole = CompositionRole; - -type _ProtocolRootOptions = ProtocolRootOptions; -type _ProtocolClientOptions = MisoProtocolClientOptions; -type _ProtocolParty = Party; -type _ProtocolBcsParser = BcsParser; -type _ProtocolContractsPartyNewOptions = Parameters[0]; - -type _MisoRecord = ReturnType; +type _PlatformParty = Profile; +type _PlatformContractsRecordReleaseIdOptions = Parameters[0]; +type _Record = ReturnType; type _LanguageCode = ReturnType; +type _StreamingRendition = Rendition; +type _TranscodingRequest = TranscodeRequest; + // Value-position uses of every imported value. void ([ + MusicosRootClient, + MisoProtocolClient, + isNotFound, + musicosComposition, + CompositionStruct, + PartyosClient, + getPartyDeployment, + PartyStruct, PlatformRootClient, MisoPlatformClient, derivePressingId, directAdminCap, misoConfig, attachCompositionCredit, - ProtocolRootClient, - MisoProtocolClient, - PartyProtocolClient, - isNotFound, - contractsParty, - MisoRecordStruct, + PartyPlatformClient, + platformRecord, + RecordStruct, LanguageCodeTuple, + HLS_CONTRACT, + chooseSegmentTargetMs, ] satisfies unknown[]); // Referenced so the otherwise-unused type aliases above cannot be reported as // dead code by a stricter consumer tsconfig than this fixture's own. export type IsolatedConsumerTypeProbe = [ + _MusicosRootOptions, + _MusicosClientOptions, + _MusicosBcsParser, + _MusicosContractsCompositionNewOptions, + _Composition, + _PartyosParty, + _PartyosDeployment, + _PartyStruct, _PlatformRootOptions, _PlatformClientConfig, _PlatformPressingParams, _PlatformVaultAuthority, _PlatformReadConfig, _PlatformCreditRole, - _ProtocolRootOptions, - _ProtocolClientOptions, - _ProtocolParty, - _ProtocolBcsParser, - _ProtocolContractsPartyNewOptions, - _MisoRecord, + _PlatformParty, + _PlatformContractsRecordReleaseIdOptions, + _Record, _LanguageCode, + _StreamingRendition, + _TranscodingRequest, ]; diff --git a/packages/platform/tests/fixtures/isolated-consumer/package.json b/packages/platform/tests/fixtures/isolated-consumer/package.json index d6958ba..ec87476 100644 --- a/packages/platform/tests/fixtures/isolated-consumer/package.json +++ b/packages/platform/tests/fixtures/isolated-consumer/package.json @@ -1,13 +1,16 @@ { - "name": "misofm-platform-isolated-consumer", + "name": "misofm-sdks-isolated-consumer", "private": true, "type": "module", "scripts": { "check": "tsc --noEmit && node verify.mjs" }, "dependencies": { + "@misofm/musicos": "file:../misofm-musicos.tgz", + "@misofm/partyos": "file:../misofm-partyos.tgz", "@misofm/platform": "file:../misofm-platform.tgz", - "@misofm/protocol": "file:../misofm-protocol.tgz", + "@misofm/streaming": "file:../misofm-streaming.tgz", + "@misofm/transcoding": "file:../misofm-transcoding.tgz", "typescript": "5.9.3" } } diff --git a/packages/platform/tests/fixtures/isolated-consumer/tsconfig.json b/packages/platform/tests/fixtures/isolated-consumer/tsconfig.json index 36c23e0..f690ecc 100644 --- a/packages/platform/tests/fixtures/isolated-consumer/tsconfig.json +++ b/packages/platform/tests/fixtures/isolated-consumer/tsconfig.json @@ -6,7 +6,8 @@ "moduleResolution": "bundler", "strict": true, "skipLibCheck": false, - "noEmit": true + "noEmit": true, + "types": [] }, "include": ["imports.ts"] } diff --git a/packages/platform/tests/fixtures/isolated-consumer/verify.mjs b/packages/platform/tests/fixtures/isolated-consumer/verify.mjs index ca67571..9a97db1 100644 --- a/packages/platform/tests/fixtures/isolated-consumer/verify.mjs +++ b/packages/platform/tests/fixtures/isolated-consumer/verify.mjs @@ -1,20 +1,26 @@ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs"; import { join } from "node:path"; +import * as musicosRoot from "@misofm/musicos"; +import * as musicosClient from "@misofm/musicos/client"; +import * as musicosQueries from "@misofm/musicos/queries"; +import * as musicosContracts from "@misofm/musicos/contracts"; + +import * as partyosRoot from "@misofm/partyos"; +import * as partyosContracts from "@misofm/partyos/contracts"; + import * as platformRoot from "@misofm/platform"; import * as platformClient from "@misofm/platform/client"; import * as platformPressing from "@misofm/platform/pressing"; import * as platformVault from "@misofm/platform/vault"; import * as platformRead from "@misofm/platform/read"; import * as platformCredits from "@misofm/platform/credits"; +import * as platformParty from "@misofm/platform/party"; +import * as recordModule from "@misofm/platform/contracts/record/record"; +import * as languageCodeModule from "@misofm/platform/contracts/recording_language/deps/language_code/language_code"; -import * as protocolRoot from "@misofm/protocol"; -import * as protocolClient from "@misofm/protocol/client"; -import * as protocolParty from "@misofm/protocol/party"; -import * as protocolQueries from "@misofm/protocol/queries"; -import * as protocolContracts from "@misofm/protocol/contracts"; -import * as recordModule from "@misofm/protocol/contracts/miso_record/record"; -import * as languageCodeModule from "@misofm/protocol/contracts/recording_language/deps/language_code/language_code"; +import * as streamingRoot from "@misofm/streaming"; +import * as transcodingRoot from "@misofm/transcoding"; function invariant(condition, message) { if (!condition) throw new Error(message); @@ -26,6 +32,33 @@ function hasExport(namespace, name) { // --- module load + expected-export checks ------------------------------- +invariant(Object.keys(musicosRoot).length > 0, "@misofm/musicos root export did not load"); +invariant(hasExport(musicosRoot, "MisoClient"), "@misofm/musicos root missing MisoClient"); +invariant(hasExport(musicosRoot, "contracts"), "@misofm/musicos root missing contracts namespace"); + +invariant(Object.keys(musicosClient).length > 0, "@misofm/musicos/client export did not load"); +invariant(hasExport(musicosClient, "MisoProtocolClient"), "@misofm/musicos/client missing MisoProtocolClient"); + +invariant(Object.keys(musicosQueries).length > 0, "@misofm/musicos/queries export did not load"); +invariant(hasExport(musicosQueries, "isNotFound"), "@misofm/musicos/queries missing isNotFound"); +invariant(musicosQueries.isNotFound(new Error("not found")) === false, "isNotFound behaved unexpectedly"); + +invariant(Object.keys(musicosContracts).length > 0, "@misofm/musicos/contracts export did not load"); +invariant(hasExport(musicosContracts, "composition"), "@misofm/musicos/contracts (curated) missing composition"); +invariant( + typeof musicosContracts.composition._new === "function", + "@misofm/musicos/contracts (curated) composition._new did not load as a function", +); +invariant( + !("uid" in musicosContracts.composition), + "@misofm/musicos/contracts (curated) leaked the raw `uid` accessor it is meant to curate away", +); + +invariant(Object.keys(partyosRoot).length > 0, "@misofm/partyos root export did not load"); +invariant(hasExport(partyosRoot, "PartyosClient"), "@misofm/partyos root missing PartyosClient"); +invariant(hasExport(partyosContracts, "party"), "@misofm/partyos/contracts missing party"); +invariant(!("uid" in partyosContracts.party), "@misofm/partyos/contracts leaked the raw `uid` accessor"); + invariant(Object.keys(platformRoot).length > 0, "@misofm/platform root export did not load"); invariant(hasExport(platformRoot, "MisoClient"), "@misofm/platform root missing MisoClient"); invariant(hasExport(platformRoot, "contracts"), "@misofm/platform root missing contracts namespace"); @@ -45,45 +78,31 @@ invariant(hasExport(platformRead, "misoConfig"), "@misofm/platform/read missing invariant(Object.keys(platformCredits).length > 0, "@misofm/platform/credits export did not load"); invariant(hasExport(platformCredits, "attachCompositionCredit"), "@misofm/platform/credits missing attachCompositionCredit"); -invariant(Object.keys(protocolRoot).length > 0, "@misofm/protocol root export did not load"); -invariant(hasExport(protocolRoot, "MisoClient"), "@misofm/protocol root missing MisoClient"); -invariant(hasExport(protocolRoot, "party"), "@misofm/protocol root missing party namespace"); -invariant(hasExport(protocolRoot, "contracts"), "@misofm/protocol root missing contracts namespace"); - -invariant(Object.keys(protocolClient).length > 0, "@misofm/protocol/client export did not load"); -invariant(hasExport(protocolClient, "MisoProtocolClient"), "@misofm/protocol/client missing MisoProtocolClient"); - -invariant(Object.keys(protocolParty).length > 0, "@misofm/protocol/party export did not load"); -invariant(hasExport(protocolParty, "PartyProtocolClient"), "@misofm/protocol/party missing PartyProtocolClient"); - -invariant(Object.keys(protocolQueries).length > 0, "@misofm/protocol/queries export did not load"); -invariant(hasExport(protocolQueries, "isNotFound"), "@misofm/protocol/queries missing isNotFound"); -invariant(protocolQueries.isNotFound(new Error("not found")) === false, "isNotFound behaved unexpectedly"); - -invariant(Object.keys(protocolContracts).length > 0, "@misofm/protocol/contracts export did not load"); -invariant(hasExport(protocolContracts, "party"), "@misofm/protocol/contracts (curated) missing party"); -invariant( - typeof protocolContracts.party._new === "function", - "@misofm/protocol/contracts (curated) party._new did not load as a function", -); -invariant( - !("uid" in protocolContracts.party), - "@misofm/protocol/contracts (curated) leaked the raw `uid` accessor it is meant to curate away", -); +invariant(Object.keys(platformParty).length > 0, "@misofm/platform/party export did not load"); +invariant(hasExport(platformParty, "PartyPlatformClient"), "@misofm/platform/party missing PartyPlatformClient"); // Raw wildcard subpath: a deep module and a nested `deps/*` module that are // NOT reachable through the curated `./contracts` barrel. -invariant(Object.keys(recordModule).length > 0, "@misofm/protocol/contracts/miso_record/record did not load"); -invariant(hasExport(recordModule, "Record"), "raw contracts/miso_record/record missing Record"); +invariant(Object.keys(recordModule).length > 0, "@misofm/platform/contracts/record/record did not load"); +invariant(hasExport(recordModule, "Record"), "raw contracts/record/record missing Record"); invariant(typeof recordModule.Record.parse === "function", "Record BCS codec missing parse()"); invariant( Object.keys(languageCodeModule).length > 0, - "@misofm/protocol/contracts/recording_language/deps/language_code/language_code did not load", + "@misofm/platform/contracts/recording_language/deps/language_code/language_code did not load", ); invariant(hasExport(languageCodeModule, "LanguageCode"), "raw nested deps/language_code module missing LanguageCode"); invariant(typeof languageCodeModule.LanguageCode.parse === "function", "LanguageCode BCS codec missing parse()"); +invariant(Object.keys(streamingRoot).length > 0, "@misofm/streaming root export did not load"); +invariant(hasExport(streamingRoot, "HLS_CONTRACT"), "@misofm/streaming root missing HLS_CONTRACT"); + +invariant(Object.keys(transcodingRoot).length > 0, "@misofm/transcoding root export did not load"); +invariant( + hasExport(transcodingRoot, "chooseSegmentTargetMs"), + "@misofm/transcoding root missing chooseSegmentTargetMs", +); + // --- dependency identity: package manager agnostic ----------------------- // // Walks every `node_modules` directory reachable from this consumer (real @@ -130,11 +149,30 @@ function findPackageInstances(root, packageName) { const consumerRoot = import.meta.dirname; -const suiInstances = findPackageInstances(consumerRoot, "@mysten/sui"); -invariant(suiInstances.size === 1, `expected exactly one @mysten/sui instance, found ${suiInstances.size}`); +function assertSingleInstance(packageName) { + const instances = findPackageInstances(consumerRoot, packageName); + invariant(instances.size === 1, `expected exactly one ${packageName} instance, found ${instances.size}`); + const [manifestPath] = instances; + const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); + console.log(`isolated consumer verified: single ${packageName}@${manifest.version} installed`); +} -const [suiManifestPath] = suiInstances; -const suiManifest = JSON.parse(readFileSync(suiManifestPath, "utf8")); +assertSingleInstance("@mysten/sui"); + +// A consumer that ends up resolving two copies of the same `@misofm/*` +// package (e.g. because one package pins another at a version distinct from +// the workspace's) gets two distinct class identities for the same name, so +// `instanceof` checks and BCS codecs passed across the package boundary fail +// silently. Discovered from the fixture's own `node_modules/@misofm/*` +// rather than hard-coded, so this check covers whatever this fixture's +// `package.json` currently installs. +const misofmScopeDir = join(consumerRoot, "node_modules", "@misofm"); +const installedMisofmPackages = readdirSync(misofmScopeDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => `@misofm/${entry.name}`); +invariant(installedMisofmPackages.length > 0, "no @misofm/* packages found under node_modules/@misofm"); +for (const packageName of installedMisofmPackages) { + assertSingleInstance(packageName); +} -console.log(`isolated consumer verified: single @mysten/sui@${suiManifest.version} installed`); console.log("isolated consumer dependency identity verified"); diff --git a/packages/platform/tests/genre.test.ts b/packages/platform/tests/genre.test.ts index 9779749..0c625b8 100644 --- a/packages/platform/tests/genre.test.ts +++ b/packages/platform/tests/genre.test.ts @@ -6,8 +6,8 @@ import { bcs } from "@mysten/sui/bcs"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; import { fromBase64 } from "@mysten/sui/utils"; -import * as releaseGenreContract from "@misofm/protocol/contracts/release_genre/release_genre"; -import * as recordingGenreContract from "@misofm/protocol/contracts/recording_genre/recording_genre"; +import * as releaseGenreContract from "../src/contracts/release_genre/release_genre.ts"; +import * as recordingGenreContract from "../src/contracts/recording_genre/recording_genre.ts"; import { addRecordingGenre, addReleaseGenre, diff --git a/packages/platform/tests/packages.test.ts b/packages/platform/tests/packages.test.ts new file mode 100644 index 0000000..013bd28 --- /dev/null +++ b/packages/platform/tests/packages.test.ts @@ -0,0 +1,147 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Covers `MisoPlatformPackageBindings` / `misoPlatformPackages` — the +// extensions/primitives/party package-bound generated calls that used to live +// on the pre-split protocol package's combined `MisoPackageBindings` before the +// musicos/platform split (issue misofm/sdks#26). + +import { expect, test } from "bun:test"; +import { Transaction } from "@mysten/sui/transactions"; +import { misoPlatformPackages } from "../src/packages.ts"; +import { getMisoPlatformDeployment } from "../src/deployments.ts"; +import type { MisoPlatformDeployment } from "../src/deployments.ts"; + +const id = (value: number) => `0x${value.toString(16).padStart(64, "0")}`; +const SHARE = `0x${"ab".repeat(32)}::share::Share`; +const A = `0x${"11".repeat(32)}`; + +const base = getMisoPlatformDeployment("testnet"); + +/** A full, canonical, pairwise-distinct platform deployment for binding tests. */ +const FULL_DEPLOYMENT: MisoPlatformDeployment = { + ...base, + packages: { + ...base.packages, + compositionCredits: id(1), + recordingCredits: id(2), + recordingMasterReference: id(3), + releaseCoverArt: id(4), + releaseCredits: id(5), + releaseDescription: id(6), + releaseDspLink: id(7), + releaseGenre: id(8), + releaseKind: id(9), + recordingAdvisory: id(10), + recordingLanguage: id(11), + royaltyPool: id(12), + routedStake: id(13), + countryCode: id(14), + languageCode: id(15), + }, + partyos: { + ...base.partyos, + partyos: id(20), + }, + party: { + ...base.party, + partyCta: id(21), + partyGenre: id(22), + partyMedia: id(23), + partyMusic: id(24), + partyPlatformLink: id(25), + partyProLink: id(26), + partyProfile: id(27), + partyRoles: id(28), + partySocial: id(29), + partyTags: id(30), + // The two shared dependency packages must repeat `packages.*` exactly. + countryCode: id(14), + languageCode: id(15), + }, +}; + +interface Call { + package?: string; + module: string; + function: string; +} +function moveCalls(tx: Transaction): Call[] { + return (tx.getData().commands as Array<{ $kind: string; MoveCall?: Call }>) + .filter((command) => command.$kind === "MoveCall") + .map((command) => command.MoveCall!); +} + +test("extension and primitive calls bind to their own fresh package IDs", () => { + const packages = misoPlatformPackages(FULL_DEPLOYMENT); + const tx = new Transaction(); + + tx.add( + packages.call.extensions.recordingAdvisory.unsetRating({ + typeArguments: [SHARE, SHARE], + arguments: [tx.object(A), tx.object(A)], + }), + ); + tx.add( + packages.call.primitives.royaltyPool.pool.sweepAndDeposit({ + typeArguments: [SHARE, "0x2::sui::SUI"], + arguments: [tx.object(A)], + }), + ); + + const calls = moveCalls(tx); + expect(calls.find((call) => call.module === "recording_advisory")).toMatchObject({ + package: FULL_DEPLOYMENT.packages.recordingAdvisory, + function: "unset_rating", + }); + expect(calls.find((call) => call.function === "sweep_and_deposit")).toMatchObject({ + package: FULL_DEPLOYMENT.packages.royaltyPool, + module: "pool", + }); +}); + +test("party calls bind to the party deployment and omit unsafe reference-returning calls", () => { + const packages = misoPlatformPackages(FULL_DEPLOYMENT); + const tx = new Transaction(); + + tx.add( + packages.call.party.core.setName({ + arguments: [tx.object(A), tx.object(A), tx.pure.string("Miso")], + }), + ); + + expect(moveCalls(tx).find((call) => call.module === "party")).toMatchObject({ + package: FULL_DEPLOYMENT.partyos.partyos, + function: "set_name", + }); + expect("uid" in packages.call.party.core).toBeFalse(); + expect("uidMut" in packages.call.party.core).toBeFalse(); + expect("groupMembers" in packages.call.party.core).toBeFalse(); + expect("profile" in packages.call.party.profile).toBeFalse(); +}); + +test("package-bound calls omit reference-returning Move views", () => { + const packages = misoPlatformPackages(FULL_DEPLOYMENT); + + expect("credits" in packages.call.extensions.compositionCredits.compositionCredits).toBeFalse(); + expect("register" in packages.call.primitives.routedStake).toBeFalse(); + expect("unregister" in packages.call.primitives.routedStake).toBeFalse(); + expect("unstake" in packages.call.primitives.routedStake).toBeFalse(); + expect("restake" in packages.call.primitives.routedStake).toBeFalse(); + expect("_new" in packages.call.primitives.royaltyPool.pool).toBeFalse(); +}); + +test("bcs codecs stay available even where the matching call is filtered out", () => { + const packages = misoPlatformPackages(FULL_DEPLOYMENT); + expect(packages.bcs.party.core).toBeDefined(); + expect(packages.bcs.primitives.royaltyPool.pool).toBeDefined(); + expect(packages.bcs.extensions.compositionCredits.compositionCredits).toBeDefined(); +}); + +test("misoPlatformPackages rejects an incomplete deployment", () => { + const incomplete = { + ...FULL_DEPLOYMENT, + packages: { ...FULL_DEPLOYMENT.packages, royaltyPool: undefined }, + } as unknown as MisoPlatformDeployment; + expect(() => misoPlatformPackages(incomplete)).toThrow(); +}); diff --git a/packages/protocol/tests/party-internal.test.ts b/packages/platform/tests/party-internal.test.ts similarity index 100% rename from packages/protocol/tests/party-internal.test.ts rename to packages/platform/tests/party-internal.test.ts diff --git a/packages/platform/tests/pressing-read.test.ts b/packages/platform/tests/pressing-read.test.ts index 7fb08fe..c30e930 100644 --- a/packages/platform/tests/pressing-read.test.ts +++ b/packages/platform/tests/pressing-read.test.ts @@ -21,9 +21,9 @@ import { purchaseRecord, RECORD_SALES_DERIVATION_VECTOR_V1, } from "../src/pressing.ts"; -import * as pressing from "@misofm/protocol/contracts/miso_record/pressing"; -import * as record from "@misofm/protocol/contracts/miso_record/record"; -import * as listing from "@misofm/protocol/contracts/miso_record_shop/listing"; +import * as pressing from "../src/contracts/record/pressing.ts"; +import * as record from "../src/contracts/record/record.ts"; +import * as listing from "../src/contracts/record_shop/listing.ts"; const BUYER = `0x${"44".repeat(32)}`; const RELEASE = RECORD_SALES_DERIVATION_VECTOR_V1.releaseId; diff --git a/packages/platform/tests/publication.test.ts b/packages/platform/tests/publication.test.ts index 35562c0..4bae7ed 100644 --- a/packages/platform/tests/publication.test.ts +++ b/packages/platform/tests/publication.test.ts @@ -15,7 +15,7 @@ import { deriveCompositionAdminCapId, deriveRecordingAdminCapId, deriveReleaseAdminCapId, -} from "@misofm/protocol"; +} from "@misofm/musicos"; import { assertAtomicPublicationBounds, inspectAtomicPublication, @@ -213,7 +213,7 @@ test("atomic publication includes the full graph, extensions, plugins, and custo expect(count("vault::new")).toBe(5); expect(count("vault::share")).toBe(5); expect(tx.getData().commands.filter((command) => command.$kind === "TransferObjects")).toHaveLength(7); - const recordingCapType = `${input.deployment.protocol.miso}::recording::RecordingAdminCap<${SHARE_2}>`; + const recordingCapType = `${input.deployment.protocol.musicos}::recording::RecordingAdminCap<${SHARE_2}>`; const recordingVault = tx .getData() .commands.find( @@ -502,15 +502,15 @@ test("atomic result parsing maps canonical Vault events without requiring top-le const routedStakeId = "0x" + "36".repeat(32); const compositionCapId = deriveCompositionAdminCapId( compositionId, - deployment.protocol.miso, + deployment.protocol.musicos, ); const recordingCapId = deriveRecordingAdminCapId( recordingId, - deployment.protocol.miso, + deployment.protocol.musicos, ); const releaseCapId = deriveReleaseAdminCapId( releaseId, - deployment.protocol.miso, + deployment.protocol.musicos, ); const sales = deployment.recordSales.status === "available" @@ -543,9 +543,9 @@ test("atomic result parsing maps canonical Vault events without requiring top-le }).toBytes(), })); const objectTypes: Record = { - [compositionId]: `${deployment.protocol.miso}::composition::Composition<${SHARE_1}>`, - [recordingId]: `${deployment.protocol.miso}::recording::Recording<${SHARE_2},${SHARE_1}>`, - [releaseId]: `${deployment.protocol.miso}::release::Release`, + [compositionId]: `${deployment.protocol.musicos}::composition::Composition<${SHARE_1}>`, + [recordingId]: `${deployment.protocol.musicos}::recording::Recording<${SHARE_2},${SHARE_1}>`, + [releaseId]: `${deployment.protocol.musicos}::release::Release`, [compositionPoolId]: `${deployment.packages.royaltyPool}::pool::RoyaltyPool<${SHARE_1},0x2::sui::SUI>`, [recordingPoolId]: `${deployment.packages.royaltyPool}::pool::RoyaltyPool<${SHARE_2},0x2::sui::SUI>`, [routedStakeId]: `${deployment.packages.routedStake}::routed_stake::RoutedStake<${SHARE_2},${SHARE_1}>`, diff --git a/packages/platform/tests/read/catalog-resources.test.ts b/packages/platform/tests/read/catalog-resources.test.ts index 529b54c..adf4caf 100644 --- a/packages/platform/tests/read/catalog-resources.test.ts +++ b/packages/platform/tests/read/catalog-resources.test.ts @@ -4,8 +4,8 @@ import { expect, test } from "bun:test"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import { deriveSaleIds } from "../../src/pressing.ts"; -import * as listing from "@misofm/protocol/contracts/miso_record_shop/listing"; -import * as pressing from "@misofm/protocol/contracts/miso_record/pressing"; +import * as listing from "../../src/contracts/record_shop/listing.ts"; +import * as pressing from "../../src/contracts/record/pressing.ts"; import { getListingView, getPressingView, diff --git a/packages/platform/tests/read/receipts.test.ts b/packages/platform/tests/read/receipts.test.ts index 4145d96..8fe9420 100644 --- a/packages/platform/tests/read/receipts.test.ts +++ b/packages/platform/tests/read/receipts.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, test } from "bun:test"; -import * as listing from "@misofm/protocol/contracts/miso_record_shop/listing"; +import * as listing from "../../src/contracts/record_shop/listing.ts"; import { MalformedRecordSoldEventError, findRecordSale, diff --git a/packages/platform/tests/read/release-kind.test.ts b/packages/platform/tests/read/release-kind.test.ts index 2397de4..d4b679b 100644 --- a/packages/platform/tests/read/release-kind.test.ts +++ b/packages/platform/tests/read/release-kind.test.ts @@ -4,7 +4,7 @@ import { expect, test } from "bun:test"; import { bcs } from "@mysten/sui/bcs"; import type { ClientWithCoreApi } from "@mysten/sui/client"; -import * as releaseKind from "@misofm/protocol/contracts/release_kind/release_kind"; +import * as releaseKind from "../../src/contracts/release_kind/release_kind.ts"; import { getReleaseKind, releaseKindFieldId, diff --git a/packages/platform/tests/read/vaulted-wallet.test.ts b/packages/platform/tests/read/vaulted-wallet.test.ts index 99d4684..9f20e36 100644 --- a/packages/platform/tests/read/vaulted-wallet.test.ts +++ b/packages/platform/tests/read/vaulted-wallet.test.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, test } from "bun:test"; -import { contracts } from "@misofm/protocol"; -import * as vaultContract from "@misofm/protocol/contracts/vault/vault"; +import { contracts } from "@misofm/musicos"; +import * as vaultContract from "../../src/contracts/vault/vault.ts"; import type { MisoClient } from "../../src/read/client.ts"; import { classifyVaultedWorkAdminCapType, @@ -93,7 +93,7 @@ function fakeClient(): MisoClient { }; return { config: { - deployment: { miso: MISO }, + deployment: { musicos: MISO }, protocol: { vault: VAULT_PACKAGE }, }, protocol: { core }, diff --git a/packages/platform/tests/read/wallet.test.ts b/packages/platform/tests/read/wallet.test.ts index 1740f05..b07dc48 100644 --- a/packages/platform/tests/read/wallet.test.ts +++ b/packages/platform/tests/read/wallet.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, test } from "bun:test"; -import * as recordContract from "@misofm/protocol/contracts/miso_record/record"; +import * as recordContract from "../../src/contracts/record/record.ts"; import { deriveRecordId } from "../../src/pressing.ts"; import type { MisoClient } from "../../src/read/client.ts"; import { getBalance, getOwnedRecords } from "../../src/read/wallet.ts"; diff --git a/packages/platform/tests/read/works.test.ts b/packages/platform/tests/read/works.test.ts index b1df967..dc119f7 100644 --- a/packages/platform/tests/read/works.test.ts +++ b/packages/platform/tests/read/works.test.ts @@ -4,7 +4,7 @@ import { expect, test } from "bun:test"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import type { SuiGraphQLClient } from "@mysten/sui/graphql"; -import { contracts } from "@misofm/protocol"; +import { contracts } from "@misofm/musicos"; import { getRecordingTitles, getWorksByIds, parseReleaseObject } from "../../src/read/works.ts"; const RELEASE = "0x" + "11".repeat(32); diff --git a/packages/platform/tests/recording-extensions.test.ts b/packages/platform/tests/recording-extensions.test.ts index 69afde7..86911b9 100644 --- a/packages/platform/tests/recording-extensions.test.ts +++ b/packages/platform/tests/recording-extensions.test.ts @@ -5,9 +5,9 @@ import { expect, test } from "bun:test"; import { bcs } from "@mysten/sui/bcs"; import type { ClientWithCoreApi } from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; -import * as walrusData from "@misofm/protocol/contracts/recording_master_reference/deps/ori/data"; -import * as masterReference from "@misofm/protocol/contracts/recording_master_reference/recording_master_reference"; -import * as engineSessionContract from "@misofm/protocol/contracts/recording_engine_session/recording_engine_session"; +import * as walrusData from "../src/contracts/recording_master_reference/deps/ori/data.ts"; +import * as masterReference from "../src/contracts/recording_master_reference/recording_master_reference.ts"; +import * as engineSessionContract from "../src/contracts/recording_engine_session/recording_engine_session.ts"; import { getRecordingEngineSession, getRecordingMasterReference, @@ -287,7 +287,7 @@ test("batches unique master-reference fields and omits absent recordings", async // ── Streaming transcode and batched engine-session reads ──────────────────── -import * as streamingTranscodeContract from "@misofm/protocol/contracts/recording_streaming_transcode/recording_streaming_transcode"; +import * as streamingTranscodeContract from "../src/contracts/recording_streaming_transcode/recording_streaming_transcode.ts"; import { getRecordingEngineSessionsByIds, getRecordingStreamingTranscode, diff --git a/packages/platform/tests/royalty.test.ts b/packages/platform/tests/royalty.test.ts new file mode 100644 index 0000000..13207ee --- /dev/null +++ b/packages/platform/tests/royalty.test.ts @@ -0,0 +1,120 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Covers `src/royalty.ts` — the generic royalty-pool / stake / routed-stake +// derive helpers and PTB builders that used to live on the pre-split protocol package +// before the musicos/platform split (issue misofm/sdks#26). Vault-gated crank +// operations (composition/recording royalty pool plugins, routed-stake +// Actions) are covered by `vault.test.ts`. + +import { expect, test } from "bun:test"; +import { Transaction } from "@mysten/sui/transactions"; +import { + claimRoyaltyRewards, + createRoyaltyStake, + destroyRoyaltyStake, + deriveRoutedStakeId, + deriveRoyaltyPoolId, + registerRoyaltyStake, +} from "../src/royalty.ts"; + +const PKG = "0x" + "cd".repeat(32); +const A = "0x" + "ab".repeat(32); +const CS = `${PKG}::cs::CS`; +const RS = `${PKG}::rs::RS`; + +interface MoveCallInfo { + module: string; + function: string; + typeArguments: string[]; + argCount: number; +} + +function moveCalls(tx: Transaction): MoveCallInfo[] { + const data = tx.getData() as { + commands: { $kind: string; MoveCall?: { module: string; function: string; typeArguments: string[]; arguments: unknown[] } }[]; + }; + return data.commands + .filter((c) => c.$kind === "MoveCall" && c.MoveCall) + .map((c) => ({ + module: c.MoveCall!.module, + function: c.MoveCall!.function, + typeArguments: c.MoveCall!.typeArguments, + argCount: c.MoveCall!.arguments.length, + })); +} + +test("derived royalty-pool and routed-stake ids are deterministic", () => { + const firstPool = deriveRoyaltyPoolId(A, CS, RS, PKG); + const secondPool = deriveRoyaltyPoolId(A, CS, RS, PKG); + const differentCurrency = deriveRoyaltyPoolId(A, CS, `${PKG}::rs::Other`, PKG); + expect(firstPool).toBe(secondPool); + expect(firstPool).not.toBe(differentCurrency); + + const firstStake = deriveRoutedStakeId(PKG, CS, `${PKG}::routed_stake`); + const secondStake = deriveRoutedStakeId(PKG, CS, `${PKG}::routed_stake`); + const differentShare = deriveRoutedStakeId(PKG, RS, `${PKG}::routed_stake`); + expect(firstStake).toBe(secondStake); + expect(firstStake).not.toBe(differentShare); +}); + +test("royalty stake and claim helpers compose in one PTB, preserving by-value results", () => { + const tx = new Transaction(); + const stake = createRoyaltyStake(tx, { + balance: tx.object(A), + shareType: CS, + royaltyPoolPackageId: PKG, + }); + registerRoyaltyStake(tx, { + poolId: A, + stake, + shareType: CS, + currencyType: RS, + royaltyPoolPackageId: PKG, + }); + const reward = claimRoyaltyRewards(tx, { + poolId: A, + stakeId: A, + shareType: CS, + currencyType: RS, + royaltyPoolPackageId: PKG, + }); + + expect(moveCalls(tx).map((call) => `${call.module}::${call.function}`)).toEqual([ + "stake::new", + "pool::register_stake", + "pool::claim_rewards", + ]); + expect(reward).toEqual({ $kind: "Result", Result: 2 }); + // `stake::new` returns exactly one `Stake`; its Result is borrowed by the + // registration call in the same PTB, leaving no address round-trip. + const registerCall = moveCalls(tx)[1]!; + expect(registerCall.argCount).toBe(2); +}); + +test("registerRoyaltyStake requires a stake or a stakeId", () => { + const tx = new Transaction(); + expect(() => + registerRoyaltyStake(tx, { + poolId: A, + shareType: CS, + currencyType: RS, + royaltyPoolPackageId: PKG, + }), + ).toThrow(/stakeId or stake required/); +}); + +test("destroyRoyaltyStake returns the principal balance", () => { + const tx = new Transaction(); + const stake = createRoyaltyStake(tx, { + balance: tx.object(A), + shareType: CS, + royaltyPoolPackageId: PKG, + }); + destroyRoyaltyStake(tx, { stake, shareType: CS, royaltyPoolPackageId: PKG }); + + expect(moveCalls(tx).map((call) => `${call.module}::${call.function}`)).toEqual([ + "stake::new", + "stake::destroy", + ]); +}); diff --git a/packages/platform/tests/transactions.test.ts b/packages/platform/tests/transactions.test.ts index 0b4cf09..d9a2ada 100644 --- a/packages/platform/tests/transactions.test.ts +++ b/packages/platform/tests/transactions.test.ts @@ -6,7 +6,7 @@ // module/function, type arguments, and argument counts, and that // borrow-before-share ordering (composition::new → recording::new → publish → // publish) is preserved now that createComposition/createRecording are -// imported from @misofm/protocol across the package boundary. +// imported from @misofm/musicos across the package boundary. import { test, expect } from "bun:test"; import { Transaction } from "@mysten/sui/transactions"; diff --git a/packages/platform/tests/vault.test.ts b/packages/platform/tests/vault.test.ts index baa68a2..35278a8 100644 --- a/packages/platform/tests/vault.test.ts +++ b/packages/platform/tests/vault.test.ts @@ -4,7 +4,7 @@ import { expect, test } from "bun:test"; import { Transaction } from "@mysten/sui/transactions"; import * as vaultApi from "../src/vault.ts"; -import * as releasePluginContract from "@misofm/protocol/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin"; +import * as releasePluginContract from "../src/contracts/release_revenue_distributor_plugin/release_revenue_distributor_plugin.ts"; import { createCompositionRoutedStake, directAdminCap, custodyNewAdminCap, deriveVaultAdminCapId, deriveVaultId, diff --git a/packages/protocol/README.md b/packages/protocol/README.md deleted file mode 100644 index a9a798b..0000000 --- a/packages/protocol/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# @misofm/protocol - -Typed bindings, queries, event decoders, and PTB builders for the Miso -protocol and Party package set on Sui. - -## Exports - -| Subpath | Purpose | -| ------------------------ | ----------------------------------------------------- | -| `@misofm/protocol` | Root entrypoint | -| `@misofm/protocol/client` | Client construction | -| `@misofm/protocol/deployments` | Deployed package/object IDs by network | -| `@misofm/protocol/queries` | Read queries over protocol and Party objects | -| `@misofm/protocol/transactions`| PTB command builders | -| `@misofm/protocol/execute` | Transaction execution helpers | -| `@misofm/protocol/view` | `devInspect`-backed view helpers | -| `@misofm/protocol/types` | Shared TypeScript types | -| `@misofm/protocol/parsers` | Event parsers | -| `@misofm/protocol/events` | Event decoders | -| `@misofm/protocol/packages` | Module→package bindings | -| `@misofm/protocol/party` | Party identity bindings | -| `@misofm/protocol/contracts` | Generated ABI-bound bindings (BCS structs + Move calls) | - -## Install - -```sh -bun add @misofm/protocol -``` - -Peer dependency: `@mysten/sui@2.29.0` (exact). - -## License - -Apache-2.0 diff --git a/packages/protocol/src/contracts.ts b/packages/protocol/src/contracts.ts deleted file mode 100644 index eb5bbb0..0000000 --- a/packages/protocol/src/contracts.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// Barrel for the codegen-generated, ABI-bound bindings (BCS structs + type-safe -// Move calls). Re-exported from the package root as the `contracts` namespace. - -// Every package here is generated from the current first-party Move source. The -// generated default `@local-pkg/*` identities are source labels only: callers -// must inject exact published addresses through `MisoDeployment` before building -// a transaction. Do not use a historic package ID as a substitute for a fresh -// deployment. - -type PublicModule = Omit; -function withoutUnsafeCalls( - module: M, - keys: K, -): PublicModule { - return Object.fromEntries(Object.entries(module).filter(([key]) => !keys.includes(key as keyof M))) as PublicModule; -} - -import * as rawComposition from "./contracts/miso/composition.ts"; -import * as rawRecording from "./contracts/miso/recording.ts"; -import * as rawRelease from "./contracts/miso/release.ts"; -import * as rawCompositionCredits from "./contracts/composition_credits/composition_credits.ts"; -import * as rawRecordingCredits from "./contracts/recording_credits/recording_credits.ts"; -import * as rawRecordingMasterReference from "./contracts/recording_master_reference/recording_master_reference.ts"; -import * as rawReleaseCoverArt from "./contracts/release_cover_art/release_cover_art.ts"; -import * as rawReleaseCredits from "./contracts/release_credits/release_credits.ts"; -import * as rawReleaseDescription from "./contracts/release_description/release_description.ts"; -import * as rawRoyaltyPool from "./contracts/royalty_pool/pool.ts"; -import * as rawRoyaltyStake from "./contracts/royalty_pool/stake.ts"; -import * as rawRoutedStake from "./contracts/routed_stake/routed_stake.ts"; -import * as rawParty from "./contracts/miso_party/party.ts"; -import * as rawPartyProfile from "./contracts/party_profile/party_profile.ts"; - -// Protocol core (BCS codecs remain available; PTB-inaccessible references do not). -export const composition = withoutUnsafeCalls(rawComposition, ["title", "uid", "uidMut"] as const); -export const recording = withoutUnsafeCalls(rawRecording, ["uid", "uidMut"] as const); -export const release = withoutUnsafeCalls(rawRelease, ["title", "tracks", "uid", "uidMut"] as const); -export * as track from "./contracts/miso/track.ts"; - -// Protocol extensions. -export const compositionCredits = withoutUnsafeCalls(rawCompositionCredits, ["credits"] as const); -export * as compositionPartyRole from "./contracts/composition_credits/composition_party_role.ts"; -export * as recordingAdvisory from "./contracts/recording_advisory/recording_advisory.ts"; -export const recordingCredits = withoutUnsafeCalls(rawRecordingCredits, ["credits", "primaryArtistIds", "featuredArtistIds"] as const); -export * as recordingPartyRole from "./contracts/recording_credits/recording_party_role.ts"; -export * as recordingGenre from "./contracts/recording_genre/recording_genre.ts"; -export * as recordingLanguage from "./contracts/recording_language/recording_language.ts"; -export const recordingMasterReference = withoutUnsafeCalls(rawRecordingMasterReference, ["masterReference"] as const); -export const releaseCoverArt = withoutUnsafeCalls(rawReleaseCoverArt, ["cover"] as const); -export const releaseCredits = withoutUnsafeCalls(rawReleaseCredits, ["credits"] as const); -export * as releasePartyRole from "./contracts/release_credits/release_party_role.ts"; -export const releaseDescription = withoutUnsafeCalls(rawReleaseDescription, ["description"] as const); -export * as releaseDspLink from "./contracts/release_dsp_link/release_dsp_link.ts"; -export * as releaseGenre from "./contracts/release_genre/release_genre.ts"; -export * as releaseKind from "./contracts/release_kind/release_kind.ts"; - -// First-party generic primitives. -export const royaltyPool = withoutUnsafeCalls(rawRoyaltyPool, ["balance", "_new"] as const); -export const royaltyStake = withoutUnsafeCalls(rawRoyaltyStake, ["balance", "getRegistration"] as const); -export const routedStake = withoutUnsafeCalls( - rawRoutedStake, - ["stake", "_new", "register", "unregister", "unstake", "restake"] as const, -); - -// Party core and extensions. -export const party = withoutUnsafeCalls(rawParty, ["groupMembers", "uid", "uidMut"] as const); -export * as partyCta from "./contracts/party_cta/party_cta.ts"; -export * as partyGenre from "./contracts/party_genre/party_genre.ts"; -export * as partyMedia from "./contracts/party_media/party_media.ts"; -export * as partyMusic from "./contracts/party_music/party_music.ts"; -export * as partyPlatformLink from "./contracts/party_platform_link/party_platform_link.ts"; -export * as partyProLink from "./contracts/party_pro_link/party_pro_link.ts"; -export const partyProfile = withoutUnsafeCalls(rawPartyProfile, ["profile"] as const); -export * as partyRoles from "./contracts/party_roles/party_roles.ts"; -export * as partySocial from "./contracts/party_social/party_social.ts"; -export * as partyTags from "./contracts/party_tags/party_tags.ts"; diff --git a/packages/protocol/src/deployments.ts b/packages/protocol/src/deployments.ts deleted file mode 100644 index 127414f..0000000 --- a/packages/protocol/src/deployments.ts +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { normalizeSuiAddress } from "@mysten/sui/utils"; - -/** Sui networks for which this SDK may bundle a verified deployment. */ -export type MisoNetwork = "mainnet" | "testnet"; - -/** - * Every independently published package required by this SDK surface. - * - * These names are stable deployment-manifest keys, not Move module names. Their - * values are never inferred from source labels or a previous publish. - */ -const CANONICAL_MISO_PACKAGE_NAMES = [ - "miso", - "compositionCredits", - "recordingAdvisory", - "recordingCredits", - "recordingLanguage", - "recordingMasterReference", - "releaseCoverArt", - "releaseCredits", - "releaseDescription", - "releaseDspLink", - "releaseGenre", - "releaseKind", - "royaltyPool", - "routedStake", - "misoParty", - "partyCta", - "partyGenre", - "partyMedia", - "partyMusic", - "partyPlatformLink", - "partyProLink", - "partyProfile", - "partyRoles", - "partySocial", - "partyTags", - "countryCode", - "languageCode", - "genre", -] as const; - -/** Public immutable copy; validation always uses the private canonical tuple. */ -export const MISO_PACKAGE_NAMES = Object.freeze([ - ...CANONICAL_MISO_PACKAGE_NAMES, -]) as readonly [...typeof CANONICAL_MISO_PACKAGE_NAMES]; - -export type MisoPackageName = (typeof CANONICAL_MISO_PACKAGE_NAMES)[number]; - -/** - * Exact package addresses from one compatible publish set. - * - * A complete manifest is intentional: a caller cannot accidentally combine - * generated bindings for a fresh ABI with stale package IDs. Fill this only from - * the verified immutable `admin-cli` publish record after the dependency-ordered - * releases are complete. - */ -export type MisoDeployment = Readonly>; - -/** A minimal core-only deployment, retained for callers that use only `miso()`. */ -export interface MisoProtocolDeployment { - /** The published `miso` package used for calls, types, and derived IDs. */ - readonly packageId: string; -} - -/** Validate and freeze a core-only package binding at a client boundary. */ -export function normalizeMisoProtocolDeployment( - deployment: unknown, -): MisoProtocolDeployment { - if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { - throw new Error("@misonetwork/sdk: core deployment must be an object with a normalized Miso package ID."); - } - const packageId = (deployment as { packageId?: unknown }).packageId; - if (typeof packageId !== "string") { - throw new Error("@misonetwork/sdk: core deployment is missing a Miso package ID."); - } - let normalized: string; - try { - normalized = normalizeSuiAddress(packageId); - } catch { - throw new Error("@misonetwork/sdk: core deployment package ID is not a valid Sui address."); - } - if (packageId !== normalized) { - throw new Error( - `@misonetwork/sdk: core deployment package ID must be normalized (${normalized}).`, - ); - } - return Object.freeze({ packageId }); -} - -/** Verified immutable deployments bundled with this SDK release. */ -export const MISO_DEPLOYMENTS = Object.freeze({ - testnet: Object.freeze({ - miso: "0x95bb43d650fe582caba27d4ead2c3f9939c38125943f8716c5baf5af528bc671", - compositionCredits: "0x54cac9dde365a08eff983af16d74d798e6c0a14264bf6cc8a24790708498e37b", - recordingAdvisory: "0x48a16738e6af6548e50d8f76dce9b9de247f365ef2a07c171f732bdc9c88b235", - recordingCredits: "0x0ca2c727c3eb8d3f889d9b29ce1118bc60518c6990e26e9ab278a7ffe635bf1e", - recordingLanguage: "0x677cef9c766b17f87fdf9d6623f1adca0d2e244ee228c86d82c23599ad9a828a", - recordingMasterReference: "0x2b1ca393e8d41eba5b0d3cabf6df1e207a349bba0486ca9ea3a31734d3445971", - releaseCoverArt: "0xb289dfb58ccf2af3722f7bc93b072426a791d05a401115f2901d130f12d8e4b2", - releaseCredits: "0x3a05d0c863ca0b5210f90f69cf87da59791b127c9eaaa10633961aee229a3ed5", - releaseDescription: "0x60a8bc11b7d41d594a2c54c8dee4534dcf94d3be20054100c7774026a543ec6f", - releaseDspLink: "0xbeffd79f656ce89d3595c9fb36dac42a169b72a5e7090504c48ba6c2425961a6", - // Predates the ordered-list (`vector`, add/set-primary/remove) redesign — this id has no `add_genre` and must be replaced by the republished package before use. - releaseGenre: "0x111dd8bff35a1779067d7c75f8a514691342f91f67d2fcdae392768ba6db26f2", - releaseKind: "0x90bf2633b45699d424da616869ecb3d824a88d78d8f8d2ccf5b711b18a734bf1", - royaltyPool: "0xa49e297e4ed8c29ea9bd5941b3ac7d41327f97c85fc35e11bda466f09fce1943", - routedStake: "0xc920af18421cd11c315fa8d0cdd56056854cf453f1ced1fa1b32d555bca2955a", - misoParty: "0xc9fc5d918da992b7c6499880fc509635def384d8529948b28f58494daedf8ec8", - partyCta: "0x25bbdb3dc3fa9482be90294a2b3e328ab4fc2658f1258f1cdf4fe73b13a8d7c5", - partyGenre: "0xa02cffae5be2820e8cda8ecd4f10ae4149a40800aee1a78c74736a409aa515b9", - partyMedia: "0xcafdbe4be3dd6bfade79e546f7faf08972ed864fdb1d49157ad420d71f927783", - partyMusic: "0x76405c486eccde051a621d91a9fd05a911fdc88a3282ef58a3c58eeaad200008", - partyPlatformLink: "0x1d555a8ed265f4a76119c5c01be3646807b91b2cec588e33db016671bdcc259c", - partyProLink: "0xbe6f66e75c6f41afcbf88b166e11cf0a849337379e2f12673598ff976c9f327b", - partyProfile: "0xa5b333defcf07ad3c59feac4b831f4d6775776b1734c9fb1145f590c3028e789", - partyRoles: "0x7a3ab86bfe19b3d291a18d7c0582a1248cf9d74f981f901e9dc75dce0dc3ced9", - partySocial: "0xaaa1af3282370b3f1cd376b2e6f1e0c3b457498eae8ba6150b86e7fe56285392", - partyTags: "0x68a90de97c82f90f22eaa8f2e2b2bfcc3bcfef4847d62e8540474a494f1198d9", - countryCode: "0x69fb214a74d5253971a45b2d07f83f13ae96992dd38198d7bacb21e1f5fb5f81", - languageCode: "0xac318126565a2fab608984a091b3582ba9cda6c32232f567eef50277c5042c36", - genre: "0x6ae4aefdd9d147db6f04a14e4f6149944c485fc7d87d8201dcdb0dee15f0f296", - } as const), -} as const) satisfies Partial< - Record ->; - -/** - * @deprecated Use `MISO_DEPLOYMENTS`. This alias preserves source compatibility - * for callers that still use the former protocol-only constant name. - */ -export const MISO_PROTOCOL_DEPLOYMENTS = MISO_DEPLOYMENTS; - -/** Resolve a verified full manifest, failing closed for unbundled networks. */ -export function getMisoDeployment(network: string): MisoDeployment { - const deployment = ( - MISO_DEPLOYMENTS as Partial> - )[network]; - if (!deployment) { - throw new Error( - `@misonetwork/sdk: no verified Miso deployment is bundled for network "${network}". ` + - "Inject the exact post-publish manifest or pass an explicit deployment; historic package IDs are rejected.", - ); - } - return normalizeMisoDeployment(deployment); -} - -/** Extract the core package identity from a complete deployment manifest. */ -export function protocolDeployment( - deployment: MisoDeployment, -): MisoProtocolDeployment { - return normalizeMisoProtocolDeployment({ packageId: normalizeMisoDeployment(deployment).miso }); -} - -/** Resolve the core package only, preserving the established `miso()` default API. */ -export function getMisoProtocolDeployment( - network: string, -): MisoProtocolDeployment { - return protocolDeployment(getMisoDeployment(network)); -} - -/** - * Validates an explicit manifest before any Move target is constructed. This is - * useful at configuration boundaries such as environment-file loading. - */ -export function assertMisoDeployment( - deployment: unknown, -): asserts deployment is MisoDeployment { - if (!deployment || typeof deployment !== "object" || Array.isArray(deployment)) { - throw new Error( - `@misonetwork/sdk: deployment must be an object with exactly ${CANONICAL_MISO_PACKAGE_NAMES.length} Miso package IDs.`, - ); - } - const entries = deployment as Record; - const keys = Object.keys(entries); - const unexpected = keys.filter((key) => !CANONICAL_MISO_PACKAGE_NAMES.includes(key as MisoPackageName)); - if (unexpected.length > 0 || keys.length !== CANONICAL_MISO_PACKAGE_NAMES.length) { - throw new Error( - `@misonetwork/sdk: deployment must contain exactly these package IDs: ${CANONICAL_MISO_PACKAGE_NAMES.join(", ")}.`, - ); - } - const seenPackageIds = new Set(); - for (const name of CANONICAL_MISO_PACKAGE_NAMES) { - const packageId = entries[name]; - if (typeof packageId !== "string") { - throw new Error( - `@misonetwork/sdk: deployment is missing package ID for "${name}". ` + - "Use a complete manifest from the fresh verified immutable admin-cli publish record.", - ); - } - let normalized: string; - try { - normalized = normalizeSuiAddress(packageId); - } catch { - throw new Error(`@misonetwork/sdk: deployment package ID for "${name}" is not a valid Sui address.`); - } - if (packageId !== normalized) { - throw new Error( - `@misonetwork/sdk: deployment package ID for "${name}" must be normalized (${normalized}).`, - ); - } - if (seenPackageIds.has(packageId)) { - throw new Error( - `@misonetwork/sdk: deployment package ID for "${name}" duplicates another package. ` + - "A publish manifest must bind every package to its own address.", - ); - } - seenPackageIds.add(packageId); - } -} - -/** Validate and snapshot an untrusted manifest so later caller mutation is inert. */ -export function normalizeMisoDeployment(deployment: unknown): MisoDeployment { - assertMisoDeployment(deployment); - return Object.freeze( - Object.fromEntries(CANONICAL_MISO_PACKAGE_NAMES.map((name) => [name, deployment[name]])), - ) as MisoDeployment; -} diff --git a/packages/protocol/src/party/index.ts b/packages/protocol/src/party/index.ts deleted file mode 100644 index 0026b89..0000000 --- a/packages/protocol/src/party/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -export * from "./types.ts"; -export * from "./transactions.ts"; -export * from "./queries.ts"; -export * from "./client.ts"; -export * from "./extensions/profile.ts"; -export * from "./extensions/media.ts"; -export * from "./extensions/roles.ts"; -export * from "./extensions/tags.ts"; -export * from "./extensions/genres.ts"; -export * from "./extensions/ctas.ts"; -export * from "./extensions/links.ts"; diff --git a/packages/protocol/src/party/queries.ts b/packages/protocol/src/party/queries.ts deleted file mode 100644 index 8a3f029..0000000 --- a/packages/protocol/src/party/queries.ts +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// Typed reads: fetch an on-chain object (or dynamic field) via the Core API, BCS-parse -// it through the generated struct, and map to the public camelCase types. - -import type { ClientWithCoreApi } from "@mysten/sui/client"; -import { deriveDynamicFieldID, deriveObjectID, normalizeStructTag } from "@mysten/sui/utils"; -import { bcs } from "@mysten/sui/bcs"; -import { fromBase64 } from "@mysten/sui/utils"; -import { - MembershipKey as MembershipKeyBcs, - Party as PartyBcs, - PendingInviteKey as PendingInviteKeyBcs, - PendingMembershipKey as PendingMembershipKeyBcs, -} from "../contracts/miso_party/party.ts"; -import { Profile as ProfileBcs, ProfileKey as ProfileKeyBcs } from "../contracts/party_profile/party_profile.ts"; -import { Media as MediaBcs, MediaKey as MediaKeyBcs } from "../contracts/party_media/party_media.ts"; -import { ArtistRole as ArtistRoleBcs, RolesKey as RolesKeyBcs } from "../contracts/party_roles/party_roles.ts"; -import { TagsKey as TagsKeyBcs } from "../contracts/party_tags/party_tags.ts"; -import { GenresKey as GenresKeyBcs } from "../contracts/party_genre/party_genre.ts"; -import { Cta as CtaBcs, CtasKey as CtasKeyBcs } from "../contracts/party_cta/party_cta.ts"; -import { mapCtas, mapGenres, mapMedia, mapParty, mapProfile, mapRoles, mapTags } from "./internal.ts"; -import { buildLink, platformForDataType } from "./extensions/links.ts"; -import type { Cta, Media, Party, PlatformLink, Profile } from "./types.ts"; - -/** True for the Core API's "object does not exist" error (a missing dynamic field). */ -function isNotFound(e: unknown): boolean { - const msg = e instanceof Error ? e.message : String(e); - return /not\s*found|does not exist|no object/i.test(msg); -} - -// A missing object (e.g. an unset profile/media dynamic field) surfaces as a -// thrown "not found" from the Core API, not an empty result — treat it as null -// so optional extensions read cleanly. -async function getContent(client: ClientWithCoreApi, objectId: string): Promise { - try { - const { object } = await client.core.getObject({ objectId, include: { content: true } }); - return object?.content ?? null; - } catch (e) { - if (isNotFound(e)) return null; - throw e; - } -} - -/** The `Party` struct tag of one miso_party deployment. */ -export function partyType(partyPackageId: string): string { - return normalizeStructTag(`${partyPackageId}::party::Party`); -} - -// A Party from another miso_party package (a previous protocol generation) -// parses identically but no deployed package accepts it, so the object's type -// is checked against the deployment before its content is trusted. -function assertPartyType(objectId: string, type: string | undefined, partyPackageId: string): void { - const expected = partyType(partyPackageId); - if (type === undefined || normalizeStructTag(type) !== expected) { - throw new Error( - `Object ${objectId} is ${type ?? "of unknown type"}, not a ${expected}` + - (type?.endsWith("::party::Party") ? " (a Party from a different miso_party deployment)" : ""), - ); - } -} - -/** Fetches and parses a shared `Party` object of this deployment's miso_party package. */ -export async function getPartyById( - client: ClientWithCoreApi, - partyId: string, - partyPackageId: string, -): Promise { - let object; - try { - ({ object } = await client.core.getObject({ objectId: partyId, include: { content: true } })); - } catch (e) { - if (isNotFound(e)) throw new Error(`Party not found: ${partyId}`); - throw e; - } - if (!object?.content) throw new Error(`Party not found: ${partyId}`); - assertPartyType(partyId, object.type, partyPackageId); - return mapParty(partyId, PartyBcs.parse(object.content)); -} - -/** - * Fetches and parses multiple shared `Party` objects in one Core request. - * Missing objects are skipped; an object of another type is an error. - */ -export async function getPartiesByIds( - client: ClientWithCoreApi, - partyIds: readonly string[], - partyPackageId: string, -): Promise>> { - if (partyIds.length === 0) return {}; - const { objects } = await client.core.getObjects({ - objectIds: [...new Set(partyIds)], - include: { content: true }, - }); - const parties: Partial> = {}; - for (const obj of objects) { - if (obj instanceof Error) continue; - assertPartyType(obj.objectId, obj.type, partyPackageId); - parties[obj.objectId] = mapParty(obj.objectId, PartyBcs.parse(obj.content)); - } - return parties; -} - -/** Derives a party's `PartyAdminCap` id (it is a `derived_object` off the party UID). */ -export function derivePartyAdminCapId(partyId: string, partyPackageId: string): string { - return deriveObjectID( - partyId, - `${partyPackageId}::party::PartyAdminCapKey`, - bcs.Address.serialize(partyId).toBytes(), - ); -} - -// The profile is a dynamic field on the party UID under an empty `ProfileKey()` -// (Move's implicit `dummy_field: bool` → one zero byte). -const ProfileField = bcs.struct("Field", { id: bcs.Address, name: ProfileKeyBcs, value: ProfileBcs }); -const PROFILE_KEY_BYTES = ProfileKeyBcs.serialize([false]).toBytes(); - -/** Fetches a party's profile, or null if none is set. */ -export async function getProfile( - client: ClientWithCoreApi, - partyId: string, - partyProfilePackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID( - partyId, - `${partyProfilePackageId}::party_profile::ProfileKey`, - PROFILE_KEY_BYTES, - ); - const content = await getContent(client, fieldId); - if (!content) return null; - return mapProfile(partyId, ProfileField.parse(content).value); -} - -// Media is a dynamic field on the party UID under an empty `MediaKey()` (same -// implicit `dummy_field: bool` → one zero byte as the profile key). -const MediaField = bcs.struct("Field", { id: bcs.Address, name: MediaKeyBcs, value: MediaBcs }); -const MEDIA_KEY_BYTES = MediaKeyBcs.serialize([false]).toBytes(); - -/** Fetches a party's media (avatar + header), or null if none is set. */ -export async function getMedia( - client: ClientWithCoreApi, - partyId: string, - partyMediaPackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID( - partyId, - `${partyMediaPackageId}::party_media::MediaKey`, - MEDIA_KEY_BYTES, - ); - const content = await getContent(client, fieldId); - if (!content) return null; - return mapMedia(partyId, MediaField.parse(content).value); -} - -// === Collection extension reads (roles / tags / genres / ctas) === -// -// Each is a single dynamic field on the party UID under an empty positional key -// (Move's implicit `dummy_field: bool` → one zero byte, same as the profile key). - -const ROLES_KEY_BYTES = RolesKeyBcs.serialize([false]).toBytes(); -const RolesField = bcs.struct("Field", { id: bcs.Address, name: RolesKeyBcs, value: bcs.vector(ArtistRoleBcs) }); - -/** The party's artist-type roles (display names), or `[]` if none are set. */ -export async function getRoles( - client: ClientWithCoreApi, - partyId: string, - partyRolesPackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID(partyId, `${partyRolesPackageId}::party_roles::RolesKey`, ROLES_KEY_BYTES); - const content = await getContent(client, fieldId); - if (!content) return []; - return mapRoles(RolesField.parse(content).value); -} - -const TAGS_KEY_BYTES = TagsKeyBcs.serialize([false]).toBytes(); -const TagsField = bcs.struct("Field", { id: bcs.Address, name: TagsKeyBcs, value: bcs.vector(bcs.string()) }); - -/** The party's free-form tags, or `[]` if none are set. */ -export async function getTags( - client: ClientWithCoreApi, - partyId: string, - partyTagsPackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID(partyId, `${partyTagsPackageId}::party_tags::TagsKey`, TAGS_KEY_BYTES); - const content = await getContent(client, fieldId); - if (!content) return []; - return mapTags(TagsField.parse(content).value); -} - -const GENRES_KEY_BYTES = GenresKeyBcs.serialize([false]).toBytes(); -const GenresField = bcs.struct("Field", { id: bcs.Address, name: GenresKeyBcs, value: bcs.vector(bcs.Address) }); - -/** The party's genre object ids, or `[]` if none are set. */ -export async function getGenres( - client: ClientWithCoreApi, - partyId: string, - partyGenrePackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID(partyId, `${partyGenrePackageId}::party_genre::GenresKey`, GENRES_KEY_BYTES); - const content = await getContent(client, fieldId); - if (!content) return []; - return mapGenres(GenresField.parse(content).value); -} - -const CTAS_KEY_BYTES = CtasKeyBcs.serialize([false]).toBytes(); -// The CTA field's value is a bare `vector` (not a wrapper struct). -const CtasField = bcs.struct("Field", { id: bcs.Address, name: CtasKeyBcs, value: bcs.vector(CtaBcs) }); - -/** The party's ordered CTA list (position is priority), or `[]` if none is set. */ -export async function getCtas( - client: ClientWithCoreApi, - partyId: string, - partyCtaPackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID(partyId, `${partyCtaPackageId}::party_cta::CtasKey`, CTAS_KEY_BYTES); - const content = await getContent(client, fieldId); - if (!content) return []; - return mapCtas(CtasField.parse(content).value); -} - -// === Platform-link reads === -// -// Each platform is an independent `PlatformLink` dynamic field keyed by -// `platform_link::PlatformLinkKey`. There is no single key to derive, so -// enumerate the party's dynamic fields and pick out the platform-link ones, -// identifying the platform from the `Data` type argument. Every payload `Data` -// type is a single-`String` newtype, so `PlatformLink` serializes exactly -// as one BCS string — parse the field value as such. -const PlatformLinkField = bcs.struct("Field", { id: bcs.Address, name: bcs.bool(), value: bcs.string() }); -const PLATFORM_LINK_KEY_RE = /::platform_link::PlatformLinkKey<(.+)>$/; - -/** All external-platform links attached to a party (social, music, professional). */ -export async function getLinks(client: ClientWithCoreApi, partyId: string): Promise { - const links: PlatformLink[] = []; - let cursor: string | null | undefined; - do { - const page = await client.core.listDynamicFields({ parentId: partyId, cursor: cursor ?? undefined }); - for (const f of page.dynamicFields) { - if (typeof f.name?.type !== "string") continue; - const match = f.name.type.match(PLATFORM_LINK_KEY_RE); - const dataType = match?.[1]; - if (!dataType) continue; - const platform = platformForDataType(dataType); - if (!platform) continue; - const content = await getContent(client, f.fieldId); - if (!content) continue; - links.push(buildLink(platform, PlatformLinkField.parse(content).value)); - } - cursor = page.hasNextPage ? page.cursor : null; - } while (cursor); - return links; -} - -// === Group membership reads === - -/** Normalizes a dynamic-field key's BCS bytes (Uint8Array or base64 string). */ -function keyBytes(bcs: unknown): Uint8Array { - return typeof bcs === "string" ? fromBase64(bcs) : (bcs as Uint8Array); -} - -/** - * The group ids a party currently belongs to, read from its `MembershipKey` - * dynamic fields (the member-side record). No indexer required. - */ -export async function getMemberships(client: ClientWithCoreApi, partyId: string): Promise { - const groups: string[] = []; - let cursor: string | null | undefined; - do { - const page = await client.core.listDynamicFields({ parentId: partyId, cursor: cursor ?? undefined }); - for (const f of page.dynamicFields) { - if (typeof f.name?.type === "string" && f.name.type.endsWith("::party::MembershipKey") && f.name.bcs != null) { - const [groupId] = MembershipKeyBcs.parse(keyBytes(f.name.bcs)); - groups.push(String(groupId)); - } - } - cursor = page.hasNextPage ? page.cursor : null; - } while (cursor); - return groups; -} - -/** The member ids invited to a group but not yet accepted (its `PendingInviteKey` fields). */ -export async function getPendingInvites(client: ClientWithCoreApi, groupId: string): Promise { - const members: string[] = []; - let cursor: string | null | undefined; - do { - const page = await client.core.listDynamicFields({ parentId: groupId, cursor: cursor ?? undefined }); - for (const f of page.dynamicFields) { - if (typeof f.name?.type === "string" && f.name.type.endsWith("::party::PendingInviteKey") && f.name.bcs != null) { - const [memberId] = PendingInviteKeyBcs.parse(keyBytes(f.name.bcs)); - members.push(String(memberId)); - } - } - cursor = page.hasNextPage ? page.cursor : null; - } while (cursor); - return members; -} - -/** - * The group ids that have invited a party but are still awaiting its response. - * This reads the member-side `PendingMembershipKey` inbox index, so it only - * enumerates the target party's dynamic fields. - */ -export async function getPendingMemberships(client: ClientWithCoreApi, partyId: string): Promise { - const groups: string[] = []; - let cursor: string | null | undefined; - do { - const page = await client.core.listDynamicFields({ parentId: partyId, cursor: cursor ?? undefined }); - for (const f of page.dynamicFields) { - if ( - typeof f.name?.type === "string" && - f.name.type.endsWith("::party::PendingMembershipKey") && - f.name.bcs != null - ) { - const [groupId] = PendingMembershipKeyBcs.parse(keyBytes(f.name.bcs)); - groups.push(String(groupId)); - } - } - cursor = page.hasNextPage ? page.cursor : null; - } while (cursor); - return groups; -} - -/** Whether `memberId` currently holds a membership record for `groupId`. */ -export async function isMember( - client: ClientWithCoreApi, - memberId: string, - groupId: string, - partyPackageId: string, -): Promise { - const fieldId = deriveDynamicFieldID( - memberId, - `${partyPackageId}::party::MembershipKey`, - MembershipKeyBcs.serialize([groupId]).toBytes(), - ); - return (await getContent(client, fieldId)) !== null; -} diff --git a/packages/protocol/tests/codegen-output.test.ts b/packages/protocol/tests/codegen-output.test.ts deleted file mode 100644 index cc2389f..0000000 --- a/packages/protocol/tests/codegen-output.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { expect, test } from "bun:test"; -import { existsSync, mkdtempSync, mkdirSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { pruneRemovedPackageDirectories } from "../scripts/codegen-output.ts"; - -test("codegen output is closed-world and prunes a removed package fixture", () => { - const fixture = mkdtempSync(join(tmpdir(), "miso-sdk-codegen-output-")); - try { - mkdirSync(join(fixture, "miso")); - mkdirSync(join(fixture, "release_registry")); - mkdirSync(join(fixture, "unrelated-directory")); - - pruneRemovedPackageDirectories(fixture); - - expect(existsSync(join(fixture, "miso"))).toBeTrue(); - expect(existsSync(join(fixture, "release_registry"))).toBeFalse(); - expect(existsSync(join(fixture, "unrelated-directory"))).toBeFalse(); - } finally { - rmSync(fixture, { recursive: true, force: true }); - } -}); diff --git a/packages/protocol/tests/events.test.ts b/packages/protocol/tests/events.test.ts deleted file mode 100644 index 44a6d70..0000000 --- a/packages/protocol/tests/events.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Miso Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { expect, test } from "bun:test"; -import { eventParsers } from "../src/events.ts"; -import { ReleaseRegistryCreatedEvent } from "../src/contracts/miso/release.ts"; -import { DescriptionSetEvent } from "../src/contracts/release_description/release_description.ts"; -import { RoyaltyClaimedEvent } from "../src/contracts/royalty_pool/pool.ts"; -import { RoutedStakeSweptEvent } from "../src/contracts/routed_stake/routed_stake.ts"; -import { - GenreAddedEvent as ReleaseGenreAddedEvent, - GenreRemovedEvent as ReleaseGenreRemovedEvent, - GenresClearedEvent as ReleaseGenresClearedEvent, -} from "../src/contracts/release_genre/release_genre.ts"; -import { - GenreAddedEvent as RecordingGenreAddedEvent, - GenreRemovedEvent as RecordingGenreRemovedEvent, - GenresClearedEvent as RecordingGenresClearedEvent, -} from "../src/contracts/recording_genre/recording_genre.ts"; - -const A = "0x" + "12".repeat(32); -const B = "0x" + "34".repeat(32); - -test("extension event decoders preserve current generated BCS fields", () => { - const bytes = DescriptionSetEvent.serialize({ - release_id: A, - description: "Liner notes", - }).toBytes(); - - expect(eventParsers.extensions.releaseDescription.descriptionSet(bytes)).toEqual({ - release_id: A, - description: "Liner notes", - }); -}); - -test("release_genre and recording_genre event decoders round-trip the current ABI", () => { - const releaseAdded = ReleaseGenreAddedEvent.serialize({ - release_id: A, - genre_id: B, - }).toBytes(); - const releaseRemoved = ReleaseGenreRemovedEvent.serialize({ - release_id: A, - genre_id: B, - }).toBytes(); - const releaseCleared = ReleaseGenresClearedEvent.serialize({ - release_id: A, - }).toBytes(); - const recordingAdded = RecordingGenreAddedEvent.serialize({ - recording_id: A, - genre_id: B, - }).toBytes(); - const recordingRemoved = RecordingGenreRemovedEvent.serialize({ - recording_id: A, - genre_id: B, - }).toBytes(); - const recordingCleared = RecordingGenresClearedEvent.serialize({ - recording_id: A, - }).toBytes(); - - expect(eventParsers.extensions.releaseGenre.genreAdded(releaseAdded)).toEqual({ - release_id: A, - genre_id: B, - }); - expect(eventParsers.extensions.releaseGenre.genreRemoved(releaseRemoved)).toEqual({ - release_id: A, - genre_id: B, - }); - expect(eventParsers.extensions.releaseGenre.genresCleared(releaseCleared)).toEqual({ - release_id: A, - }); - expect(eventParsers.extensions.recordingGenre.genreAdded(recordingAdded)).toEqual({ - recording_id: A, - genre_id: B, - }); - expect(eventParsers.extensions.recordingGenre.genreRemoved(recordingRemoved)).toEqual({ - recording_id: A, - genre_id: B, - }); - expect(eventParsers.extensions.recordingGenre.genresCleared(recordingCleared)).toEqual({ - recording_id: A, - }); -}); - -test("core-registry and primitive event decoders round-trip the current ABI", () => { - const registry = ReleaseRegistryCreatedEvent.serialize({ - registry_id: A, - created_by: B, - }).toBytes(); - const claimed = RoyaltyClaimedEvent.serialize({ - pool_id: A, - stake_id: B, - reward_amount: "42", - }).toBytes(); - const swept = RoutedStakeSweptEvent.serialize({ - routed_stake_id: A, - parent_id: B, - value: "99", - }).toBytes(); - - expect(eventParsers.core.releaseRegistryCreated(registry)).toEqual({ - registry_id: A, - created_by: B, - }); - expect(eventParsers.primitives.royaltyPool.royaltyClaimed(claimed)).toEqual({ - pool_id: A, - stake_id: B, - reward_amount: "42", - }); - expect(eventParsers.primitives.routedStake.swept(swept)).toEqual({ - routed_stake_id: A, - parent_id: B, - value: "99", - }); -}); diff --git a/packages/transcoding/package.json b/packages/transcoding/package.json index 60f5b64..a42377e 100644 --- a/packages/transcoding/package.json +++ b/packages/transcoding/package.json @@ -1,6 +1,6 @@ { "name": "@misofm/transcoding", - "version": "0.3.0", + "version": "0.3.1", "description": "Effect-based deterministic plaintext AAC fMP4 HLS transcoder.", "repository": { "type": "git", @@ -52,7 +52,7 @@ "test:ffmpeg": "bun test test/ffmpeg-integration.test.ts" }, "dependencies": { - "@misofm/streaming": "0.1.0", + "@misofm/streaming": "0.2.1", "m3u8-parser": "7.2.0" }, "peerDependencies": { diff --git a/packages/transcoding/test/dependency-contract.test.ts b/packages/transcoding/test/dependency-contract.test.ts index 7617b93..923319b 100644 --- a/packages/transcoding/test/dependency-contract.test.ts +++ b/packages/transcoding/test/dependency-contract.test.ts @@ -12,7 +12,7 @@ test("Effect peers are exact and the runtime dependency is storage-neutral", asy effect: "4.0.0-rc.112", }); expect(manifest.dependencies).toEqual({ - "@misofm/streaming": "0.1.0", + "@misofm/streaming": "0.2.1", "m3u8-parser": "7.2.0", }); }); diff --git a/packages/transcoding/tsconfig.build.json b/packages/transcoding/tsconfig.build.json index 4ad6721..de92650 100644 --- a/packages/transcoding/tsconfig.build.json +++ b/packages/transcoding/tsconfig.build.json @@ -5,8 +5,7 @@ "outDir": "dist", "declaration": true, "declarationMap": false, - "sourceMap": false, - "stripInternal": true + "sourceMap": false }, "include": ["src/**/*.ts"], "exclude": ["test/**/*.ts"] diff --git a/scripts/check-consumer.ts b/scripts/check-consumer.ts new file mode 100644 index 0000000..f83fbc6 --- /dev/null +++ b/scripts/check-consumer.ts @@ -0,0 +1,71 @@ +// Copyright (c) Miso Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Inside this workspace, every `@misofm/*` import resolves locally regardless +// of a package's `exports` map — a broken or missing published entry point, +// a stale subpath, or two packages nesting incompatible versions of a shared +// dependency all typecheck and run fine here. Only installing real tarballs +// into a consumer outside the workspace fails the way a downstream consumer +// would. This script packs every workspace package, installs the tarballs +// into `packages/platform/tests/fixtures/isolated-consumer`, and runs its +// `check` script (tsc + a runtime module-load/dependency-identity probe). + +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs"; +import { join } from "node:path"; + +const root = join(import.meta.dir, ".."); +const packagesDir = join(root, "packages"); +const fixtureParent = join(packagesDir, "platform", "tests", "fixtures"); +const fixtureDir = join(fixtureParent, "isolated-consumer"); + +function unscopedName(pkgName: string): string { + return pkgName.includes("/") ? pkgName.split("/")[1]! : pkgName; +} + +const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort(); + +console.log(`test:consumer: packing ${packageDirs.length} package(s): ${packageDirs.join(", ")}`); + +for (const dir of packageDirs) { + const pkgDir = join(packagesDir, dir); + const distEntry = join(pkgDir, "dist", "index.js"); + if (!existsSync(distEntry)) { + console.error( + `test:consumer: ${dir}/dist/index.js is missing. Run \`bun run build\` before ` + + `\`bun run test:consumer\` — this check packs built output, not source.`, + ); + process.exit(1); + } + + const pkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf8")) as { name: string }; + const packJson = execFileSync( + "npm", + ["pack", "--ignore-scripts", "--json", "--pack-destination", fixtureParent], + { cwd: pkgDir }, + ).toString(); + const [packed] = JSON.parse(packJson) as { filename: string }[]; + if (!packed) throw new Error(`test:consumer: npm pack produced no output for ${dir}`); + const { filename } = packed; + + const stableName = `misofm-${unscopedName(pkg.name)}.tgz`; + renameSync(join(fixtureParent, filename), join(fixtureParent, stableName)); + console.log(`test:consumer: packed ${dir} -> ${stableName}`); +} + +rmSync(join(fixtureDir, "node_modules"), { recursive: true, force: true }); +rmSync(join(fixtureDir, "package-lock.json"), { force: true }); + +console.log("test:consumer: installing isolated-consumer fixture"); +execFileSync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], { + cwd: fixtureDir, + stdio: "inherit", +}); + +console.log("test:consumer: running isolated-consumer check (tsc + verify.mjs)"); +execFileSync("npm", ["run", "check"], { cwd: fixtureDir, stdio: "inherit" }); + +console.log(`test:consumer: OK — ${packageDirs.length} package(s) packed, installed, and verified in isolation`); diff --git a/scripts/codegen-output.ts b/scripts/codegen-output.ts index e157d13..88bcf0d 100644 --- a/scripts/codegen-output.ts +++ b/scripts/codegen-output.ts @@ -3,68 +3,83 @@ import { readdirSync, rmSync } from "node:fs"; import { join } from "node:path"; +import { + generatedDirectoryName, + type GeneratedTree, + type MisoPackageConfig, +} from "../sui-codegen.config.ts"; /** - * The complete, closed-world directory set emitted by the current codegen config — - * one entry per `packages[].package` output plus `utils`, which `@mysten/codegen` - * writes on every run without a corresponding config entry, and the `frozen` - * package entries (see `sui-codegen.config.ts`): retained here so each survives - * pruning even though the runner never regenerates them. + * `@mysten/codegen` writes this directory on every run, in every output tree, + * without a corresponding config entry. */ -export const GENERATED_CONTRACT_DIRECTORIES = new Set([ - "miso", - "miso_party", - "party_cta", - "party_genre", - "party_media", - "party_music", - "party_platform_link", - "party_pro_link", - "party_profile", - "party_roles", - "party_social", - "party_tags", - "party_wallet", - "composition_credits", - "recording_advisory", - "recording_credits", - "recording_language", - "recording_streaming_transcode", - "release_cover_art", - "release_credits", - "release_description", - "release_dsp_link", - "release_genre", - "recording_genre", - "release_kind", - "royalty_pool", - "routed_stake", - "vault", - "genre", - "cover_art", - "composition_royalty_pool", - "recording_royalty_pool", - "release_revenue_distributor", - "miso_record", - "utils", - // Frozen — see the corresponding `frozen` package entry in - // sui-codegen.config.ts for why each is un-generatable upstream. - "recording_master_reference", - "composition_routed_stake", - "composition_royalty_pool_plugin", - "recording_royalty_pool_plugin", - "release_revenue_distributor_plugin", - "miso_record_shop", -]); +const CODEGEN_UTILS_DIRECTORY = "utils"; + +// A `.d.ts` referencing `@mysten/bcs` types only through inferred (unwritten) +// signatures still needs a direct declaration reference to that package, or a +// consumer building with `skipLibCheck: false` fails to resolve it. Every +// generated file that imports from `@mysten/sui/bcs` gets exactly one such +// anchor line directly beneath that import. +const directBcsAnchor = 'import type {} from "@mysten/bcs";'; +const suiBcsImportLine = /^(import .* from '@mysten\/sui\/bcs';)$/m; + +/** Idempotent: strips any existing anchor line(s) before reinserting exactly one. */ +export function normalizeBcsAnchor(source: string): string { + const withoutAnchor = source + .split("\n") + .filter((line) => line !== directBcsAnchor) + .join("\n"); + return suiBcsImportLine.test(withoutAnchor) + ? withoutAnchor.replace(suiBcsImportLine, `$1\n${directBcsAnchor}`) + : withoutAnchor; +} /** - * Enforce codegen as a closed-world operation. A removed Move package must not - * leave a stale generated namespace that can be accidentally imported. + * The complete, closed-world directory set one generated tree may contain — + * derived from the codegen config rather than hand-maintained, so adding or + * renaming a Move package in `sui-codegen.config.ts` is the only edit needed. */ -export function pruneRemovedPackageDirectories(directory: string): void { +export function generatedContractDirectories( + packages: readonly MisoPackageConfig[], + tree: GeneratedTree, +): ReadonlySet { + return new Set([ + ...packages.filter((entry) => entry.tree === tree).map(generatedDirectoryName), + CODEGEN_UTILS_DIRECTORY, + ]); +} + +/** + * Enforce codegen as a closed-world operation. A removed or renamed Move package + * must not leave a stale generated namespace that can be accidentally imported. + * Returns the directories it removed. + */ +export function pruneRemovedPackageDirectories( + directory: string, + expected: ReadonlySet, +): string[] { + const removed: string[] = []; for (const entry of readdirSync(directory, { withFileTypes: true })) { - if (entry.isDirectory() && !GENERATED_CONTRACT_DIRECTORIES.has(entry.name)) { + if (entry.isDirectory() && !expected.has(entry.name)) { rmSync(join(directory, entry.name), { recursive: true, force: true }); + removed.push(entry.name); } } + return removed; +} + +/** + * Directories present in a generated tree that the config does not account + * for. After a run this can only mean a `@local-pkg/` label disagrees + * with the Move package's real name: codegen emitted a directory for the real + * name while the keep-set expected the label. + */ +export function unexpectedPackageDirectories( + directory: string, + expected: ReadonlySet, +): string[] { + return readdirSync(directory, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && !expected.has(entry.name)) + .map((entry) => entry.name) + .sort(); } diff --git a/scripts/codegen.ts b/scripts/codegen.ts index ae1315f..f7396c2 100644 --- a/scripts/codegen.ts +++ b/scripts/codegen.ts @@ -5,12 +5,13 @@ * Generate bindings without writing `package_summaries/` into sibling Move * source worktrees. `sui move summary` receives each source path but writes its * result into a unique temporary directory; @mysten/codegen then reads only - * that copied summary and writes the tracked bindings under `src/contracts`. + * that copied summary and writes the tracked bindings under each tree's + * `src/contracts` (see `outputs` in `sui-codegen.config.ts`). * - * A `frozen` package entry (see `sui-codegen.config.ts`) has no Move source to - * generate from. Its existing generated directory is left exactly as committed - * — `codegen-output.ts` keeps it out of the pruned set, and this runner never - * calls `sui move summary` or `generateFromPackageSummary` for it. + * Every tree is a closed world: before generating, directories the config no + * longer names are pruned; after generating, any directory the config did not + * predict fails the run, because it means a `@local-pkg/` label drifted + * from the Move package's real name. */ import { @@ -26,38 +27,17 @@ import { tmpdir } from "node:os"; import { basename, join, resolve } from "node:path"; import { execFileSync } from "node:child_process"; import { generateFromPackageSummary } from "@mysten/codegen"; -import config, { isFrozenPackageConfig, type MisoCodegenConfig } from "../sui-codegen.config.ts"; -import { pruneRemovedPackageDirectories } from "./codegen-output.ts"; +import config, { type GeneratedTree } from "../sui-codegen.config.ts"; +import { + generatedContractDirectories, + normalizeBcsAnchor, + pruneRemovedPackageDirectories, + unexpectedPackageDirectories, +} from "./codegen-output.ts"; -const codegenConfig = config as MisoCodegenConfig; const temporaryRoot = mkdtempSync(join(tmpdir(), "miso-sdk-codegen-")); -const outputDir = resolve(process.cwd(), codegenConfig.output); -// A full SDK copy can live outside the usual sibling-checkout layout. Point this -// at the SDK checkout whose `../misofm*` siblings should supply Move source. It -// defaults to this SDK checkout, which is the normal development layout. -const sourceConfigRoot = resolve( - process.env.MISO_SDK_CODEGEN_SOURCE_ROOT ?? join(import.meta.dir, ".."), -); -// A `.d.ts` referencing `@mysten/bcs` types only through inferred (unwritten) -// signatures still needs a direct declaration reference to that package, or a -// consumer building with `skipLibCheck: false` fails to resolve it. Every -// generated file that imports from `@mysten/sui/bcs` gets exactly one such -// anchor line directly beneath that import. Ported from the platform -// package's old standalone `scripts/format-codegen.ts` post-processing pass, -// now folded into the one unified codegen pipeline. -const directBcsAnchor = 'import type {} from "@mysten/bcs";'; -const suiBcsImportLine = /^(import .* from '@mysten\/sui\/bcs';)$/m; - -/** Idempotent: strips any existing anchor line(s) before reinserting exactly one. */ -function normalizeBcsAnchor(source: string): string { - const withoutAnchor = source - .split("\n") - .filter((line) => line !== directBcsAnchor) - .join("\n"); - return suiBcsImportLine.test(withoutAnchor) - ? withoutAnchor.replace(suiBcsImportLine, `$1\n${directBcsAnchor}`) - : withoutAnchor; -} +const trees = Object.keys(config.outputs) as GeneratedTree[]; +const outputDir = (tree: GeneratedTree) => resolve(process.cwd(), config.outputs[tree]); /** Keep generated output clean even when a generator template emits ` * ` lines. */ function normalizeGeneratedFiles(directory: string): void { @@ -74,19 +54,17 @@ function normalizeGeneratedFiles(directory: string): void { } try { - pruneRemovedPackageDirectories(outputDir); - for (const [index, packageConfig] of codegenConfig.packages.entries()) { - if (isFrozenPackageConfig(packageConfig)) { - continue; - } - if (!("path" in packageConfig) || !packageConfig.path) { - throw new Error("Miso SDK codegen supports only source-backed and frozen packages."); - } + for (const tree of trees) { + mkdirSync(outputDir(tree), { recursive: true }); + const removed = pruneRemovedPackageDirectories( + outputDir(tree), + generatedContractDirectories(config.packages, tree), + ); + for (const name of removed) console.log(`pruned stale ${tree} binding directory: ${name}`); + } - const sourcePath = - packageConfig.package === "@local-pkg/miso" && process.env.MISO_SDK_CORE_SOURCE_PATH - ? resolve(process.env.MISO_SDK_CORE_SOURCE_PATH) - : resolve(sourceConfigRoot, packageConfig.path); + for (const [index, packageConfig] of config.packages.entries()) { + const sourcePath = resolve(packageConfig.path); const summaryPath = join( temporaryRoot, `${String(index).padStart(2, "0")}-${basename(sourcePath)}`, @@ -109,15 +87,28 @@ try { ); await generateFromPackageSummary({ - package: { ...packageConfig, path: summaryPath }, - prune: codegenConfig.prune ?? true, - outputDir, - globalGenerate: codegenConfig.generate, - importExtension: codegenConfig.importExtension, - includePhantomTypeParameters: codegenConfig.includePhantomTypeParameters, + package: { package: packageConfig.package, path: summaryPath }, + prune: config.prune ?? true, + outputDir: outputDir(packageConfig.tree), + globalGenerate: config.generate, + importExtension: config.importExtension, + includePhantomTypeParameters: config.includePhantomTypeParameters, }); } - normalizeGeneratedFiles(outputDir); + + for (const tree of trees) { + const unexpected = unexpectedPackageDirectories( + outputDir(tree), + generatedContractDirectories(config.packages, tree), + ); + if (unexpected.length > 0) { + throw new Error( + `codegen emitted ${tree} directories the config does not name: ${unexpected.join(", ")}. ` + + "A `@local-pkg/` label in sui-codegen.config.ts disagrees with that package's Move.toml name.", + ); + } + normalizeGeneratedFiles(outputDir(tree)); + } } finally { rmSync(temporaryRoot, { recursive: true, force: true }); } diff --git a/sui-codegen.config.ts b/sui-codegen.config.ts index 5a51a28..9811099 100644 --- a/sui-codegen.config.ts +++ b/sui-codegen.config.ts @@ -3,120 +3,148 @@ import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; /** - * A package entry with no Move source to generate from. The runner retains its - * existing generated directory (see `scripts/codegen-output.ts`) without ever - * regenerating it — for a deployed, canonical binding whose source has since - * been removed upstream. + * The two generated trees, one per npm package. Which tree a Move package + * lands in is the boundary rule of the SDK split: + * + * `@misofm/musicos` is the object model. Everything Miso offers on top of + * it is platform. + * + * `musicos` (composition, recording, release, track) is the object model of + * works and `partyos` is the object model of parties; each is its own npm + * package. Every extension, Action, primitive, plugin, and Record package is + * something Miso chose to offer over them, so all of those — including the + * `party_*` extensions and `party_wallet` — generate into `@misofm/platform`. */ -export interface FrozenPackageConfig { - readonly package: string; - readonly frozen: true; +export type GeneratedTree = "musicos" | "partyos" | "platform"; + +export interface MisoPackageConfig { + /** + * `@local-pkg/` where `` is the Move package's `name` from its + * `Move.toml`. @mysten/codegen names the output directory from the package + * summary — the real Move name — while the prune keep-set is derived from + * this label, so the two must agree. `bun run codegen` fails when they do + * not, rather than leaving an orphaned directory behind. + */ + readonly package: `@local-pkg/${string}`; + /** Move package source directory (contains `Move.toml`). */ + readonly path: string; + readonly tree: GeneratedTree; } -export type MisoPackageConfig = SuiCodegenConfig["packages"][number] | FrozenPackageConfig; - -/** `SuiCodegenConfig`, widened to allow `frozen` package entries. */ -export interface MisoCodegenConfig extends Omit { - packages: readonly MisoPackageConfig[]; +/** `SuiCodegenConfig`, with one output directory per generated tree. */ +export interface MisoCodegenConfig extends Omit { + readonly outputs: Readonly>; + readonly packages: readonly MisoPackageConfig[]; } -export function isFrozenPackageConfig( - packageConfig: MisoPackageConfig, -): packageConfig is FrozenPackageConfig { - return "frozen" in packageConfig && packageConfig.frozen === true; +/** The generated directory a package config emits: its label without the `@local-pkg/` scope. */ +export function generatedDirectoryName(packageConfig: MisoPackageConfig): string { + return packageConfig.package.slice("@local-pkg/".length); } -// The single, unified codegen config for the SDK monorepo. It generates the -// ABI-bound BCS structs and Move-call builders for every deployed Miso package -// into `packages/protocol/src/contracts` — the one canonical generated tree. -// `packages/platform` still generates its own (Phase 6 rewires it onto this one). -// -// Package IDs are injected at runtime by `deployments.ts`; no source-local -// address or previously published package ID is treated as live. +// Package IDs are injected at runtime by each package's `deployments.ts`; no +// source-local address or previously published package ID is treated as live. // // `bun run codegen` deliberately writes every `sui move summary` output to a // temporary directory before generating (see `scripts/codegen.ts`) rather than // into `package_summaries/` inside these sibling checkouts. // // Expected checkout layout — everything resolves against the directory that -// directly contains `misofm/` (defaults to two levels up from this file, i.e. -// `~/Documents/GitHub`; override with `MISO_SDK_CODEGEN_SOURCE_ROOT` for an -// isolated worktree): -// misofm/{protocol, party, party-extensions, party-actions, -// protocol-extensions, protocol-actions, royalty-pool, routed-stake, -// vault, vault-plugins, genre, cover-art, record, record-shop} +// directly contains `misofm/` (defaults to two levels up from this file; +// override with `MISO_SDK_CODEGEN_SOURCE_ROOT` for an isolated worktree). The +// directory names are the GitHub repository names: +// misofm/{musicos, musicos-extensions, musicos-actions, +// partyos, partyos-extensions, partyos-actions, +// royalty-pool, routed-stake, vault, vault-plugins, genre, cover-art, +// record, record-shop} const sourceRoot = process.env.MISO_SDK_CODEGEN_SOURCE_ROOT ? resolve(process.env.MISO_SDK_CODEGEN_SOURCE_ROOT) : fileURLToPath(new URL("../..", import.meta.url)); const source = (path: string) => resolve(sourceRoot, path); +const musicos = (name: string, path: string): MisoPackageConfig => ({ + package: `@local-pkg/${name}`, + path: source(path), + tree: "musicos", +}); +const partyos = (name: string, path: string): MisoPackageConfig => ({ + package: `@local-pkg/${name}`, + path: source(path), + tree: "partyos", +}); +const platform = (name: string, path: string): MisoPackageConfig => ({ + package: `@local-pkg/${name}`, + path: source(path), + tree: "platform", +}); + const config: MisoCodegenConfig = { - output: "./packages/protocol/src/contracts", + outputs: { + musicos: "./packages/musicos/src/contracts", + partyos: "./packages/partyos/src/contracts", + platform: "./packages/platform/src/contracts", + }, importExtension: ".ts", generateSummaries: false, packages: [ - // Protocol core. - { package: "@local-pkg/miso", path: source("misofm/protocol") }, - - // Party identity + its data extensions. - { package: "@local-pkg/miso_party", path: source("misofm/party") }, - { package: "@local-pkg/party_cta", path: source("misofm/party-extensions/party_cta") }, - { package: "@local-pkg/party_genre", path: source("misofm/party-extensions/party_genre") }, - { package: "@local-pkg/party_media", path: source("misofm/party-extensions/party_media") }, - { package: "@local-pkg/party_music", path: source("misofm/party-extensions/party_music") }, - { package: "@local-pkg/party_platform_link", path: source("misofm/party-extensions/party_platform_link") }, - { package: "@local-pkg/party_pro_link", path: source("misofm/party-extensions/party_pro_link") }, - { package: "@local-pkg/party_profile", path: source("misofm/party-extensions/party_profile") }, - { package: "@local-pkg/party_roles", path: source("misofm/party-extensions/party_roles") }, - { package: "@local-pkg/party_social", path: source("misofm/party-extensions/party_social") }, - { package: "@local-pkg/party_tags", path: source("misofm/party-extensions/party_tags") }, - // Public, custody-agnostic Actions over Party — lives in party-actions, not - // party-extensions (the latter has no Move.toml for party_wallet). - { package: "@local-pkg/party_wallet", path: source("misofm/party-actions/party_wallet") }, - - // Protocol-extension packages (persistent data attached to a protocol work). - { package: "@local-pkg/composition_credits", path: source("misofm/protocol-extensions/composition_credits") }, - { package: "@local-pkg/recording_advisory", path: source("misofm/protocol-extensions/recording_advisory") }, - { package: "@local-pkg/recording_credits", path: source("misofm/protocol-extensions/recording_credits") }, - { package: "@local-pkg/recording_genre", path: source("misofm/protocol-extensions/recording_genre") }, - { package: "@local-pkg/recording_language", path: source("misofm/protocol-extensions/recording_language") }, - - { package: "@local-pkg/recording_master_reference", path: source("misofm/protocol-extensions/recording_master_reference") }, - - { package: "@local-pkg/recording_engine_session", path: source("misofm/protocol-extensions/recording_engine_session") }, - { package: "@local-pkg/recording_streaming_transcode", path: source("misofm/protocol-extensions/recording_streaming_transcode") }, - { package: "@local-pkg/release_cover_art", path: source("misofm/protocol-extensions/release_cover_art") }, - { package: "@local-pkg/release_credits", path: source("misofm/protocol-extensions/release_credits") }, - { package: "@local-pkg/release_description", path: source("misofm/protocol-extensions/release_description") }, - { package: "@local-pkg/release_dsp_link", path: source("misofm/protocol-extensions/release_dsp_link") }, - { package: "@local-pkg/release_genre", path: source("misofm/protocol-extensions/release_genre") }, - { package: "@local-pkg/release_kind", path: source("misofm/protocol-extensions/release_kind") }, - + // The object models: the only package in each of the musicos and partyos trees. + musicos("musicos", "misofm/musicos"), + partyos("partyos", "misofm/partyos"), + + // Party data extensions. + platform("party_cta", "misofm/partyos-extensions/party_cta"), + platform("party_genre", "misofm/partyos-extensions/party_genre"), + platform("party_media", "misofm/partyos-extensions/party_media"), + platform("party_music", "misofm/partyos-extensions/party_music"), + platform("party_platform_link", "misofm/partyos-extensions/party_platform_link"), + platform("party_pro_link", "misofm/partyos-extensions/party_pro_link"), + platform("party_profile", "misofm/partyos-extensions/party_profile"), + platform("party_roles", "misofm/partyos-extensions/party_roles"), + platform("party_social", "misofm/partyos-extensions/party_social"), + platform("party_tags", "misofm/partyos-extensions/party_tags"), + // Public, custody-agnostic Actions over Party — lives in partyos-actions, + // not partyos-extensions. + platform("party_wallet", "misofm/partyos-actions/party_wallet"), + + // Work extensions (persistent data attached to a musicos work). + platform("composition_credits", "misofm/musicos-extensions/composition_credits"), + platform("recording_advisory", "misofm/musicos-extensions/recording_advisory"), + platform("recording_credits", "misofm/musicos-extensions/recording_credits"), + platform("recording_engine_session", "misofm/musicos-extensions/recording_engine_session"), + platform("recording_genre", "misofm/musicos-extensions/recording_genre"), + platform("recording_language", "misofm/musicos-extensions/recording_language"), + platform("recording_master_reference", "misofm/musicos-extensions/recording_master_reference"), + platform("recording_streaming_transcode", "misofm/musicos-extensions/recording_streaming_transcode"), + platform("release_cover_art", "misofm/musicos-extensions/release_cover_art"), + platform("release_credits", "misofm/musicos-extensions/release_credits"), + platform("release_description", "misofm/musicos-extensions/release_description"), + platform("release_dsp_link", "misofm/musicos-extensions/release_dsp_link"), + platform("release_genre", "misofm/musicos-extensions/release_genre"), + platform("release_kind", "misofm/musicos-extensions/release_kind"), // Primitives. - { package: "@local-pkg/royalty_pool", path: source("misofm/royalty-pool") }, - { package: "@local-pkg/routed_stake", path: source("misofm/routed-stake") }, - { package: "@local-pkg/vault", path: source("misofm/vault") }, - { package: "@local-pkg/genre", path: source("misofm/genre") }, - { package: "@local-pkg/cover_art", path: source("misofm/cover-art") }, + platform("royalty_pool", "misofm/royalty-pool"), + platform("routed_stake", "misofm/routed-stake"), + platform("vault", "misofm/vault"), + platform("genre", "misofm/genre"), + platform("cover_art", "misofm/cover-art"), // Actions — public, custody-agnostic business logic over a work. - { package: "@local-pkg/composition_royalty_pool", path: source("misofm/protocol-actions/composition_royalty_pool") }, - { package: "@local-pkg/recording_royalty_pool", path: source("misofm/protocol-actions/recording_royalty_pool") }, - { package: "@local-pkg/composition_routed_stake", path: source("misofm/protocol-actions/composition_routed_stake") }, - { package: "@local-pkg/release_revenue_distributor", path: source("misofm/protocol-actions/release_revenue_distributor") }, + platform("composition_royalty_pool", "misofm/musicos-actions/composition_royalty_pool"), + platform("recording_royalty_pool", "misofm/musicos-actions/recording_royalty_pool"), + platform("composition_routed_stake", "misofm/musicos-actions/composition_routed_stake"), + platform("release_revenue_distributor", "misofm/musicos-actions/release_revenue_distributor"), // Vault plugins — permissionless automation adapters for the subset of // Actions that is safe to crank without an admin. - // - { package: "@local-pkg/composition_royalty_pool_plugin", path: source("misofm/vault-plugins/composition_royalty_pool_plugin") }, - { package: "@local-pkg/recording_royalty_pool_plugin", path: source("misofm/vault-plugins/recording_royalty_pool_plugin") }, - { package: "@local-pkg/release_revenue_distributor_plugin", path: source("misofm/vault-plugins/release_revenue_distributor_plugin") }, + platform("composition_royalty_pool_plugin", "misofm/vault-plugins/composition_royalty_pool_plugin"), + platform("recording_royalty_pool_plugin", "misofm/vault-plugins/recording_royalty_pool_plugin"), + platform("release_revenue_distributor_plugin", "misofm/vault-plugins/release_revenue_distributor_plugin"), // Record identity and edition-scoped Pressings, separate from Record Shop's // primary-sale mechanics. - { package: "@local-pkg/miso_record", path: source("misofm/record") }, - { package: "@local-pkg/miso_record_shop", path: source("misofm/record-shop") }, + platform("record", "misofm/record"), + platform("record_shop", "misofm/record-shop"), ], }; From 01250306c4bdbf4bcc5c8971f49a23a23f8b76bd Mon Sep 17 00:00:00 2001 From: BL Date: Tue, 8 Sep 2026 16:23:42 +0000 Subject: [PATCH 2/2] docs(musicos,partyos): correct the Party ownership note; alias royaltyStake in the platform barrel Review follow-ups for the split: the musicos README named the Party object model as platform, the partyos README example lacked its Transaction import, the platform contracts barrel had no export under the name the retired protocol barrel used for the stake module, and an unused ref-returning entry implied a second curation layer over partyos's curated barrel. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QGmuA3eMr4iaRTdz3J8JZJ --- packages/musicos/README.md | 14 ++++++++------ packages/partyos/README.md | 1 + packages/platform/src/contracts.ts | 2 ++ packages/platform/src/packages.ts | 1 - 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/musicos/README.md b/packages/musicos/README.md index abc5d61..0ab36ae 100644 --- a/packages/musicos/README.md +++ b/packages/musicos/README.md @@ -1,9 +1,10 @@ # @misofm/musicos -`@misofm/musicos` is the object model. Everything Miso offers on top of it — -work extensions, royalty primitives, Party, and every product-specific -workflow — is platform, and lives in -[`@misofm/platform`](../platform/README.md). This package holds ONLY the +`@misofm/musicos` is the object model of works. Everything Miso offers on top +of it — work extensions, royalty primitives, Party extensions, and every +product-specific workflow — is platform, and lives in +[`@misofm/platform`](../platform/README.md); the Party object model itself is +[`@misofm/partyos`](../partyos/README.md). This package holds ONLY the typed bindings, queries, event decoders, and PTB builders for the `musicos` Move package: Composition, Recording, Release, and Track. @@ -56,8 +57,9 @@ freshly-published deployment. Work extensions (composition/recording/release credits, advisory ratings, genres, DSP links, cover art, ...), royalty primitives (royalty pools, -stakes, routed stakes), Party (`partyos` and its extensions), and every -other first-party Move package live in `@misofm/platform`. See +stakes, routed stakes), the Party extensions, and every other first-party +Move package live in `@misofm/platform`; the `partyos` object model has its +own package, `@misofm/partyos`. See [`sui-codegen.config.ts`](../../sui-codegen.config.ts) at the repo root for the authoritative list of Move packages and which generated tree each one lands in. diff --git a/packages/partyos/README.md b/packages/partyos/README.md index c12d832..a613ded 100644 --- a/packages/partyos/README.md +++ b/packages/partyos/README.md @@ -23,6 +23,7 @@ bun add @misofm/partyos @mysten/sui ```ts import { SuiGrpcClient } from "@mysten/sui/grpc"; +import { Transaction } from "@mysten/sui/transactions"; import { partyos } from "@misofm/partyos"; const client = new SuiGrpcClient({ network: "testnet" }).$extend(partyos()); diff --git a/packages/platform/src/contracts.ts b/packages/platform/src/contracts.ts index a1909ac..45b95f5 100644 --- a/packages/platform/src/contracts.ts +++ b/packages/platform/src/contracts.ts @@ -46,6 +46,8 @@ export * as listing from "./contracts/record_shop/listing.ts"; // plugins (BCS codecs remain available; PTB-inaccessible references do not). export const royaltyPool = withoutUnsafeCalls(rawRoyaltyPool, ["balance", "_new"] as const); export const royaltyPoolStake = withoutUnsafeCalls(rawRoyaltyStake, ["balance", "getRegistration"] as const); +/** The name the retired `@misofm/protocol` barrel used for {@link royaltyPoolStake}. */ +export { royaltyPoolStake as royaltyStake }; export * as vault from "./contracts/vault/vault.ts"; export * as compositionRoyaltyPool from "./contracts/composition_royalty_pool/composition_royalty_pool.ts"; export * as recordingRoyaltyPool from "./contracts/recording_royalty_pool/recording_royalty_pool.ts"; diff --git a/packages/platform/src/packages.ts b/packages/platform/src/packages.ts index 81b35b6..f0ad47a 100644 --- a/packages/platform/src/packages.ts +++ b/packages/platform/src/packages.ts @@ -88,7 +88,6 @@ export const REF_RETURNING_CALLS = { // caller-provided PTB input. Only `share`, `sweep`, scalar reads and derives // remain usable at this layer. routedStake: ["stake", "_new", "register", "unregister", "unstake", "restake"], - party: ["groupMembers", "uid", "uidMut"], partyProfile: ["profile"], } as const;