@misofm/platform 0.0.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +686 -3
  3. package/dist/auth.d.ts +73 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +193 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/catalog.d.ts +48 -0
  8. package/dist/catalog.d.ts.map +1 -0
  9. package/dist/catalog.js +92 -0
  10. package/dist/catalog.js.map +1 -0
  11. package/dist/client.d.ts +462 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +788 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/contracts.d.ts +34 -0
  16. package/dist/contracts.d.ts.map +1 -0
  17. package/dist/contracts.js +49 -0
  18. package/dist/contracts.js.map +1 -0
  19. package/dist/cover.d.ts +69 -0
  20. package/dist/cover.d.ts.map +1 -0
  21. package/dist/cover.js +132 -0
  22. package/dist/cover.js.map +1 -0
  23. package/dist/credits.d.ts +200 -0
  24. package/dist/credits.d.ts.map +1 -0
  25. package/dist/credits.js +465 -0
  26. package/dist/credits.js.map +1 -0
  27. package/dist/deployments.d.ts +224 -0
  28. package/dist/deployments.d.ts.map +1 -0
  29. package/dist/deployments.js +177 -0
  30. package/dist/deployments.js.map +1 -0
  31. package/dist/execute.d.ts +30 -0
  32. package/dist/execute.d.ts.map +1 -0
  33. package/dist/execute.js +31 -0
  34. package/dist/execute.js.map +1 -0
  35. package/dist/index.d.ts +21 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +52 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/internal.d.ts +13 -0
  40. package/dist/internal.d.ts.map +1 -0
  41. package/dist/internal.js +41 -0
  42. package/dist/internal.js.map +1 -0
  43. package/dist/mix.d.ts +144 -0
  44. package/dist/mix.d.ts.map +1 -0
  45. package/dist/mix.js +656 -0
  46. package/dist/mix.js.map +1 -0
  47. package/dist/pressing.d.ts +137 -0
  48. package/dist/pressing.d.ts.map +1 -0
  49. package/dist/pressing.js +390 -0
  50. package/dist/pressing.js.map +1 -0
  51. package/dist/publication.d.ts +216 -0
  52. package/dist/publication.d.ts.map +1 -0
  53. package/dist/publication.js +872 -0
  54. package/dist/publication.js.map +1 -0
  55. package/dist/queries.d.ts +2 -0
  56. package/dist/queries.d.ts.map +1 -0
  57. package/dist/queries.js +8 -0
  58. package/dist/queries.js.map +1 -0
  59. package/dist/read/artist.d.ts +18 -0
  60. package/dist/read/artist.d.ts.map +1 -0
  61. package/dist/read/artist.js +86 -0
  62. package/dist/read/artist.js.map +1 -0
  63. package/dist/read/catalog.d.ts +93 -0
  64. package/dist/read/catalog.d.ts.map +1 -0
  65. package/dist/read/catalog.js +441 -0
  66. package/dist/read/catalog.js.map +1 -0
  67. package/dist/read/client.d.ts +37 -0
  68. package/dist/read/client.d.ts.map +1 -0
  69. package/dist/read/client.js +50 -0
  70. package/dist/read/client.js.map +1 -0
  71. package/dist/read/config.d.ts +68 -0
  72. package/dist/read/config.d.ts.map +1 -0
  73. package/dist/read/config.js +63 -0
  74. package/dist/read/config.js.map +1 -0
  75. package/dist/read/genres.d.ts +7 -0
  76. package/dist/read/genres.d.ts.map +1 -0
  77. package/dist/read/genres.js +38 -0
  78. package/dist/read/genres.js.map +1 -0
  79. package/dist/read/index.d.ts +14 -0
  80. package/dist/read/index.d.ts.map +1 -0
  81. package/dist/read/index.js +23 -0
  82. package/dist/read/index.js.map +1 -0
  83. package/dist/read/internal/scalars.d.ts +23 -0
  84. package/dist/read/internal/scalars.d.ts.map +1 -0
  85. package/dist/read/internal/scalars.js +57 -0
  86. package/dist/read/internal/scalars.js.map +1 -0
  87. package/dist/read/internal/walrus.d.ts +6 -0
  88. package/dist/read/internal/walrus.d.ts.map +1 -0
  89. package/dist/read/internal/walrus.js +29 -0
  90. package/dist/read/internal/walrus.js.map +1 -0
  91. package/dist/read/receipts.d.ts +41 -0
  92. package/dist/read/receipts.d.ts.map +1 -0
  93. package/dist/read/receipts.js +398 -0
  94. package/dist/read/receipts.js.map +1 -0
  95. package/dist/read/types.d.ts +328 -0
  96. package/dist/read/types.d.ts.map +1 -0
  97. package/dist/read/types.js +21 -0
  98. package/dist/read/types.js.map +1 -0
  99. package/dist/read/wallet.d.ts +74 -0
  100. package/dist/read/wallet.d.ts.map +1 -0
  101. package/dist/read/wallet.js +578 -0
  102. package/dist/read/wallet.js.map +1 -0
  103. package/dist/read/works.d.ts +29 -0
  104. package/dist/read/works.d.ts.map +1 -0
  105. package/dist/read/works.js +191 -0
  106. package/dist/read/works.js.map +1 -0
  107. package/dist/recording-extensions.d.ts +62 -0
  108. package/dist/recording-extensions.d.ts.map +1 -0
  109. package/dist/recording-extensions.js +159 -0
  110. package/dist/recording-extensions.js.map +1 -0
  111. package/dist/release-extensions.d.ts +93 -0
  112. package/dist/release-extensions.d.ts.map +1 -0
  113. package/dist/release-extensions.js +190 -0
  114. package/dist/release-extensions.js.map +1 -0
  115. package/dist/release-graph.d.ts +65 -0
  116. package/dist/release-graph.d.ts.map +1 -0
  117. package/dist/release-graph.js +136 -0
  118. package/dist/release-graph.js.map +1 -0
  119. package/dist/share-template.d.ts +3 -0
  120. package/dist/share-template.d.ts.map +1 -0
  121. package/dist/share-template.js +46 -0
  122. package/dist/share-template.js.map +1 -0
  123. package/dist/share.d.ts +67 -0
  124. package/dist/share.d.ts.map +1 -0
  125. package/dist/share.js +157 -0
  126. package/dist/share.js.map +1 -0
  127. package/dist/transactions.d.ts +216 -0
  128. package/dist/transactions.d.ts.map +1 -0
  129. package/dist/transactions.js +314 -0
  130. package/dist/transactions.js.map +1 -0
  131. package/dist/vault.d.ts +364 -0
  132. package/dist/vault.d.ts.map +1 -0
  133. package/dist/vault.js +507 -0
  134. package/dist/vault.js.map +1 -0
  135. package/package.json +145 -5
  136. package/src/auth.ts +275 -0
  137. package/src/catalog.ts +189 -0
  138. package/src/client.ts +1320 -0
  139. package/src/contracts.ts +54 -0
  140. package/src/cover.ts +240 -0
  141. package/src/credits.ts +906 -0
  142. package/src/deployments.ts +357 -0
  143. package/src/execute.ts +52 -0
  144. package/src/index.ts +69 -0
  145. package/src/internal.ts +46 -0
  146. package/src/mix.ts +910 -0
  147. package/src/pressing.ts +705 -0
  148. package/src/publication.ts +1214 -0
  149. package/src/queries.ts +9 -0
  150. package/src/read/artist.ts +118 -0
  151. package/src/read/catalog.ts +668 -0
  152. package/src/read/client.ts +87 -0
  153. package/src/read/config.ts +138 -0
  154. package/src/read/genres.ts +37 -0
  155. package/src/read/index.ts +85 -0
  156. package/src/read/internal/scalars.ts +59 -0
  157. package/src/read/internal/walrus.ts +42 -0
  158. package/src/read/receipts.ts +476 -0
  159. package/src/read/types.ts +415 -0
  160. package/src/read/wallet.ts +687 -0
  161. package/src/read/works.ts +297 -0
  162. package/src/recording-extensions.ts +256 -0
  163. package/src/release-extensions.ts +311 -0
  164. package/src/release-graph.ts +219 -0
  165. package/src/share-template.ts +55 -0
  166. package/src/share.ts +261 -0
  167. package/src/transactions.ts +542 -0
  168. package/src/vault.ts +1009 -0
