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..0ab36ae --- /dev/null +++ b/packages/musicos/README.md @@ -0,0 +1,77 @@ +# @misofm/musicos + +`@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. + +## 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), 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. + +## 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..a613ded --- /dev/null +++ b/packages/partyos/README.md @@ -0,0 +1,51 @@ +# @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 { Transaction } from "@mysten/sui/transactions"; +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..45b95f5 100644 --- a/packages/platform/src/contracts.ts +++ b/packages/platform/src/contracts.ts @@ -4,52 +4,98 @@ // 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); +/** 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"; +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 61% rename from packages/protocol/src/packages.ts rename to packages/platform/src/packages.ts index 64a5b5e..f0ad47a 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"], @@ -114,105 +88,103 @@ 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; /** - * 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 +192,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 +219,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 +235,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"), ], };