@@ -0,0 +1,1214 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Opinionated, atomic Miso catalog publication.
6
+ *
7
+ * Share packages and their registered Currency objects must already exist. This
8
+ * builder then creates every declared Party, Composition, Recording, Track,
9
+ * Release, extension, Vault plugin, Pressing, and Listing in one PTB. Share
10
+ * allocation is explicit: callers may preserve raw address balances or create
11
+ * holder Stakes, register them before a fresh pool is shared, and transfer the
12
+ * registered objects. Fresh raw protocol capabilities never leave the
13
+ * transaction: data extensions use them directly, Vault-only plugins are
14
+ * configured while each new Vault is still owned, and only the selected direct
15
+ * cap or VaultAdminCap is delivered.
16
+ */
17
+
18
+ import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions";
19
+ import {
20
+ contracts as protocolContracts,
21
+ deriveCompositionAdminCapId,
22
+ deriveRecordingAdminCapId,
23
+ deriveReleaseAdminCapId,
24
+ } from "@misofm/protocol";
25
+ import { derivePartyAdminCapId } from "@misofm/protocol/party";
26
+ import {
27
+ requireOperationsDeployment,
28
+ type AvailableOperationsDeployment,
29
+ type MisoPlatformDeployment,
30
+ } from "./deployments.ts";
31
+ import type { TxThunk } from "./transactions.ts";
32
+ import {
33
+ createShareStakes,
34
+ disperseShares,
35
+ registerShareStake,
36
+ requiredCommandResult,
37
+ shareRoyaltyPool,
38
+ type ShareRecipient,
39
+ } from "./transactions.ts";
40
+ import {
41
+ addRecordingPrimaryArtist,
42
+ addReleaseCredit,
43
+ attachCompositionCredit,
44
+ attachRecordingCredit,
45
+ type CompositionRole,
46
+ type RecordingRole,
47
+ type ReleaseRole,
48
+ } from "./credits.ts";
49
+ import {
50
+ setRecordingAdvisory,
51
+ setRecordingInstrumental,
52
+ setRecordingLanguages,
53
+ setRecordingMasterReference,
54
+ setRecordingPreview,
55
+ setRecordingStreamingTranscode,
56
+ } from "./recording-extensions.ts";
57
+ import {
58
+ setReleaseDescription,
59
+ setReleaseDspLinks,
60
+ setReleaseGenres,
61
+ setReleaseKind,
62
+ type DspLink,
63
+ } from "./release-extensions.ts";
64
+ import { setReleaseCover, setReleaseTrackCover } from "./cover.ts";
65
+ import {
66
+ custodyNewAdminCap,
67
+ createCompositionRoutedStake,
68
+ deriveVaultAdminCapId,
69
+ directAdminCap,
70
+ disposeNewAdminCap,
71
+ installCompositionRoyaltyPoolPlugin,
72
+ installRecordingRoyaltyPoolPlugin,
73
+ installReleaseRevenueDistributorPlugin,
74
+ invokeWithAdminCap,
75
+ newCompositionRoyaltyPool,
76
+ newRecordingRoyaltyPool,
77
+ parseVaultCreatedEvent,
78
+ registerCompositionRoutedStake,
79
+ shareRoutedStake,
80
+ type AdminCapAuthority,
81
+ type AdminCapCustody,
82
+ } from "./vault.ts";
83
+ import {
84
+ derivePressingAdminCapId,
85
+ derivePressingId,
86
+ type ListingPrice,
87
+ } from "./pressing.ts";
88
+ import { requireRecordSalesDeployment } from "./deployments.ts";
89
+ import { allCreatedByType, createdByExactType, type PlatformExecResult } from "./execute.ts";
90
+ import * as pressingContract from "@misofm/protocol/contracts/miso_record/pressing";
91
+ import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing";
92
+
93
+ const { composition, recording, release, track, party } = protocolContracts;
94
+
95
+ export const MAX_ATOMIC_PUBLICATION_COMMANDS = 900;
96
+ export const MAX_ATOMIC_PUBLICATION_INPUTS = 2048;
97
+ /** Fixed protocol work-share supply: 10 million shares at six decimals. */
98
+ const WORK_SHARE_SUPPLY = 10_000_000_000_000n;
99
+
100
+ export interface PublicationCustody {
101
+ readonly kind: "direct" | "vault";
102
+ readonly owner: string;
103
+ }
104
+
105
+ export type PublicationParty =
106
+ | { readonly ref: string; readonly id: string }
107
+ | {
108
+ readonly ref: string;
109
+ readonly create: "individual" | "group";
110
+ readonly name: string;
111
+ readonly custody: PublicationCustody;
112
+ };
113
+
114
+ export interface PublicationCompositionCredit {
115
+ readonly party: string;
116
+ readonly displayName: string;
117
+ readonly roles: CompositionRole[];
118
+ }
119
+
120
+ export interface PublicationRecordingCredit {
121
+ readonly party: string;
122
+ readonly displayName: string;
123
+ readonly roles: RecordingRole[];
124
+ readonly primaryArtist?: boolean;
125
+ }
126
+
127
+ export interface PublicationReleaseCredit {
128
+ readonly party: string;
129
+ readonly displayName: string;
130
+ readonly role: ReleaseRole;
131
+ }
132
+
133
+ export interface PublicationComposition {
134
+ readonly ref: string;
135
+ readonly shareType: string;
136
+ readonly shareCurrencyId: string;
137
+ readonly shareTreasuryCapId: string;
138
+ readonly title: string;
139
+ readonly royaltyRateBps: number;
140
+ readonly shareRecipients: ShareRecipient[];
141
+ /** Defaults to raw address balances for SDK compatibility; the CLI selects `stake`. */
142
+ readonly shareDistribution?: "balance" | "stake";
143
+ readonly custody: PublicationCustody;
144
+ readonly credits?: PublicationCompositionCredit[];
145
+ readonly royaltyPool?: { readonly currencyType: string };
146
+ }
147
+
148
+ type PublicationRecordingParent =
149
+ | { readonly parentCompositionIndex: number; readonly parentCompositionId?: never }
150
+ | { readonly parentCompositionIndex?: never; readonly parentCompositionId: string };
151
+
152
+ export type PublicationRecordingLanguages =
153
+ | { readonly kind: "instrumental" }
154
+ | { readonly kind: "languages"; readonly codes: string[] };
155
+
156
+ export type PublicationRecording = PublicationRecordingParent & {
157
+ readonly ref: string;
158
+ readonly shareType: string;
159
+ readonly shareCurrencyId: string;
160
+ readonly shareTreasuryCapId: string;
161
+ readonly compositionShareType: string;
162
+ readonly shareRecipients: ShareRecipient[];
163
+ /** Defaults to raw address balances for SDK compatibility; the CLI selects `stake`. */
164
+ readonly shareDistribution?: "balance" | "stake";
165
+ readonly custody: PublicationCustody;
166
+ readonly credits?: PublicationRecordingCredit[];
167
+ readonly royaltyPool?: { readonly currencyType: string };
168
+ /** Route the exact parent Composition royalty cut through this Recording's pool. */
169
+ readonly routedStake?: true;
170
+ readonly advisory?: "Explicit" | "NotExplicit" | "Cleaned";
171
+ readonly languages?: PublicationRecordingLanguages;
172
+ readonly masterReferenceBlobId?: bigint | string;
173
+ /** Complete Walrus Quilt ID containing this Recording's streaming transcodes. */
174
+ readonly streamingTranscodeQuiltId?: bigint | string;
175
+ readonly previewBlobId?: bigint | string;
176
+ };
177
+
178
+ export interface PublicationFreshTrack {
179
+ readonly recordingIndex: number;
180
+ readonly splitBps: number;
181
+ }
182
+
183
+ export interface PublicationExistingTrack {
184
+ readonly recordingId: string;
185
+ readonly recordingShareType: string;
186
+ readonly compositionShareType: string;
187
+ readonly splitBps: number;
188
+ readonly authority: AdminCapAuthority;
189
+ }
190
+
191
+ export type PublicationTrack = PublicationFreshTrack | PublicationExistingTrack;
192
+
193
+ export interface PublicationRelease {
194
+ readonly title: string;
195
+ readonly nonce: string;
196
+ readonly tracks: PublicationTrack[];
197
+ readonly custody: PublicationCustody;
198
+ readonly credits?: PublicationReleaseCredit[];
199
+ readonly kind?: string;
200
+ readonly description?: string;
201
+ readonly genres?: {
202
+ readonly primaryGenreId: string;
203
+ readonly secondaryGenreIds?: string[];
204
+ readonly tracks?: { readonly trackIndex: number; readonly genreId: string }[];
205
+ };
206
+ readonly dspLinks?: {
207
+ readonly release?: DspLink[];
208
+ readonly tracks?: { readonly trackIndex: number; readonly link: DspLink }[];
209
+ };
210
+ readonly cover?: {
211
+ readonly stillBlobId?: bigint | string;
212
+ readonly animatedBlobId?: bigint | string;
213
+ readonly tracks?: {
214
+ readonly trackIndex: number;
215
+ readonly stillBlobId: bigint | string;
216
+ readonly animatedBlobId?: bigint | string;
217
+ }[];
218
+ };
219
+ readonly revenueDistribution?: boolean;
220
+ }
221
+
222
+ export interface PublicationPressing {
223
+ /** Positive edition number encoded as Move u16. */
224
+ readonly edition: number;
225
+ /** Immutable positive u32 ceiling, or null/omitted for an uncapped edition. */
226
+ readonly maxSupply?: number | null;
227
+ readonly listings: {
228
+ readonly currencyType: string;
229
+ readonly price: ListingPrice;
230
+ readonly enabled?: boolean;
231
+ }[];
232
+ readonly custody: PublicationCustody;
233
+ }
234
+
235
+ export interface AtomicPublicationParams {
236
+ readonly deployment: MisoPlatformDeployment;
237
+ readonly parties: PublicationParty[];
238
+ readonly compositions: PublicationComposition[];
239
+ readonly recordings: PublicationRecording[];
240
+ readonly release?: PublicationRelease;
241
+ readonly pressing?: PublicationPressing;
242
+ }
243
+
244
+ interface WorkParts {
245
+ work: TransactionObjectArgument;
246
+ adminCap: TransactionObjectArgument;
247
+ balance: TransactionObjectArgument;
248
+ }
249
+
250
+ interface PartyParts {
251
+ party: TransactionObjectArgument;
252
+ adminCap: TransactionObjectArgument;
253
+ }
254
+
255
+ function requiredAt<T>(items: readonly T[], index: number, description: string): T {
256
+ const value = items[index];
257
+ if (value === undefined) throw new Error(`${description} index ${index} is out of range`);
258
+ return value;
259
+ }
260
+
261
+ function partyCapType(p: AtomicPublicationParams): string {
262
+ return `${p.deployment.protocol.misoParty}::party::PartyAdminCap`;
263
+ }
264
+
265
+ function compositionCapType(p: AtomicPublicationParams, shareType: string): string {
266
+ return `${p.deployment.protocol.miso}::composition::CompositionAdminCap<${shareType}>`;
267
+ }
268
+
269
+ function recordingCapType(p: AtomicPublicationParams, shareType: string): string {
270
+ return `${p.deployment.protocol.miso}::recording::RecordingAdminCap<${shareType}>`;
271
+ }
272
+
273
+ function releaseCapType(p: AtomicPublicationParams): string {
274
+ return `${p.deployment.protocol.miso}::release::ReleaseAdminCap`;
275
+ }
276
+
277
+ function pressingCapType(p: AtomicPublicationParams): string {
278
+ return `${requireRecordSalesDeployment(p.deployment.recordSales).recordPackageId}::pressing::PressingAdminCap`;
279
+ }
280
+
281
+ function publicationUsesVault(p: AtomicPublicationParams): boolean {
282
+ return (
283
+ p.parties.some((node) => "create" in node && node.custody.kind === "vault") ||
284
+ p.compositions.some((node) => node.custody.kind === "vault") ||
285
+ p.recordings.some((node) => node.custody.kind === "vault") ||
286
+ p.release?.custody.kind === "vault" ||
287
+ p.release?.tracks.some((node) =>
288
+ "authority" in node && node.authority.kind === "vault"
289
+ ) === true ||
290
+ p.pressing?.custody.kind === "vault"
291
+ );
292
+ }
293
+
294
+ function operations(p: AtomicPublicationParams): AvailableOperationsDeployment {
295
+ return requireOperationsDeployment(p.deployment.operations);
296
+ }
297
+
298
+ function custody(
299
+ p: AtomicPublicationParams,
300
+ selected: PublicationCustody,
301
+ capType: string,
302
+ configure?: Exclude<AdminCapCustody, { kind: "direct" }>["configure"],
303
+ ): AdminCapCustody {
304
+ if (selected.kind === "direct") return { kind: "direct", owner: selected.owner };
305
+ const available = operations(p);
306
+ return {
307
+ kind: "vault",
308
+ owner: selected.owner,
309
+ vaultRegistry: available.vault.registryId,
310
+ capType,
311
+ vaultPackageId: available.vault.packageId,
312
+ configure,
313
+ };
314
+ }
315
+
316
+ function partyByRef(
317
+ parties: Map<string, TransactionObjectArgument>,
318
+ ref: string,
319
+ ): TransactionObjectArgument {
320
+ const value = parties.get(ref);
321
+ if (!value) throw new Error(`Unknown publication party ref ${JSON.stringify(ref)}`);
322
+ return value;
323
+ }
324
+
325
+ function languageVector(tx: Transaction, p: AtomicPublicationParams, codes: string[]) {
326
+ const values = codes.map((code) => tx.moveCall({
327
+ target: `${p.deployment.protocol.languageCode}::language_code::new`,
328
+ arguments: [tx.pure.string(code)],
329
+ }));
330
+ return tx.makeMoveVec({
331
+ type: `${p.deployment.protocol.languageCode}::language_code::LanguageCode`,
332
+ elements: values,
333
+ });
334
+ }
335
+
336
+ function walrusBlob(tx: Transaction, p: AtomicPublicationParams, blobId: bigint | string) {
337
+ return tx.moveCall({
338
+ target: `${p.deployment.packages.ori}::walrus_data::new_blob`,
339
+ arguments: [tx.pure.u256(blobId)],
340
+ });
341
+ }
342
+
343
+ function listingPrice(tx: Transaction, p: AtomicPublicationParams, price: ListingPrice) {
344
+ const pkg = requireRecordSalesDeployment(p.deployment.recordSales).recordShopPackageId;
345
+ const args = { package: pkg, arguments: [BigInt(price.amount)] as [bigint] };
346
+ return tx.add(price.kind === "fixed"
347
+ ? listingContract.fixed(args)
348
+ : listingContract.floor(args));
349
+ }
350
+
351
+ function publicationEdition(value: number): number {
352
+ if (!Number.isInteger(value) || value <= 0 || value > 0xffff) {
353
+ throw new RangeError("pressing.edition must be an integer from 1 to 65535");
354
+ }
355
+ return value;
356
+ }
357
+
358
+ function publicationMaxSupply(value: number | null | undefined): number | null {
359
+ if (value == null) return null;
360
+ if (!Number.isInteger(value) || value <= 0 || value > 0xffff_ffff) {
361
+ throw new RangeError("pressing.maxSupply must be a positive u32 or null");
362
+ }
363
+ return value;
364
+ }
365
+
366
+ function compositionRoyaltyCut(royaltyRateBps: number): bigint {
367
+ // Atomic publication always allocates a non-zero Recording creator remainder.
368
+ // Lower-level routed-stake builders remain available for Move's 100% case.
369
+ if (!Number.isInteger(royaltyRateBps) || royaltyRateBps <= 0 || royaltyRateBps >= 10_000) {
370
+ throw new RangeError("routed stake requires parent royaltyRateBps from 1 to 9999");
371
+ }
372
+ return (WORK_SHARE_SUPPLY * BigInt(royaltyRateBps)) / 10_000n;
373
+ }
374
+
375
+ interface PublicationShareAllocation {
376
+ readonly shareType: string;
377
+ readonly shareRecipients: ShareRecipient[];
378
+ readonly shareDistribution?: "balance" | "stake";
379
+ }
380
+
381
+ function allocatePublicationShares(
382
+ tx: Transaction,
383
+ p: AtomicPublicationParams,
384
+ node: PublicationShareAllocation,
385
+ balance: TransactionObjectArgument,
386
+ ): TransactionObjectArgument[] {
387
+ if (node.shareDistribution !== "stake") {
388
+ disperseShares(tx, p.deployment.packages.minato, node.shareType, balance, node.shareRecipients);
389
+ return [];
390
+ }
391
+ return createShareStakes(tx, {
392
+ balance,
393
+ shareType: node.shareType,
394
+ recipients: node.shareRecipients,
395
+ royaltyPoolPackageId: p.deployment.packages.royaltyPool,
396
+ });
397
+ }
398
+
399
+ function registerPublicationStakes(
400
+ tx: Transaction,
401
+ p: AtomicPublicationParams,
402
+ node: PublicationShareAllocation,
403
+ stakes: readonly TransactionObjectArgument[],
404
+ pool: TransactionObjectArgument,
405
+ currencyType: string,
406
+ ): void {
407
+ for (const stake of stakes) {
408
+ registerShareStake(tx, {
409
+ stake,
410
+ pool,
411
+ shareType: node.shareType,
412
+ currencyType,
413
+ royaltyPoolPackageId: p.deployment.packages.royaltyPool,
414
+ });
415
+ }
416
+ }
417
+
418
+ function transferPublicationStakes(
419
+ tx: Transaction,
420
+ node: PublicationShareAllocation,
421
+ stakes: readonly TransactionObjectArgument[],
422
+ ): void {
423
+ if (node.shareDistribution !== "stake") {
424
+ if (stakes.length !== 0) throw new Error("balance distribution unexpectedly created stakes");
425
+ return;
426
+ }
427
+ if (stakes.length !== node.shareRecipients.length) {
428
+ throw new Error("publication stake count does not match share recipient count");
429
+ }
430
+ stakes.forEach((stake, index) => {
431
+ tx.transferObjects([stake], tx.pure.address(node.shareRecipients[index]!.address));
432
+ });
433
+ }
434
+
435
+ function publishComposition(
436
+ tx: Transaction,
437
+ p: AtomicPublicationParams,
438
+ node: PublicationComposition,
439
+ parts: WorkParts,
440
+ ): void {
441
+ const available = node.custody.kind === "vault" ? operations(p) : undefined;
442
+ const stakes = allocatePublicationShares(tx, p, node, parts.balance);
443
+ const typeArguments: [string] = [node.shareType];
444
+ if (node.custody.kind === "direct") {
445
+ tx.add(composition.publish({
446
+ package: p.deployment.protocol.miso,
447
+ typeArguments,
448
+ arguments: [parts.work, parts.adminCap],
449
+ }));
450
+ disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, compositionCapType(p, node.shareType)));
451
+ transferPublicationStakes(tx, node, stakes);
452
+ return;
453
+ }
454
+ custodyNewAdminCap(tx, {
455
+ adminCap: parts.adminCap,
456
+ vaultRegistry: available!.vault.registryId,
457
+ capType: compositionCapType(p, node.shareType),
458
+ vaultPackageId: available!.vault.packageId,
459
+ owner: node.custody.owner,
460
+ configure: (vault, vaultAdminCap) => {
461
+ if (node.royaltyPool) {
462
+ installCompositionRoyaltyPoolPlugin(tx, {
463
+ vault, vaultAdminCap, compositionShareType: node.shareType,
464
+ pluginPackageId: available!.plugins.compositionRoyaltyPool,
465
+ });
466
+ const authority: AdminCapAuthority = {
467
+ kind: "vault", vault, vaultAdminCap,
468
+ capType: compositionCapType(p, node.shareType),
469
+ vaultPackageId: available!.vault.packageId,
470
+ };
471
+ const poolParams = {
472
+ authority, composition: parts.work,
473
+ compositionShareType: node.shareType,
474
+ currencyType: node.royaltyPool.currencyType,
475
+ actionPackageId: available!.actions.compositionRoyaltyPool,
476
+ };
477
+ const pool = newCompositionRoyaltyPool(tx, poolParams);
478
+ if (stakes.length > 0) {
479
+ registerPublicationStakes(tx, p, node, stakes, pool, node.royaltyPool.currencyType);
480
+ }
481
+ shareRoyaltyPool(tx, {
482
+ pool,
483
+ shareType: node.shareType,
484
+ currencyType: node.royaltyPool.currencyType,
485
+ royaltyPoolPackageId: p.deployment.packages.royaltyPool,
486
+ });
487
+ }
488
+ invokeWithAdminCap(tx, {
489
+ kind: "vault", vault, vaultAdminCap,
490
+ capType: compositionCapType(p, node.shareType),
491
+ vaultPackageId: available!.vault.packageId,
492
+ }, {
493
+ target: `${p.deployment.protocol.miso}::composition::publish`,
494
+ typeArguments,
495
+ arguments: [parts.work, tx.object.clock()],
496
+ adminCapIndex: 1,
497
+ });
498
+ },
499
+ });
500
+ transferPublicationStakes(tx, node, stakes);
501
+ }
502
+
503
+ function publishRecording(
504
+ tx: Transaction,
505
+ p: AtomicPublicationParams,
506
+ node: PublicationRecording,
507
+ parts: WorkParts,
508
+ parentComposition?: { node: PublicationComposition; parts: WorkParts },
509
+ ): void {
510
+ const available = node.custody.kind === "vault" ? operations(p) : undefined;
511
+ const typeArguments: [string, string] = [node.shareType, node.compositionShareType];
512
+ const stakes = allocatePublicationShares(tx, p, node, parts.balance);
513
+ if (node.custody.kind === "direct") {
514
+ tx.add(recording.publish({
515
+ package: p.deployment.protocol.miso,
516
+ typeArguments,
517
+ arguments: [parts.work, parts.adminCap],
518
+ }));
519
+ disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, recordingCapType(p, node.shareType)));
520
+ transferPublicationStakes(tx, node, stakes);
521
+ return;
522
+ }
523
+ custodyNewAdminCap(tx, {
524
+ adminCap: parts.adminCap,
525
+ vaultRegistry: available!.vault.registryId,
526
+ capType: recordingCapType(p, node.shareType),
527
+ vaultPackageId: available!.vault.packageId,
528
+ owner: node.custody.owner,
529
+ configure: (vault, vaultAdminCap) => {
530
+ if (node.royaltyPool) {
531
+ installRecordingRoyaltyPoolPlugin(tx, {
532
+ vault, vaultAdminCap,
533
+ recordingShareType: node.shareType,
534
+ compositionShareType: node.compositionShareType,
535
+ pluginPackageId: available!.plugins.recordingRoyaltyPool,
536
+ });
537
+ const authority: AdminCapAuthority = {
538
+ kind: "vault", vault, vaultAdminCap,
539
+ capType: recordingCapType(p, node.shareType),
540
+ vaultPackageId: available!.vault.packageId,
541
+ };
542
+ const poolParams = {
543
+ authority, recording: parts.work,
544
+ recordingShareType: node.shareType,
545
+ compositionShareType: node.compositionShareType,
546
+ currencyType: node.royaltyPool.currencyType,
547
+ actionPackageId: available!.actions.recordingRoyaltyPool,
548
+ };
549
+ const pool = newRecordingRoyaltyPool(tx, poolParams);
550
+ if (stakes.length > 0) {
551
+ registerPublicationStakes(tx, p, node, stakes, pool, node.royaltyPool.currencyType);
552
+ }
553
+ if (node.routedStake) {
554
+ if (!parentComposition) throw new Error(`${node.ref}: routed stake requires a fresh parent Composition`);
555
+ const authority = directAdminCap(parentComposition.parts.adminCap);
556
+ const routed = createCompositionRoutedStake(tx, {
557
+ authority,
558
+ composition: parentComposition.parts.work,
559
+ recording: parts.work,
560
+ recordingShareType: node.shareType,
561
+ compositionShareType: node.compositionShareType,
562
+ value: compositionRoyaltyCut(parentComposition.node.royaltyRateBps),
563
+ actionPackageId: available!.actions.compositionRoutedStake,
564
+ });
565
+ registerCompositionRoutedStake(tx, {
566
+ authority,
567
+ composition: parentComposition.parts.work,
568
+ recording: parts.work,
569
+ routedStake: routed,
570
+ royaltyPool: pool,
571
+ recordingShareType: node.shareType,
572
+ compositionShareType: node.compositionShareType,
573
+ currencyType: node.royaltyPool.currencyType,
574
+ actionPackageId: available!.actions.compositionRoutedStake,
575
+ });
576
+ shareRoutedStake(tx, {
577
+ routedStake: routed,
578
+ routedStakePackageId: p.deployment.packages.routedStake,
579
+ stakeShareType: node.shareType,
580
+ poolShareType: node.compositionShareType,
581
+ });
582
+ }
583
+ shareRoyaltyPool(tx, {
584
+ pool,
585
+ shareType: node.shareType,
586
+ currencyType: node.royaltyPool.currencyType,
587
+ royaltyPoolPackageId: p.deployment.packages.royaltyPool,
588
+ });
589
+ }
590
+ invokeWithAdminCap(tx, {
591
+ kind: "vault", vault, vaultAdminCap,
592
+ capType: recordingCapType(p, node.shareType),
593
+ vaultPackageId: available!.vault.packageId,
594
+ }, {
595
+ target: `${p.deployment.protocol.miso}::recording::publish`,
596
+ typeArguments,
597
+ arguments: [parts.work, tx.object.clock()],
598
+ adminCapIndex: 1,
599
+ });
600
+ },
601
+ });
602
+ transferPublicationStakes(tx, node, stakes);
603
+ }
604
+
605
+ function publishReleaseObject(
606
+ tx: Transaction,
607
+ p: AtomicPublicationParams,
608
+ node: PublicationRelease,
609
+ releaseObject: TransactionObjectArgument,
610
+ adminCap: TransactionObjectArgument,
611
+ ): void {
612
+ const available = node.custody.kind === "vault" ? operations(p) : undefined;
613
+ if (node.custody.kind === "direct") {
614
+ tx.add(release.publish({
615
+ package: p.deployment.protocol.miso,
616
+ arguments: [releaseObject, adminCap],
617
+ }));
618
+ disposeNewAdminCap(tx, adminCap, custody(p, node.custody, releaseCapType(p)));
619
+ return;
620
+ }
621
+ custodyNewAdminCap(tx, {
622
+ adminCap,
623
+ vaultRegistry: available!.vault.registryId,
624
+ capType: releaseCapType(p),
625
+ vaultPackageId: available!.vault.packageId,
626
+ owner: node.custody.owner,
627
+ configure: (vault, vaultAdminCap) => {
628
+ if (node.revenueDistribution) {
629
+ installReleaseRevenueDistributorPlugin(tx, {
630
+ vault, vaultAdminCap,
631
+ pluginPackageId: available!.plugins.releaseRevenueDistributor,
632
+ });
633
+ }
634
+ invokeWithAdminCap(tx, {
635
+ kind: "vault", vault, vaultAdminCap,
636
+ capType: releaseCapType(p),
637
+ vaultPackageId: available!.vault.packageId,
638
+ }, {
639
+ target: `${p.deployment.protocol.miso}::release::publish`,
640
+ arguments: [releaseObject, tx.object.clock()],
641
+ adminCapIndex: 1,
642
+ });
643
+ },
644
+ });
645
+ }
646
+
647
+ /** Build the complete post-share catalog graph as one atomic PTB. */
648
+ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk {
649
+ if (
650
+ p.recordings.some((node) => node.streamingTranscodeQuiltId !== undefined) &&
651
+ !p.deployment.packages.recordingStreamingTranscode
652
+ ) {
653
+ throw new Error(
654
+ "A recording streaming transcode requires deployment.packages.recordingStreamingTranscode",
655
+ );
656
+ }
657
+ if (publicationUsesVault(p)) {
658
+ requireOperationsDeployment(p.deployment.operations);
659
+ }
660
+ if (p.pressing) {
661
+ requireRecordSalesDeployment(p.deployment.recordSales);
662
+ if (!p.release) throw new Error("A pressing requires a release");
663
+ }
664
+ for (const node of [...p.compositions, ...p.recordings]) {
665
+ if (node.royaltyPool && node.custody.kind !== "vault") {
666
+ throw new Error(`${node.ref}: permissionless royalty cranks require Vault custody`);
667
+ }
668
+ }
669
+ p.recordings.forEach((node) => {
670
+ if (!node.routedStake) return;
671
+ if (node.parentCompositionIndex === undefined) {
672
+ throw new Error(`${node.ref}: routed stake requires a fresh parent Composition`);
673
+ }
674
+ if (!node.royaltyPool) {
675
+ throw new Error(`${node.ref}: routed stake requires a Recording royalty pool`);
676
+ }
677
+ if (node.shareDistribution !== "stake") {
678
+ throw new Error(`${node.ref}: routed stake requires Recording stake distribution`);
679
+ }
680
+ const parent = requiredAt(p.compositions, node.parentCompositionIndex, "parent composition");
681
+ if (!parent.royaltyPool) {
682
+ throw new Error(`${node.ref}: routed stake requires a parent Composition royalty pool`);
683
+ }
684
+ if (parent.shareDistribution !== "stake") {
685
+ throw new Error(`${node.ref}: routed stake requires parent Composition stake distribution`);
686
+ }
687
+ compositionRoyaltyCut(parent.royaltyRateBps);
688
+ if (parent.royaltyPool.currencyType !== node.royaltyPool.currencyType) {
689
+ throw new Error(`${node.ref}: routed stake pools must use the same currency type`);
690
+ }
691
+ if (parent.custody.kind !== "vault") {
692
+ throw new Error(`${node.ref}: routed stake requires parent Composition Vault custody`);
693
+ }
694
+ });
695
+ if (p.release?.revenueDistribution && p.release.custody.kind !== "vault") {
696
+ throw new Error("Release revenue distribution requires Vault custody");
697
+ }
698
+
699
+ return (tx) => {
700
+ const createdParties = new Map<string, PartyParts>();
701
+ const parties = new Map<string, TransactionObjectArgument>();
702
+ for (const node of p.parties) {
703
+ if ("id" in node) {
704
+ parties.set(node.ref, tx.object(node.id));
705
+ continue;
706
+ }
707
+ const kind = tx.add(node.create === "group"
708
+ ? party.newGroupKind({ package: p.deployment.protocol.misoParty })
709
+ : party.newIndividualKind({ package: p.deployment.protocol.misoParty }));
710
+ const created = tx.add(party._new({
711
+ package: p.deployment.protocol.misoParty,
712
+ arguments: [kind, node.name],
713
+ }));
714
+ const parts = {
715
+ party: requiredCommandResult(created, 0, "party::new"),
716
+ adminCap: requiredCommandResult(created, 1, "party::new"),
717
+ };
718
+ createdParties.set(node.ref, parts);
719
+ parties.set(node.ref, parts.party);
720
+ }
721
+
722
+ const compositions: WorkParts[] = p.compositions.map((node) => {
723
+ const created = tx.add(composition._new({
724
+ package: p.deployment.protocol.miso,
725
+ typeArguments: [node.shareType],
726
+ arguments: [node.title, node.royaltyRateBps, node.shareCurrencyId, node.shareTreasuryCapId],
727
+ }));
728
+ return {
729
+ work: requiredCommandResult(created, 0, "composition::new"),
730
+ adminCap: requiredCommandResult(created, 1, "composition::new"),
731
+ balance: requiredCommandResult(created, 2, "composition::new"),
732
+ };
733
+ });
734
+
735
+ const recordings: WorkParts[] = p.recordings.map((node) => {
736
+ const parent = node.parentCompositionIndex !== undefined
737
+ ? requiredAt(compositions, node.parentCompositionIndex, "parent composition").work
738
+ : tx.object(node.parentCompositionId);
739
+ const created = tx.add(recording._new({
740
+ package: p.deployment.protocol.miso,
741
+ typeArguments: [node.shareType, node.compositionShareType],
742
+ arguments: [parent, node.shareCurrencyId, node.shareTreasuryCapId],
743
+ }));
744
+ return {
745
+ work: requiredCommandResult(created, 0, "recording::new"),
746
+ adminCap: requiredCommandResult(created, 1, "recording::new"),
747
+ balance: requiredCommandResult(created, 2, "recording::new"),
748
+ };
749
+ });
750
+
751
+ let releaseObject: TransactionObjectArgument | undefined;
752
+ let releaseAdminCap: TransactionObjectArgument | undefined;
753
+ if (p.release) {
754
+ const ids = p.release.tracks.map((node) => "recordingIndex" in node
755
+ ? tx.moveCall({
756
+ target: "0x2::object::id",
757
+ typeArguments: [`${p.deployment.protocol.miso}::recording::Recording<${p.recordings[node.recordingIndex]!.shareType},${p.recordings[node.recordingIndex]!.compositionShareType}>`],
758
+ arguments: [requiredAt(recordings, node.recordingIndex, "fresh recording").work],
759
+ })
760
+ : tx.pure.id(node.recordingId));
761
+ const targetReleaseId = tx.moveCall({
762
+ target: `${p.deployment.protocol.miso}::release::derive_target_release_id`,
763
+ arguments: [
764
+ tx.object(p.deployment.objects.releaseRegistry),
765
+ tx.makeMoveVec({ type: "0x2::object::ID", elements: ids }),
766
+ tx.makeMoveVec({ type: "u64", elements: p.release.tracks.map((node) => tx.pure.u64(node.splitBps)) }),
767
+ tx.pure.u256(BigInt(p.release.nonce)),
768
+ ],
769
+ });
770
+ const tracks = p.release.tracks.map((node) => {
771
+ if ("recordingIndex" in node) {
772
+ const recNode = requiredAt(p.recordings, node.recordingIndex, "fresh recording");
773
+ const rec = requiredAt(recordings, node.recordingIndex, "fresh recording");
774
+ return tx.add(track._new({
775
+ package: p.deployment.protocol.miso,
776
+ typeArguments: [recNode.shareType, recNode.compositionShareType],
777
+ arguments: [rec.adminCap, rec.work, targetReleaseId, node.splitBps],
778
+ }));
779
+ }
780
+ return invokeWithAdminCap(tx, node.authority, {
781
+ target: `${p.deployment.protocol.miso}::track::new`,
782
+ typeArguments: [node.recordingShareType, node.compositionShareType],
783
+ arguments: [tx.object(node.recordingId), targetReleaseId, tx.pure.u16(node.splitBps)],
784
+ adminCapIndex: 0,
785
+ });
786
+ });
787
+ const created = tx.moveCall({
788
+ target: `${p.deployment.protocol.miso}::release::new`,
789
+ arguments: [
790
+ tx.object(p.deployment.objects.releaseRegistry),
791
+ tx.pure.string(p.release.title),
792
+ tx.makeMoveVec({ type: `${p.deployment.protocol.miso}::track::Track`, elements: tracks }),
793
+ tx.pure.u256(BigInt(p.release.nonce)),
794
+ ],
795
+ });
796
+ releaseObject = requiredCommandResult(created, 0, "release::new");
797
+ releaseAdminCap = requiredCommandResult(created, 1, "release::new");
798
+ }
799
+
800
+ p.compositions.forEach((node, index) => {
801
+ const parts = requiredAt(compositions, index, "composition");
802
+ for (const credit of node.credits ?? []) {
803
+ attachCompositionCredit({
804
+ compositionId: parts.work,
805
+ authority: directAdminCap(parts.adminCap),
806
+ partyId: partyByRef(parties, credit.party),
807
+ displayName: credit.displayName,
808
+ roles: credit.roles,
809
+ compositionShareType: node.shareType,
810
+ compositionCreditsPackageId: p.deployment.packages.compositionCredits,
811
+ misoCreditPackageId: p.deployment.packages.credit,
812
+ })(tx);
813
+ }
814
+ });
815
+
816
+ p.recordings.forEach((node, index) => {
817
+ const parts = requiredAt(recordings, index, "recording");
818
+ const authority = directAdminCap(parts.adminCap);
819
+ for (const credit of node.credits ?? []) {
820
+ const target = partyByRef(parties, credit.party);
821
+ attachRecordingCredit({
822
+ recordingId: parts.work, authority, partyId: target,
823
+ displayName: credit.displayName, roles: credit.roles,
824
+ recordingShareType: node.shareType,
825
+ compositionShareType: node.compositionShareType,
826
+ recordingCreditsPackageId: p.deployment.packages.recordingCredits,
827
+ misoCreditPackageId: p.deployment.packages.credit,
828
+ })(tx);
829
+ if (credit.primaryArtist) {
830
+ addRecordingPrimaryArtist({
831
+ recordingId: parts.work, authority, partyId: target,
832
+ recordingShareType: node.shareType,
833
+ compositionShareType: node.compositionShareType,
834
+ recordingCreditsPackageId: p.deployment.packages.recordingCredits,
835
+ })(tx);
836
+ }
837
+ }
838
+ if (node.advisory) setRecordingAdvisory({
839
+ recordingId: parts.work, authority,
840
+ recordingShareType: node.shareType,
841
+ compositionShareType: node.compositionShareType,
842
+ recordingAdvisoryPackageId: p.deployment.packages.recordingAdvisory,
843
+ rating: node.advisory,
844
+ })(tx);
845
+ if (node.languages?.kind === "instrumental") setRecordingInstrumental({
846
+ recordingId: parts.work, authority,
847
+ recordingShareType: node.shareType,
848
+ compositionShareType: node.compositionShareType,
849
+ recordingLanguagePackageId: p.deployment.packages.recordingLanguage,
850
+ })(tx);
851
+ if (node.languages?.kind === "languages") setRecordingLanguages({
852
+ recordingId: parts.work, authority,
853
+ recordingShareType: node.shareType,
854
+ compositionShareType: node.compositionShareType,
855
+ recordingLanguagePackageId: p.deployment.packages.recordingLanguage,
856
+ languages: languageVector(tx, p, node.languages.codes),
857
+ })(tx);
858
+ if (node.masterReferenceBlobId !== undefined) setRecordingMasterReference({
859
+ recordingId: parts.work, authority,
860
+ recordingShareType: node.shareType,
861
+ compositionShareType: node.compositionShareType,
862
+ recordingMasterReferencePackageId: p.deployment.packages.recordingMasterReference,
863
+ reference: walrusBlob(tx, p, node.masterReferenceBlobId),
864
+ })(tx);
865
+ if (node.streamingTranscodeQuiltId !== undefined) setRecordingStreamingTranscode({
866
+ recordingId: parts.work, authority,
867
+ recordingShareType: node.shareType,
868
+ compositionShareType: node.compositionShareType,
869
+ recordingStreamingTranscodePackageId:
870
+ p.deployment.packages.recordingStreamingTranscode!,
871
+ oriPackageId: p.deployment.packages.ori,
872
+ quiltId: node.streamingTranscodeQuiltId,
873
+ })(tx);
874
+ if (node.previewBlobId !== undefined) setRecordingPreview({
875
+ recordingId: parts.work, authority,
876
+ recordingShareType: node.shareType,
877
+ compositionShareType: node.compositionShareType,
878
+ recordingPreviewPackageId: p.deployment.packages.recordingPreview,
879
+ reference: walrusBlob(tx, p, node.previewBlobId),
880
+ })(tx);
881
+ });
882
+
883
+ if (p.release && releaseObject && releaseAdminCap) {
884
+ const authority = directAdminCap(releaseAdminCap);
885
+ for (const credit of p.release.credits ?? []) addReleaseCredit({
886
+ releaseId: releaseObject, authority,
887
+ partyId: partyByRef(parties, credit.party),
888
+ displayName: credit.displayName, role: credit.role,
889
+ releaseCreditsPackageId: p.deployment.packages.releaseCredits,
890
+ misoCreditPackageId: p.deployment.packages.credit,
891
+ })(tx);
892
+ if (p.release.kind) setReleaseKind({
893
+ releaseId: releaseObject, authority, kind: p.release.kind,
894
+ releaseKindPackageId: p.deployment.packages.releaseKind,
895
+ })(tx);
896
+ if (p.release.description) setReleaseDescription({
897
+ releaseId: releaseObject, authority, description: p.release.description,
898
+ releaseDescriptionPackageId: p.deployment.packages.releaseDescription,
899
+ })(tx);
900
+ if (p.release.genres) setReleaseGenres({
901
+ releaseId: releaseObject, authority,
902
+ primaryGenreId: p.release.genres.primaryGenreId,
903
+ secondaryGenreIds: p.release.genres.secondaryGenreIds ?? [],
904
+ trackPrimaryGenres: p.release.genres.tracks ?? [],
905
+ releaseGenrePackageId: p.deployment.packages.releaseGenre,
906
+ })(tx);
907
+ if (p.release.dspLinks) setReleaseDspLinks({
908
+ releaseId: releaseObject, authority,
909
+ releaseLinks: p.release.dspLinks.release ?? [],
910
+ trackLinks: p.release.dspLinks.tracks ?? [],
911
+ releaseDspLinkPackageId: p.deployment.packages.releaseDspLink,
912
+ })(tx);
913
+ if (p.release.cover?.stillBlobId !== undefined) setReleaseCover({
914
+ releaseId: releaseObject, authority,
915
+ stillBlobId: p.release.cover.stillBlobId,
916
+ animatedBlobId: p.release.cover.animatedBlobId,
917
+ coverArtPackageId: p.deployment.packages.coverArt,
918
+ releaseCoverArtPackageId: p.deployment.packages.releaseCoverArt,
919
+ oriPackageId: p.deployment.packages.ori,
920
+ })(tx);
921
+ for (const cover of p.release.cover?.tracks ?? []) setReleaseTrackCover({
922
+ releaseId: releaseObject, authority,
923
+ trackIndex: cover.trackIndex,
924
+ stillBlobId: cover.stillBlobId,
925
+ animatedBlobId: cover.animatedBlobId,
926
+ coverArtPackageId: p.deployment.packages.coverArt,
927
+ releaseCoverArtPackageId: p.deployment.packages.releaseCoverArt,
928
+ oriPackageId: p.deployment.packages.ori,
929
+ })(tx);
930
+ }
931
+
932
+ let pressingParts: { pressing: TransactionObjectArgument; adminCap: TransactionObjectArgument } | undefined;
933
+ if (p.pressing && releaseObject && releaseAdminCap) {
934
+ const sales = requireRecordSalesDeployment(p.deployment.recordSales);
935
+ const created = tx.add(pressingContract._new({
936
+ package: sales.recordPackageId,
937
+ arguments: [
938
+ releaseObject,
939
+ releaseAdminCap,
940
+ publicationEdition(p.pressing.edition),
941
+ publicationMaxSupply(p.pressing.maxSupply),
942
+ ],
943
+ }));
944
+ pressingParts = {
945
+ pressing: requiredCommandResult(created, 0, "pressing::new"),
946
+ adminCap: requiredCommandResult(created, 1, "pressing::new"),
947
+ };
948
+ tx.add(pressingContract.authorizeDistributor({
949
+ package: sales.recordPackageId,
950
+ typeArguments: [`${sales.recordShopPackageId}::witness::Witness`],
951
+ arguments: [pressingParts.pressing, pressingParts.adminCap],
952
+ }));
953
+ for (const listing of p.pressing.listings) {
954
+ const createdListing = tx.add(listingContract._new({
955
+ package: sales.recordShopPackageId,
956
+ typeArguments: [listing.currencyType],
957
+ arguments: [
958
+ pressingParts.pressing,
959
+ pressingParts.adminCap,
960
+ listingPrice(tx, p, listing.price),
961
+ ],
962
+ }));
963
+ if (listing.enabled === false) {
964
+ tx.add(listingContract.setState({
965
+ package: sales.recordShopPackageId,
966
+ typeArguments: [listing.currencyType],
967
+ arguments: [
968
+ createdListing,
969
+ pressingParts.adminCap,
970
+ tx.add(listingContract.disabled({ package: sales.recordShopPackageId })),
971
+ ],
972
+ }));
973
+ }
974
+ tx.add(listingContract.share({
975
+ package: sales.recordShopPackageId,
976
+ typeArguments: [listing.currencyType],
977
+ arguments: [createdListing],
978
+ }));
979
+ }
980
+ }
981
+
982
+ p.recordings.forEach((node, index) => publishRecording(
983
+ tx,
984
+ p,
985
+ node,
986
+ requiredAt(recordings, index, "recording"),
987
+ node.parentCompositionIndex === undefined
988
+ ? undefined
989
+ : {
990
+ node: requiredAt(p.compositions, node.parentCompositionIndex, "parent composition"),
991
+ parts: requiredAt(compositions, node.parentCompositionIndex, "parent composition"),
992
+ },
993
+ ));
994
+ p.compositions.forEach((node, index) => publishComposition(tx, p, node, requiredAt(compositions, index, "composition")));
995
+ if (p.release && releaseObject && releaseAdminCap) {
996
+ publishReleaseObject(tx, p, p.release, releaseObject, releaseAdminCap);
997
+ }
998
+
999
+ for (const node of p.parties) {
1000
+ if ("id" in node) continue;
1001
+ const parts = createdParties.get(node.ref)!;
1002
+ tx.add(party.share({
1003
+ package: p.deployment.protocol.misoParty,
1004
+ arguments: [parts.party, parts.adminCap],
1005
+ }));
1006
+ disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, partyCapType(p)));
1007
+ }
1008
+
1009
+ if (p.pressing && pressingParts) {
1010
+ const sales = requireRecordSalesDeployment(p.deployment.recordSales);
1011
+ tx.add(pressingContract.share({
1012
+ package: sales.recordPackageId,
1013
+ arguments: [pressingParts.pressing],
1014
+ }));
1015
+ disposeNewAdminCap(tx, pressingParts.adminCap, custody(
1016
+ p, p.pressing.custody, pressingCapType(p),
1017
+ ));
1018
+ }
1019
+ };
1020
+ }
1021
+
1022
+ export interface AtomicPublicationInspection {
1023
+ commands: number;
1024
+ inputs: number;
1025
+ }
1026
+
1027
+ /** Assemble without RPC access so callers can fail before publishing share packages. */
1028
+ export function inspectAtomicPublication(p: AtomicPublicationParams): AtomicPublicationInspection {
1029
+ const tx = new Transaction();
1030
+ publishAtomicCatalog(p)(tx);
1031
+ const data = tx.getData();
1032
+ return { commands: data.commands.length, inputs: data.inputs.length };
1033
+ }
1034
+
1035
+ export function assertAtomicPublicationBounds(p: AtomicPublicationParams): AtomicPublicationInspection {
1036
+ const inspection = inspectAtomicPublication(p);
1037
+ if (inspection.commands > MAX_ATOMIC_PUBLICATION_COMMANDS) {
1038
+ throw new Error(
1039
+ `Atomic publication requires ${inspection.commands} commands; the SDK safety cap is ` +
1040
+ `${MAX_ATOMIC_PUBLICATION_COMMANDS} (Sui permits at most 1024).`,
1041
+ );
1042
+ }
1043
+ if (inspection.inputs > MAX_ATOMIC_PUBLICATION_INPUTS) {
1044
+ throw new Error(
1045
+ `Atomic publication requires ${inspection.inputs} inputs; Sui permits at most ` +
1046
+ `${MAX_ATOMIC_PUBLICATION_INPUTS}.`,
1047
+ );
1048
+ }
1049
+ return inspection;
1050
+ }
1051
+
1052
+ export type PublicationAuthorityOut =
1053
+ | { kind: "direct"; adminCapId: string }
1054
+ | { kind: "vault"; vaultId: string; vaultAdminCapId: string; capType: string; vaultPackageId: string };
1055
+
1056
+ export interface AtomicPublicationResult {
1057
+ digest: string;
1058
+ gasUsed: number;
1059
+ parties: Record<string, { id: string; adminCapId: string; created: boolean; authority?: PublicationAuthorityOut }>;
1060
+ compositions: Record<string, { id: string; adminCapId: string; shareType: string; authority: PublicationAuthorityOut; royaltyPoolId?: string }>;
1061
+ recordings: Record<string, { id: string; adminCapId: string; shareType: string; compositionShareType: string; authority: PublicationAuthorityOut; royaltyPoolId?: string; routedStakeId?: string }>;
1062
+ release?: { id: string; adminCapId: string; authority: PublicationAuthorityOut };
1063
+ pressing?: { id: string; adminCapId: string; authority: PublicationAuthorityOut };
1064
+ }
1065
+
1066
+ function compactType(type: string): string {
1067
+ return type.replace(/\s+/g, "");
1068
+ }
1069
+
1070
+ function findByShareType(
1071
+ created: { objectId: string; objectType: string }[],
1072
+ shareType: string,
1073
+ description: string,
1074
+ ): string {
1075
+ const wanted = compactType(shareType);
1076
+ const matches = created.filter((item) => compactType(item.objectType).includes(wanted));
1077
+ if (matches.length !== 1) throw new Error(`Expected one ${description} for ${shareType}; found ${matches.length}`);
1078
+ return matches[0]!.objectId;
1079
+ }
1080
+
1081
+ function vaultsByCap(result: PlatformExecResult, vaultPackageId: string) {
1082
+ const out = new Map<string, { vaultId: string; vaultAdminCapId: string }>();
1083
+ for (const event of result.events) {
1084
+ if (!event.eventType.includes("::vault::VaultCreatedEvent<")) continue;
1085
+ const parsed = parseVaultCreatedEvent(event.bcs);
1086
+ out.set(parsed.cap_id, {
1087
+ vaultId: parsed.vault_id,
1088
+ vaultAdminCapId: deriveVaultAdminCapId(parsed.vault_id, vaultPackageId),
1089
+ });
1090
+ }
1091
+ return out;
1092
+ }
1093
+
1094
+ function authorityOut(
1095
+ result: PlatformExecResult,
1096
+ vaults: ReturnType<typeof vaultsByCap>,
1097
+ selected: PublicationCustody,
1098
+ adminCapId: string,
1099
+ capType: string,
1100
+ vaultPackageId: string,
1101
+ ): PublicationAuthorityOut {
1102
+ if (selected.kind === "direct") return { kind: "direct", adminCapId };
1103
+ const found = vaults.get(adminCapId);
1104
+ if (!found) throw new Error(`No VaultCreatedEvent for admin cap ${adminCapId} in ${result.digest}`);
1105
+ return { kind: "vault", ...found, capType, vaultPackageId };
1106
+ }
1107
+
1108
+ /** Parse one atomic publication using type identity plus lifecycle event payloads. */
1109
+ export function parseAtomicPublicationResult(
1110
+ p: AtomicPublicationParams,
1111
+ result: PlatformExecResult,
1112
+ ): AtomicPublicationResult {
1113
+ const available = publicationUsesVault(p) ? operations(p) : undefined;
1114
+ const vaultPackageId = available?.vault.packageId ?? "";
1115
+ const vaults = available
1116
+ ? vaultsByCap(result, vaultPackageId)
1117
+ : new Map<string, { vaultId: string; vaultAdminCapId: string }>();
1118
+ const createdCompositions = allCreatedByType(result, "::composition::Composition<");
1119
+ const createdRecordings = allCreatedByType(result, "::recording::Recording<");
1120
+ const createdPools = allCreatedByType(result, "::pool::RoyaltyPool<");
1121
+
1122
+ const createdPartyNodes = p.parties.filter((node): node is Extract<PublicationParty, { create: string }> => "create" in node);
1123
+ const partyEvents = result.events.filter((event) => event.eventType.endsWith("::party::PartyCreatedEvent"));
1124
+ if (partyEvents.length !== createdPartyNodes.length) {
1125
+ throw new Error(`Expected ${createdPartyNodes.length} PartyCreatedEvent values; found ${partyEvents.length}`);
1126
+ }
1127
+ const parties: AtomicPublicationResult["parties"] = {};
1128
+ for (const node of p.parties) {
1129
+ if ("id" in node) {
1130
+ parties[node.ref] = {
1131
+ id: node.id,
1132
+ adminCapId: derivePartyAdminCapId(node.id, p.deployment.protocol.misoParty),
1133
+ created: false,
1134
+ };
1135
+ }
1136
+ }
1137
+ createdPartyNodes.forEach((node, index) => {
1138
+ const event = partyEvents[index]!;
1139
+ const parsed = protocolContracts.party.PartyCreatedEvent.parse(event.bcs);
1140
+ const adminCapId = derivePartyAdminCapId(parsed.party_id, p.deployment.protocol.misoParty);
1141
+ parties[node.ref] = {
1142
+ id: parsed.party_id,
1143
+ adminCapId,
1144
+ created: true,
1145
+ authority: authorityOut(result, vaults, node.custody, adminCapId, partyCapType(p), vaultPackageId),
1146
+ };
1147
+ });
1148
+
1149
+ const compositions: AtomicPublicationResult["compositions"] = {};
1150
+ p.compositions.forEach((node) => {
1151
+ const id = findByShareType(createdCompositions, node.shareType, "Composition");
1152
+ const adminCapId = deriveCompositionAdminCapId(id, p.deployment.protocol.miso);
1153
+ compositions[node.ref] = {
1154
+ id,
1155
+ adminCapId,
1156
+ shareType: node.shareType,
1157
+ authority: authorityOut(result, vaults, node.custody, adminCapId, compositionCapType(p, node.shareType), vaultPackageId),
1158
+ royaltyPoolId: node.royaltyPool ? findByShareType(createdPools, node.shareType, "Composition RoyaltyPool") : undefined,
1159
+ };
1160
+ });
1161
+
1162
+ const recordings: AtomicPublicationResult["recordings"] = {};
1163
+ p.recordings.forEach((node) => {
1164
+ const id = findByShareType(createdRecordings, node.shareType, "Recording");
1165
+ const adminCapId = deriveRecordingAdminCapId(id, p.deployment.protocol.miso);
1166
+ recordings[node.ref] = {
1167
+ id,
1168
+ adminCapId,
1169
+ shareType: node.shareType,
1170
+ compositionShareType: node.compositionShareType,
1171
+ authority: authorityOut(result, vaults, node.custody, adminCapId, recordingCapType(p, node.shareType), vaultPackageId),
1172
+ royaltyPoolId: node.royaltyPool ? findByShareType(createdPools, node.shareType, "Recording RoyaltyPool") : undefined,
1173
+ routedStakeId: node.routedStake
1174
+ ? createdByExactType(
1175
+ result,
1176
+ `${p.deployment.packages.routedStake}::routed_stake::RoutedStake<${node.shareType},${node.compositionShareType}>`,
1177
+ )
1178
+ : undefined,
1179
+ };
1180
+ });
1181
+
1182
+ let releaseOut: AtomicPublicationResult["release"];
1183
+ if (p.release) {
1184
+ const id = createdByExactType(result, `${p.deployment.protocol.miso}::release::Release`);
1185
+ const adminCapId = deriveReleaseAdminCapId(id, p.deployment.protocol.miso);
1186
+ releaseOut = {
1187
+ id,
1188
+ adminCapId,
1189
+ authority: authorityOut(result, vaults, p.release.custody, adminCapId, releaseCapType(p), vaultPackageId),
1190
+ };
1191
+ }
1192
+
1193
+ let pressingOut: AtomicPublicationResult["pressing"];
1194
+ if (p.pressing && releaseOut) {
1195
+ const sales = requireRecordSalesDeployment(p.deployment.recordSales);
1196
+ const id = derivePressingId(releaseOut.id, p.pressing.edition, sales.recordPackageId);
1197
+ const adminCapId = derivePressingAdminCapId(id, sales.recordPackageId);
1198
+ pressingOut = {
1199
+ id,
1200
+ adminCapId,
1201
+ authority: authorityOut(result, vaults, p.pressing.custody, adminCapId, pressingCapType(p), vaultPackageId),
1202
+ };
1203
+ }
1204
+
1205
+ return {
1206
+ digest: result.digest,
1207
+ gasUsed: result.gasUsed,
1208
+ parties,
1209
+ compositions,
1210
+ recordings,
1211
+ release: releaseOut,
1212
+ pressing: pressingOut,
1213
+ };
1214
+ }