@misofm/sdk 0.12.4 → 0.13.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.
@@ -0,0 +1,949 @@
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. Fresh raw
10
+ * protocol capabilities never leave the transaction: data extensions use them
11
+ * directly, Vault-only plugins are configured while each new Vault is still
12
+ * owned, and only the selected direct cap or VaultAdminCap is delivered.
13
+ */
14
+
15
+ import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions";
16
+ import {
17
+ contracts as protocolContracts,
18
+ deriveCompositionAdminCapId,
19
+ deriveRecordingAdminCapId,
20
+ deriveReleaseAdminCapId,
21
+ } from "@misonetwork/sdk";
22
+ import { derivePartyAdminCapId } from "@misonetwork/sdk/party";
23
+ import type { MisoPlatformDeployment } from "./deployments.ts";
24
+ import type { TxThunk } from "./transactions.ts";
25
+ import { disperseShares, requiredCommandResult, type ShareRecipient } from "./transactions.ts";
26
+ import {
27
+ addRecordingPrimaryArtist,
28
+ addReleaseCredit,
29
+ attachCompositionCredit,
30
+ attachRecordingCredit,
31
+ type CompositionRole,
32
+ type RecordingRole,
33
+ type ReleaseRole,
34
+ } from "./credits.ts";
35
+ import {
36
+ setRecordingAdvisory,
37
+ setRecordingInstrumental,
38
+ setRecordingLanguages,
39
+ setRecordingMasterReference,
40
+ setRecordingPreview,
41
+ } from "./recording-extensions.ts";
42
+ import {
43
+ setReleaseDescription,
44
+ setReleaseDspLinks,
45
+ setReleaseGenres,
46
+ setReleaseKind,
47
+ type DspLink,
48
+ } from "./release-extensions.ts";
49
+ import { setReleaseCover, setReleaseTrackCover } from "./cover.ts";
50
+ import {
51
+ custodyNewAdminCap,
52
+ directAdminCap,
53
+ disposeNewAdminCap,
54
+ initializeCompositionRoyaltyPool,
55
+ initializeRecordingRoyaltyPool,
56
+ installCompositionRoutedStakePlugin,
57
+ installCompositionRoyaltyPoolPlugin,
58
+ installPartyWalletPlugin,
59
+ installRecordingRoyaltyPoolPlugin,
60
+ installReleaseRevenueDistributorPlugin,
61
+ invokeWithAdminCap,
62
+ parseVaultCreatedEvent,
63
+ type AdminCapAuthority,
64
+ type AdminCapCustody,
65
+ } from "./vault.ts";
66
+ import {
67
+ derivePressingAdminCapId,
68
+ derivePressingId,
69
+ type ListingPrice,
70
+ type PressingRunState,
71
+ } from "./pressing.ts";
72
+ import { allCreatedByType, createdByExactType, type PlatformExecResult } from "./execute.ts";
73
+ import * as pressingContract from "./contracts/miso_pressing/pressing.ts";
74
+ import * as listingContract from "./contracts/miso_pressing/listing.ts";
75
+
76
+ const { composition, recording, release, track, party } = protocolContracts;
77
+
78
+ export const MAX_ATOMIC_PUBLICATION_COMMANDS = 900;
79
+ export const MAX_ATOMIC_PUBLICATION_INPUTS = 2048;
80
+
81
+ export interface PublicationCustody {
82
+ readonly kind: "direct" | "vault";
83
+ readonly owner: string;
84
+ }
85
+
86
+ export type PublicationParty =
87
+ | { readonly ref: string; readonly id: string }
88
+ | {
89
+ readonly ref: string;
90
+ readonly create: "individual" | "group";
91
+ readonly name: string;
92
+ readonly custody: PublicationCustody;
93
+ /** Defaults to true with Vault custody. */
94
+ readonly installWallet?: boolean;
95
+ };
96
+
97
+ export interface PublicationCompositionCredit {
98
+ readonly party: string;
99
+ readonly displayName: string;
100
+ readonly roles: CompositionRole[];
101
+ }
102
+
103
+ export interface PublicationRecordingCredit {
104
+ readonly party: string;
105
+ readonly displayName: string;
106
+ readonly roles: RecordingRole[];
107
+ readonly primaryArtist?: boolean;
108
+ }
109
+
110
+ export interface PublicationReleaseCredit {
111
+ readonly party: string;
112
+ readonly displayName: string;
113
+ readonly role: ReleaseRole;
114
+ }
115
+
116
+ export interface PublicationComposition {
117
+ readonly ref: string;
118
+ readonly shareType: string;
119
+ readonly shareCurrencyId: string;
120
+ readonly shareTreasuryCapId: string;
121
+ readonly title: string;
122
+ readonly royaltyRateBps: number;
123
+ readonly shareRecipients: ShareRecipient[];
124
+ readonly custody: PublicationCustody;
125
+ readonly credits?: PublicationCompositionCredit[];
126
+ readonly royaltyPool?: { readonly currencyType: string };
127
+ readonly routedStake?: boolean;
128
+ }
129
+
130
+ type PublicationRecordingParent =
131
+ | { readonly parentCompositionIndex: number; readonly parentCompositionId?: never }
132
+ | { readonly parentCompositionIndex?: never; readonly parentCompositionId: string };
133
+
134
+ export type PublicationRecordingLanguages =
135
+ | { readonly kind: "instrumental" }
136
+ | { readonly kind: "languages"; readonly codes: string[] };
137
+
138
+ export type PublicationRecording = PublicationRecordingParent & {
139
+ readonly ref: string;
140
+ readonly shareType: string;
141
+ readonly shareCurrencyId: string;
142
+ readonly shareTreasuryCapId: string;
143
+ readonly compositionShareType: string;
144
+ readonly shareRecipients: ShareRecipient[];
145
+ readonly custody: PublicationCustody;
146
+ readonly credits?: PublicationRecordingCredit[];
147
+ readonly royaltyPool?: { readonly currencyType: string };
148
+ readonly advisory?: "Explicit" | "NotExplicit" | "Cleaned";
149
+ readonly languages?: PublicationRecordingLanguages;
150
+ readonly masterReferenceBlobId?: bigint | string;
151
+ readonly previewBlobId?: bigint | string;
152
+ };
153
+
154
+ export interface PublicationFreshTrack {
155
+ readonly recordingIndex: number;
156
+ readonly splitBps: number;
157
+ }
158
+
159
+ export interface PublicationExistingTrack {
160
+ readonly recordingId: string;
161
+ readonly recordingShareType: string;
162
+ readonly compositionShareType: string;
163
+ readonly splitBps: number;
164
+ readonly authority: AdminCapAuthority;
165
+ }
166
+
167
+ export type PublicationTrack = PublicationFreshTrack | PublicationExistingTrack;
168
+
169
+ export interface PublicationRelease {
170
+ readonly title: string;
171
+ readonly nonce: string;
172
+ readonly tracks: PublicationTrack[];
173
+ readonly custody: PublicationCustody;
174
+ readonly credits?: PublicationReleaseCredit[];
175
+ readonly kind?: string;
176
+ readonly description?: string;
177
+ readonly genres?: {
178
+ readonly primaryGenreId: string;
179
+ readonly secondaryGenreIds?: string[];
180
+ readonly tracks?: { readonly trackIndex: number; readonly genreId: string }[];
181
+ };
182
+ readonly dspLinks?: {
183
+ readonly release?: DspLink[];
184
+ readonly tracks?: { readonly trackIndex: number; readonly link: DspLink }[];
185
+ };
186
+ readonly cover?: {
187
+ readonly stillBlobId?: bigint | string;
188
+ readonly animatedBlobId?: bigint | string;
189
+ readonly tracks?: {
190
+ readonly trackIndex: number;
191
+ readonly stillBlobId: bigint | string;
192
+ readonly animatedBlobId?: bigint | string;
193
+ }[];
194
+ };
195
+ readonly revenueDistribution?: boolean;
196
+ }
197
+
198
+ export interface PublicationPressing {
199
+ readonly state?: PressingRunState;
200
+ readonly listings: {
201
+ readonly currencyType: string;
202
+ readonly price: ListingPrice;
203
+ readonly enabled?: boolean;
204
+ }[];
205
+ readonly custody: PublicationCustody;
206
+ }
207
+
208
+ export interface AtomicPublicationParams {
209
+ readonly deployment: MisoPlatformDeployment;
210
+ readonly parties: PublicationParty[];
211
+ readonly compositions: PublicationComposition[];
212
+ readonly recordings: PublicationRecording[];
213
+ readonly release?: PublicationRelease;
214
+ readonly pressing?: PublicationPressing;
215
+ }
216
+
217
+ interface WorkParts {
218
+ work: TransactionObjectArgument;
219
+ adminCap: TransactionObjectArgument;
220
+ balance: TransactionObjectArgument;
221
+ }
222
+
223
+ interface PartyParts {
224
+ party: TransactionObjectArgument;
225
+ adminCap: TransactionObjectArgument;
226
+ }
227
+
228
+ function requiredAt<T>(items: readonly T[], index: number, description: string): T {
229
+ const value = items[index];
230
+ if (value === undefined) throw new Error(`${description} index ${index} is out of range`);
231
+ return value;
232
+ }
233
+
234
+ function partyCapType(p: AtomicPublicationParams): string {
235
+ return `${p.deployment.protocol.misoParty}::party::PartyAdminCap`;
236
+ }
237
+
238
+ function compositionCapType(p: AtomicPublicationParams, shareType: string): string {
239
+ return `${p.deployment.protocol.miso}::composition::CompositionAdminCap<${shareType}>`;
240
+ }
241
+
242
+ function recordingCapType(p: AtomicPublicationParams, shareType: string): string {
243
+ return `${p.deployment.protocol.miso}::recording::RecordingAdminCap<${shareType}>`;
244
+ }
245
+
246
+ function releaseCapType(p: AtomicPublicationParams): string {
247
+ return `${p.deployment.protocol.miso}::release::ReleaseAdminCap`;
248
+ }
249
+
250
+ function pressingCapType(p: AtomicPublicationParams): string {
251
+ return `${p.deployment.packages.pressing}::pressing::PressingAdminCap`;
252
+ }
253
+
254
+ function custody(
255
+ p: AtomicPublicationParams,
256
+ selected: PublicationCustody,
257
+ capType: string,
258
+ configure?: Exclude<AdminCapCustody, { kind: "direct" }>["configure"],
259
+ ): AdminCapCustody {
260
+ if (selected.kind === "direct") return { kind: "direct", owner: selected.owner };
261
+ return {
262
+ kind: "vault",
263
+ owner: selected.owner,
264
+ capType,
265
+ vaultPackageId: p.deployment.packages.vault,
266
+ configure,
267
+ };
268
+ }
269
+
270
+ function partyByRef(
271
+ parties: Map<string, TransactionObjectArgument>,
272
+ ref: string,
273
+ ): TransactionObjectArgument {
274
+ const value = parties.get(ref);
275
+ if (!value) throw new Error(`Unknown publication party ref ${JSON.stringify(ref)}`);
276
+ return value;
277
+ }
278
+
279
+ function languageVector(tx: Transaction, p: AtomicPublicationParams, codes: string[]) {
280
+ const values = codes.map((code) => tx.moveCall({
281
+ target: `${p.deployment.protocol.languageCode}::language_code::new`,
282
+ arguments: [tx.pure.string(code)],
283
+ }));
284
+ return tx.makeMoveVec({
285
+ type: `${p.deployment.protocol.languageCode}::language_code::LanguageCode`,
286
+ elements: values,
287
+ });
288
+ }
289
+
290
+ function walrusBlob(tx: Transaction, p: AtomicPublicationParams, blobId: bigint | string) {
291
+ return tx.moveCall({
292
+ target: `${p.deployment.packages.ori}::walrus_data::new_blob`,
293
+ arguments: [tx.pure.u256(blobId)],
294
+ });
295
+ }
296
+
297
+ function pressingState(tx: Transaction, p: AtomicPublicationParams, state: PressingRunState) {
298
+ const pkg = p.deployment.packages.pressing;
299
+ if (state.kind === "scheduled") {
300
+ return tx.add(pressingContract.newScheduledState({
301
+ package: pkg,
302
+ arguments: [BigInt(state.startTimestampMs)],
303
+ }));
304
+ }
305
+ if (state.kind === "paused") return tx.add(pressingContract.newPausedState({ package: pkg }));
306
+ return tx.add(pressingContract.newActiveState({ package: pkg }));
307
+ }
308
+
309
+ function listingPrice(tx: Transaction, p: AtomicPublicationParams, price: ListingPrice) {
310
+ const pkg = p.deployment.packages.pressing;
311
+ const args = { package: pkg, arguments: [BigInt(price.amount)] as [bigint] };
312
+ return tx.add(price.kind === "fixed"
313
+ ? listingContract.newFixedPrice(args)
314
+ : listingContract.newFloorPrice(args));
315
+ }
316
+
317
+ function publishComposition(
318
+ tx: Transaction,
319
+ p: AtomicPublicationParams,
320
+ node: PublicationComposition,
321
+ parts: WorkParts,
322
+ ): void {
323
+ disperseShares(tx, p.deployment.packages.minato, node.shareType, parts.balance, node.shareRecipients);
324
+ const typeArguments: [string] = [node.shareType];
325
+ if (node.custody.kind === "direct") {
326
+ tx.add(composition.publish({
327
+ package: p.deployment.protocol.miso,
328
+ typeArguments,
329
+ arguments: [parts.work, parts.adminCap],
330
+ }));
331
+ disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, compositionCapType(p, node.shareType)));
332
+ return;
333
+ }
334
+ custodyNewAdminCap(tx, {
335
+ adminCap: parts.adminCap,
336
+ capType: compositionCapType(p, node.shareType),
337
+ vaultPackageId: p.deployment.packages.vault,
338
+ owner: node.custody.owner,
339
+ configure: (vault, vaultAdminCap) => {
340
+ if (node.royaltyPool) {
341
+ installCompositionRoyaltyPoolPlugin(tx, {
342
+ vault, vaultAdminCap, compositionShareType: node.shareType,
343
+ pluginPackageId: p.deployment.packages.compositionRoyaltyPoolPlugin,
344
+ });
345
+ initializeCompositionRoyaltyPool(tx, {
346
+ vault, vaultAdminCap, composition: parts.work,
347
+ compositionShareType: node.shareType,
348
+ currencyType: node.royaltyPool.currencyType,
349
+ pluginPackageId: p.deployment.packages.compositionRoyaltyPoolPlugin,
350
+ });
351
+ }
352
+ if (node.routedStake) {
353
+ installCompositionRoutedStakePlugin(tx, {
354
+ vault, vaultAdminCap, compositionShareType: node.shareType,
355
+ pluginPackageId: p.deployment.packages.compositionRoutedStakePlugin,
356
+ });
357
+ }
358
+ invokeWithAdminCap(tx, {
359
+ kind: "vault", vault, vaultAdminCap,
360
+ capType: compositionCapType(p, node.shareType),
361
+ vaultPackageId: p.deployment.packages.vault,
362
+ }, {
363
+ target: `${p.deployment.protocol.miso}::composition::publish`,
364
+ typeArguments,
365
+ arguments: [parts.work, tx.object.clock()],
366
+ adminCapIndex: 1,
367
+ });
368
+ },
369
+ });
370
+ }
371
+
372
+ function publishRecording(
373
+ tx: Transaction,
374
+ p: AtomicPublicationParams,
375
+ node: PublicationRecording,
376
+ parts: WorkParts,
377
+ ): void {
378
+ const typeArguments: [string, string] = [node.shareType, node.compositionShareType];
379
+ disperseShares(tx, p.deployment.packages.minato, node.shareType, parts.balance, node.shareRecipients);
380
+ if (node.custody.kind === "direct") {
381
+ tx.add(recording.publish({
382
+ package: p.deployment.protocol.miso,
383
+ typeArguments,
384
+ arguments: [parts.work, parts.adminCap],
385
+ }));
386
+ disposeNewAdminCap(tx, parts.adminCap, custody(p, node.custody, recordingCapType(p, node.shareType)));
387
+ return;
388
+ }
389
+ custodyNewAdminCap(tx, {
390
+ adminCap: parts.adminCap,
391
+ capType: recordingCapType(p, node.shareType),
392
+ vaultPackageId: p.deployment.packages.vault,
393
+ owner: node.custody.owner,
394
+ configure: (vault, vaultAdminCap) => {
395
+ if (node.royaltyPool) {
396
+ installRecordingRoyaltyPoolPlugin(tx, {
397
+ vault, vaultAdminCap,
398
+ recordingShareType: node.shareType,
399
+ compositionShareType: node.compositionShareType,
400
+ pluginPackageId: p.deployment.packages.recordingRoyaltyPoolPlugin,
401
+ });
402
+ initializeRecordingRoyaltyPool(tx, {
403
+ vault, vaultAdminCap, recording: parts.work,
404
+ recordingShareType: node.shareType,
405
+ compositionShareType: node.compositionShareType,
406
+ currencyType: node.royaltyPool.currencyType,
407
+ pluginPackageId: p.deployment.packages.recordingRoyaltyPoolPlugin,
408
+ });
409
+ }
410
+ invokeWithAdminCap(tx, {
411
+ kind: "vault", vault, vaultAdminCap,
412
+ capType: recordingCapType(p, node.shareType),
413
+ vaultPackageId: p.deployment.packages.vault,
414
+ }, {
415
+ target: `${p.deployment.protocol.miso}::recording::publish`,
416
+ typeArguments,
417
+ arguments: [parts.work, tx.object.clock()],
418
+ adminCapIndex: 1,
419
+ });
420
+ },
421
+ });
422
+ }
423
+
424
+ function publishReleaseObject(
425
+ tx: Transaction,
426
+ p: AtomicPublicationParams,
427
+ node: PublicationRelease,
428
+ releaseObject: TransactionObjectArgument,
429
+ adminCap: TransactionObjectArgument,
430
+ ): void {
431
+ if (node.custody.kind === "direct") {
432
+ tx.add(release.publish({
433
+ package: p.deployment.protocol.miso,
434
+ arguments: [releaseObject, adminCap],
435
+ }));
436
+ disposeNewAdminCap(tx, adminCap, custody(p, node.custody, releaseCapType(p)));
437
+ return;
438
+ }
439
+ custodyNewAdminCap(tx, {
440
+ adminCap,
441
+ capType: releaseCapType(p),
442
+ vaultPackageId: p.deployment.packages.vault,
443
+ owner: node.custody.owner,
444
+ configure: (vault, vaultAdminCap) => {
445
+ if (node.revenueDistribution) {
446
+ installReleaseRevenueDistributorPlugin(tx, {
447
+ vault, vaultAdminCap,
448
+ pluginPackageId: p.deployment.packages.releaseRevenueDistributorPlugin,
449
+ });
450
+ }
451
+ invokeWithAdminCap(tx, {
452
+ kind: "vault", vault, vaultAdminCap,
453
+ capType: releaseCapType(p),
454
+ vaultPackageId: p.deployment.packages.vault,
455
+ }, {
456
+ target: `${p.deployment.protocol.miso}::release::publish`,
457
+ arguments: [releaseObject, tx.object.clock()],
458
+ adminCapIndex: 1,
459
+ });
460
+ },
461
+ });
462
+ }
463
+
464
+ /** Build the complete post-share catalog graph as one atomic PTB. */
465
+ export function publishAtomicCatalog(p: AtomicPublicationParams): TxThunk {
466
+ if (p.pressing && !p.release) throw new Error("A pressing requires a release");
467
+ for (const node of [...p.compositions, ...p.recordings]) {
468
+ if ((node.royaltyPool || ("routedStake" in node && node.routedStake)) && node.custody.kind !== "vault") {
469
+ throw new Error(`${node.ref}: Vault-only plugins require Vault custody`);
470
+ }
471
+ }
472
+ if (p.release?.revenueDistribution && p.release.custody.kind !== "vault") {
473
+ throw new Error("Release revenue distribution requires Vault custody");
474
+ }
475
+
476
+ return (tx) => {
477
+ const createdParties = new Map<string, PartyParts>();
478
+ const parties = new Map<string, TransactionObjectArgument>();
479
+ for (const node of p.parties) {
480
+ if ("id" in node) {
481
+ parties.set(node.ref, tx.object(node.id));
482
+ continue;
483
+ }
484
+ const kind = tx.add(node.create === "group"
485
+ ? party.newGroupKind({ package: p.deployment.protocol.misoParty })
486
+ : party.newIndividualKind({ package: p.deployment.protocol.misoParty }));
487
+ const created = tx.add(party._new({
488
+ package: p.deployment.protocol.misoParty,
489
+ arguments: [kind, node.name],
490
+ }));
491
+ const parts = {
492
+ party: requiredCommandResult(created, 0, "party::new"),
493
+ adminCap: requiredCommandResult(created, 1, "party::new"),
494
+ };
495
+ createdParties.set(node.ref, parts);
496
+ parties.set(node.ref, parts.party);
497
+ }
498
+
499
+ const compositions: WorkParts[] = p.compositions.map((node) => {
500
+ const created = tx.add(composition._new({
501
+ package: p.deployment.protocol.miso,
502
+ typeArguments: [node.shareType],
503
+ arguments: [node.title, node.royaltyRateBps, node.shareCurrencyId, node.shareTreasuryCapId],
504
+ }));
505
+ return {
506
+ work: requiredCommandResult(created, 0, "composition::new"),
507
+ adminCap: requiredCommandResult(created, 1, "composition::new"),
508
+ balance: requiredCommandResult(created, 2, "composition::new"),
509
+ };
510
+ });
511
+
512
+ const recordings: WorkParts[] = p.recordings.map((node) => {
513
+ const parent = node.parentCompositionIndex !== undefined
514
+ ? requiredAt(compositions, node.parentCompositionIndex, "parent composition").work
515
+ : tx.object(node.parentCompositionId);
516
+ const created = tx.add(recording._new({
517
+ package: p.deployment.protocol.miso,
518
+ typeArguments: [node.shareType, node.compositionShareType],
519
+ arguments: [parent, node.shareCurrencyId, node.shareTreasuryCapId],
520
+ }));
521
+ return {
522
+ work: requiredCommandResult(created, 0, "recording::new"),
523
+ adminCap: requiredCommandResult(created, 1, "recording::new"),
524
+ balance: requiredCommandResult(created, 2, "recording::new"),
525
+ };
526
+ });
527
+
528
+ let releaseObject: TransactionObjectArgument | undefined;
529
+ let releaseAdminCap: TransactionObjectArgument | undefined;
530
+ if (p.release) {
531
+ const ids = p.release.tracks.map((node) => "recordingIndex" in node
532
+ ? tx.moveCall({
533
+ target: "0x2::object::id",
534
+ typeArguments: [`${p.deployment.protocol.miso}::recording::Recording<${p.recordings[node.recordingIndex]!.shareType},${p.recordings[node.recordingIndex]!.compositionShareType}>`],
535
+ arguments: [requiredAt(recordings, node.recordingIndex, "fresh recording").work],
536
+ })
537
+ : tx.pure.id(node.recordingId));
538
+ const targetReleaseId = tx.moveCall({
539
+ target: `${p.deployment.protocol.miso}::release::derive_target_release_id`,
540
+ arguments: [
541
+ tx.object(p.deployment.objects.releaseRegistry),
542
+ tx.makeMoveVec({ type: "0x2::object::ID", elements: ids }),
543
+ tx.makeMoveVec({ type: "u64", elements: p.release.tracks.map((node) => tx.pure.u64(node.splitBps)) }),
544
+ tx.pure.u256(BigInt(p.release.nonce)),
545
+ ],
546
+ });
547
+ const tracks = p.release.tracks.map((node) => {
548
+ if ("recordingIndex" in node) {
549
+ const recNode = requiredAt(p.recordings, node.recordingIndex, "fresh recording");
550
+ const rec = requiredAt(recordings, node.recordingIndex, "fresh recording");
551
+ return tx.add(track._new({
552
+ package: p.deployment.protocol.miso,
553
+ typeArguments: [recNode.shareType, recNode.compositionShareType],
554
+ arguments: [rec.adminCap, rec.work, targetReleaseId, node.splitBps],
555
+ }));
556
+ }
557
+ return invokeWithAdminCap(tx, node.authority, {
558
+ target: `${p.deployment.protocol.miso}::track::new`,
559
+ typeArguments: [node.recordingShareType, node.compositionShareType],
560
+ arguments: [tx.object(node.recordingId), targetReleaseId, tx.pure.u16(node.splitBps)],
561
+ adminCapIndex: 0,
562
+ });
563
+ });
564
+ const created = tx.moveCall({
565
+ target: `${p.deployment.protocol.miso}::release::new`,
566
+ arguments: [
567
+ tx.object(p.deployment.objects.releaseRegistry),
568
+ tx.pure.string(p.release.title),
569
+ tx.makeMoveVec({ type: `${p.deployment.protocol.miso}::track::Track`, elements: tracks }),
570
+ tx.pure.u256(BigInt(p.release.nonce)),
571
+ ],
572
+ });
573
+ releaseObject = requiredCommandResult(created, 0, "release::new");
574
+ releaseAdminCap = requiredCommandResult(created, 1, "release::new");
575
+ }
576
+
577
+ p.compositions.forEach((node, index) => {
578
+ const parts = requiredAt(compositions, index, "composition");
579
+ for (const credit of node.credits ?? []) {
580
+ attachCompositionCredit({
581
+ compositionId: parts.work,
582
+ authority: directAdminCap(parts.adminCap),
583
+ partyId: partyByRef(parties, credit.party),
584
+ displayName: credit.displayName,
585
+ roles: credit.roles,
586
+ compositionShareType: node.shareType,
587
+ compositionCreditsPackageId: p.deployment.packages.compositionCredits,
588
+ misoCreditPackageId: p.deployment.packages.credit,
589
+ })(tx);
590
+ }
591
+ });
592
+
593
+ p.recordings.forEach((node, index) => {
594
+ const parts = requiredAt(recordings, index, "recording");
595
+ const authority = directAdminCap(parts.adminCap);
596
+ for (const credit of node.credits ?? []) {
597
+ const target = partyByRef(parties, credit.party);
598
+ attachRecordingCredit({
599
+ recordingId: parts.work, authority, partyId: target,
600
+ displayName: credit.displayName, roles: credit.roles,
601
+ recordingShareType: node.shareType,
602
+ compositionShareType: node.compositionShareType,
603
+ recordingCreditsPackageId: p.deployment.packages.recordingCredits,
604
+ misoCreditPackageId: p.deployment.packages.credit,
605
+ })(tx);
606
+ if (credit.primaryArtist) {
607
+ addRecordingPrimaryArtist({
608
+ recordingId: parts.work, authority, partyId: target,
609
+ recordingShareType: node.shareType,
610
+ compositionShareType: node.compositionShareType,
611
+ recordingCreditsPackageId: p.deployment.packages.recordingCredits,
612
+ })(tx);
613
+ }
614
+ }
615
+ if (node.advisory) setRecordingAdvisory({
616
+ recordingId: parts.work, authority,
617
+ recordingShareType: node.shareType,
618
+ compositionShareType: node.compositionShareType,
619
+ recordingAdvisoryPackageId: p.deployment.packages.recordingAdvisory,
620
+ rating: node.advisory,
621
+ })(tx);
622
+ if (node.languages?.kind === "instrumental") setRecordingInstrumental({
623
+ recordingId: parts.work, authority,
624
+ recordingShareType: node.shareType,
625
+ compositionShareType: node.compositionShareType,
626
+ recordingLanguagePackageId: p.deployment.packages.recordingLanguage,
627
+ })(tx);
628
+ if (node.languages?.kind === "languages") setRecordingLanguages({
629
+ recordingId: parts.work, authority,
630
+ recordingShareType: node.shareType,
631
+ compositionShareType: node.compositionShareType,
632
+ recordingLanguagePackageId: p.deployment.packages.recordingLanguage,
633
+ languages: languageVector(tx, p, node.languages.codes),
634
+ })(tx);
635
+ if (node.masterReferenceBlobId !== undefined) setRecordingMasterReference({
636
+ recordingId: parts.work, authority,
637
+ recordingShareType: node.shareType,
638
+ compositionShareType: node.compositionShareType,
639
+ recordingMasterReferencePackageId: p.deployment.packages.recordingMasterReference,
640
+ reference: walrusBlob(tx, p, node.masterReferenceBlobId),
641
+ })(tx);
642
+ if (node.previewBlobId !== undefined) setRecordingPreview({
643
+ recordingId: parts.work, authority,
644
+ recordingShareType: node.shareType,
645
+ compositionShareType: node.compositionShareType,
646
+ recordingPreviewPackageId: p.deployment.packages.recordingPreview,
647
+ reference: walrusBlob(tx, p, node.previewBlobId),
648
+ })(tx);
649
+ });
650
+
651
+ if (p.release && releaseObject && releaseAdminCap) {
652
+ const authority = directAdminCap(releaseAdminCap);
653
+ for (const credit of p.release.credits ?? []) addReleaseCredit({
654
+ releaseId: releaseObject, authority,
655
+ partyId: partyByRef(parties, credit.party),
656
+ displayName: credit.displayName, role: credit.role,
657
+ releaseCreditsPackageId: p.deployment.packages.releaseCredits,
658
+ misoCreditPackageId: p.deployment.packages.credit,
659
+ })(tx);
660
+ if (p.release.kind) setReleaseKind({
661
+ releaseId: releaseObject, authority, kind: p.release.kind,
662
+ releaseKindPackageId: p.deployment.packages.releaseKind,
663
+ })(tx);
664
+ if (p.release.description) setReleaseDescription({
665
+ releaseId: releaseObject, authority, description: p.release.description,
666
+ releaseDescriptionPackageId: p.deployment.packages.releaseDescription,
667
+ })(tx);
668
+ if (p.release.genres) setReleaseGenres({
669
+ releaseId: releaseObject, authority,
670
+ primaryGenreId: p.release.genres.primaryGenreId,
671
+ secondaryGenreIds: p.release.genres.secondaryGenreIds ?? [],
672
+ trackPrimaryGenres: p.release.genres.tracks ?? [],
673
+ releaseGenrePackageId: p.deployment.packages.releaseGenre,
674
+ })(tx);
675
+ if (p.release.dspLinks) setReleaseDspLinks({
676
+ releaseId: releaseObject, authority,
677
+ releaseLinks: p.release.dspLinks.release ?? [],
678
+ trackLinks: p.release.dspLinks.tracks ?? [],
679
+ releaseDspLinkPackageId: p.deployment.packages.releaseDspLink,
680
+ })(tx);
681
+ if (p.release.cover?.stillBlobId !== undefined) setReleaseCover({
682
+ releaseId: releaseObject, authority,
683
+ stillBlobId: p.release.cover.stillBlobId,
684
+ animatedBlobId: p.release.cover.animatedBlobId,
685
+ coverArtPackageId: p.deployment.packages.coverArt,
686
+ releaseCoverArtPackageId: p.deployment.packages.releaseCoverArt,
687
+ oriPackageId: p.deployment.packages.ori,
688
+ })(tx);
689
+ for (const cover of p.release.cover?.tracks ?? []) setReleaseTrackCover({
690
+ releaseId: releaseObject, authority,
691
+ trackIndex: cover.trackIndex,
692
+ stillBlobId: cover.stillBlobId,
693
+ animatedBlobId: cover.animatedBlobId,
694
+ coverArtPackageId: p.deployment.packages.coverArt,
695
+ releaseCoverArtPackageId: p.deployment.packages.releaseCoverArt,
696
+ oriPackageId: p.deployment.packages.ori,
697
+ })(tx);
698
+ }
699
+
700
+ let pressingParts: { pressing: TransactionObjectArgument; adminCap: TransactionObjectArgument } | undefined;
701
+ if (p.pressing && releaseObject && releaseAdminCap) {
702
+ const created = tx.add(pressingContract._new({
703
+ package: p.deployment.packages.pressing,
704
+ arguments: [
705
+ releaseObject,
706
+ releaseAdminCap,
707
+ pressingState(tx, p, p.pressing.state ?? { kind: "active" }),
708
+ ],
709
+ }));
710
+ pressingParts = {
711
+ pressing: requiredCommandResult(created, 0, "pressing::new"),
712
+ adminCap: requiredCommandResult(created, 1, "pressing::new"),
713
+ };
714
+ for (const listing of p.pressing.listings) {
715
+ tx.add(listingContract._new({
716
+ package: p.deployment.packages.pressing,
717
+ typeArguments: [listing.currencyType],
718
+ arguments: [
719
+ pressingParts.pressing,
720
+ pressingParts.adminCap,
721
+ listingPrice(tx, p, listing.price),
722
+ tx.add(listing.enabled === false
723
+ ? listingContract.newDisabledState({ package: p.deployment.packages.pressing })
724
+ : listingContract.newEnabledState({ package: p.deployment.packages.pressing })),
725
+ ],
726
+ }));
727
+ }
728
+ }
729
+
730
+ p.compositions.forEach((node, index) => publishComposition(tx, p, node, requiredAt(compositions, index, "composition")));
731
+ p.recordings.forEach((node, index) => publishRecording(tx, p, node, requiredAt(recordings, index, "recording")));
732
+ if (p.release && releaseObject && releaseAdminCap) {
733
+ publishReleaseObject(tx, p, p.release, releaseObject, releaseAdminCap);
734
+ }
735
+
736
+ for (const node of p.parties) {
737
+ if ("id" in node) continue;
738
+ const parts = createdParties.get(node.ref)!;
739
+ tx.add(party.share({
740
+ package: p.deployment.protocol.misoParty,
741
+ arguments: [parts.party, parts.adminCap],
742
+ }));
743
+ disposeNewAdminCap(tx, parts.adminCap, custody(
744
+ p,
745
+ node.custody,
746
+ partyCapType(p),
747
+ node.custody.kind === "vault" && node.installWallet !== false
748
+ ? (vault, vaultAdminCap) => installPartyWalletPlugin(tx, {
749
+ vault, vaultAdminCap,
750
+ pluginPackageId: p.deployment.packages.partyWalletPlugin,
751
+ })
752
+ : undefined,
753
+ ));
754
+ }
755
+
756
+ if (p.pressing && pressingParts) {
757
+ tx.add(pressingContract.share({
758
+ package: p.deployment.packages.pressing,
759
+ arguments: [pressingParts.pressing],
760
+ }));
761
+ disposeNewAdminCap(tx, pressingParts.adminCap, custody(
762
+ p, p.pressing.custody, pressingCapType(p),
763
+ ));
764
+ }
765
+ };
766
+ }
767
+
768
+ export interface AtomicPublicationInspection {
769
+ commands: number;
770
+ inputs: number;
771
+ }
772
+
773
+ /** Assemble without RPC access so callers can fail before publishing share packages. */
774
+ export function inspectAtomicPublication(p: AtomicPublicationParams): AtomicPublicationInspection {
775
+ const tx = new Transaction();
776
+ publishAtomicCatalog(p)(tx);
777
+ const data = tx.getData();
778
+ return { commands: data.commands.length, inputs: data.inputs.length };
779
+ }
780
+
781
+ export function assertAtomicPublicationBounds(p: AtomicPublicationParams): AtomicPublicationInspection {
782
+ const inspection = inspectAtomicPublication(p);
783
+ if (inspection.commands > MAX_ATOMIC_PUBLICATION_COMMANDS) {
784
+ throw new Error(
785
+ `Atomic publication requires ${inspection.commands} commands; the SDK safety cap is ` +
786
+ `${MAX_ATOMIC_PUBLICATION_COMMANDS} (Sui permits at most 1024).`,
787
+ );
788
+ }
789
+ if (inspection.inputs > MAX_ATOMIC_PUBLICATION_INPUTS) {
790
+ throw new Error(
791
+ `Atomic publication requires ${inspection.inputs} inputs; Sui permits at most ` +
792
+ `${MAX_ATOMIC_PUBLICATION_INPUTS}.`,
793
+ );
794
+ }
795
+ return inspection;
796
+ }
797
+
798
+ export type PublicationAuthorityOut =
799
+ | { kind: "direct"; adminCapId: string }
800
+ | { kind: "vault"; vaultId: string; vaultAdminCapId: string; capType: string; vaultPackageId: string };
801
+
802
+ export interface AtomicPublicationResult {
803
+ digest: string;
804
+ gasUsed: number;
805
+ parties: Record<string, { id: string; adminCapId: string; created: boolean; authority?: PublicationAuthorityOut }>;
806
+ compositions: Record<string, { id: string; adminCapId: string; shareType: string; authority: PublicationAuthorityOut; royaltyPoolId?: string }>;
807
+ recordings: Record<string, { id: string; adminCapId: string; shareType: string; compositionShareType: string; authority: PublicationAuthorityOut; royaltyPoolId?: string }>;
808
+ release?: { id: string; adminCapId: string; authority: PublicationAuthorityOut };
809
+ pressing?: { id: string; adminCapId: string; authority: PublicationAuthorityOut };
810
+ }
811
+
812
+ function compactType(type: string): string {
813
+ return type.replace(/\s+/g, "");
814
+ }
815
+
816
+ function findByShareType(
817
+ created: { objectId: string; objectType: string }[],
818
+ shareType: string,
819
+ description: string,
820
+ ): string {
821
+ const wanted = compactType(shareType);
822
+ const matches = created.filter((item) => compactType(item.objectType).includes(wanted));
823
+ if (matches.length !== 1) throw new Error(`Expected one ${description} for ${shareType}; found ${matches.length}`);
824
+ return matches[0]!.objectId;
825
+ }
826
+
827
+ function vaultsByWrappedCap(result: PlatformExecResult) {
828
+ const out = new Map<string, { vaultId: string; vaultAdminCapId: string }>();
829
+ for (const event of result.events) {
830
+ if (!event.eventType.includes("::vault::VaultCreatedEvent<")) continue;
831
+ const parsed = parseVaultCreatedEvent(event.bcs);
832
+ out.set(parsed.wrapped_cap_id, {
833
+ vaultId: parsed.vault_id,
834
+ vaultAdminCapId: parsed.vault_admin_cap_id,
835
+ });
836
+ }
837
+ return out;
838
+ }
839
+
840
+ function authorityOut(
841
+ result: PlatformExecResult,
842
+ vaults: ReturnType<typeof vaultsByWrappedCap>,
843
+ selected: PublicationCustody,
844
+ adminCapId: string,
845
+ capType: string,
846
+ vaultPackageId: string,
847
+ ): PublicationAuthorityOut {
848
+ if (selected.kind === "direct") return { kind: "direct", adminCapId };
849
+ const found = vaults.get(adminCapId);
850
+ if (!found) throw new Error(`No VaultCreatedEvent wrapped admin cap ${adminCapId} in ${result.digest}`);
851
+ return { kind: "vault", ...found, capType, vaultPackageId };
852
+ }
853
+
854
+ /** Parse one atomic publication using type identity plus lifecycle event payloads. */
855
+ export function parseAtomicPublicationResult(
856
+ p: AtomicPublicationParams,
857
+ result: PlatformExecResult,
858
+ ): AtomicPublicationResult {
859
+ const vaults = vaultsByWrappedCap(result);
860
+ const createdCompositions = allCreatedByType(result, "::composition::Composition<");
861
+ const createdRecordings = allCreatedByType(result, "::recording::Recording<");
862
+ const createdPools = allCreatedByType(result, "::pool::RoyaltyPool<");
863
+
864
+ const createdPartyNodes = p.parties.filter((node): node is Extract<PublicationParty, { create: string }> => "create" in node);
865
+ const partyEvents = result.events.filter((event) => event.eventType.endsWith("::party::PartyCreatedEvent"));
866
+ if (partyEvents.length !== createdPartyNodes.length) {
867
+ throw new Error(`Expected ${createdPartyNodes.length} PartyCreatedEvent values; found ${partyEvents.length}`);
868
+ }
869
+ const parties: AtomicPublicationResult["parties"] = {};
870
+ for (const node of p.parties) {
871
+ if ("id" in node) {
872
+ parties[node.ref] = {
873
+ id: node.id,
874
+ adminCapId: derivePartyAdminCapId(node.id, p.deployment.protocol.misoParty),
875
+ created: false,
876
+ };
877
+ }
878
+ }
879
+ createdPartyNodes.forEach((node, index) => {
880
+ const event = partyEvents[index]!;
881
+ const parsed = protocolContracts.party.PartyCreatedEvent.parse(event.bcs);
882
+ const adminCapId = derivePartyAdminCapId(parsed.party_id, p.deployment.protocol.misoParty);
883
+ parties[node.ref] = {
884
+ id: parsed.party_id,
885
+ adminCapId,
886
+ created: true,
887
+ authority: authorityOut(result, vaults, node.custody, adminCapId, partyCapType(p), p.deployment.packages.vault),
888
+ };
889
+ });
890
+
891
+ const compositions: AtomicPublicationResult["compositions"] = {};
892
+ p.compositions.forEach((node) => {
893
+ const id = findByShareType(createdCompositions, node.shareType, "Composition");
894
+ const adminCapId = deriveCompositionAdminCapId(id, p.deployment.protocol.miso);
895
+ compositions[node.ref] = {
896
+ id,
897
+ adminCapId,
898
+ shareType: node.shareType,
899
+ authority: authorityOut(result, vaults, node.custody, adminCapId, compositionCapType(p, node.shareType), p.deployment.packages.vault),
900
+ royaltyPoolId: node.royaltyPool ? findByShareType(createdPools, node.shareType, "Composition RoyaltyPool") : undefined,
901
+ };
902
+ });
903
+
904
+ const recordings: AtomicPublicationResult["recordings"] = {};
905
+ p.recordings.forEach((node) => {
906
+ const id = findByShareType(createdRecordings, node.shareType, "Recording");
907
+ const adminCapId = deriveRecordingAdminCapId(id, p.deployment.protocol.miso);
908
+ recordings[node.ref] = {
909
+ id,
910
+ adminCapId,
911
+ shareType: node.shareType,
912
+ compositionShareType: node.compositionShareType,
913
+ authority: authorityOut(result, vaults, node.custody, adminCapId, recordingCapType(p, node.shareType), p.deployment.packages.vault),
914
+ royaltyPoolId: node.royaltyPool ? findByShareType(createdPools, node.shareType, "Recording RoyaltyPool") : undefined,
915
+ };
916
+ });
917
+
918
+ let releaseOut: AtomicPublicationResult["release"];
919
+ if (p.release) {
920
+ const id = createdByExactType(result, `${p.deployment.protocol.miso}::release::Release`);
921
+ const adminCapId = deriveReleaseAdminCapId(id, p.deployment.protocol.miso);
922
+ releaseOut = {
923
+ id,
924
+ adminCapId,
925
+ authority: authorityOut(result, vaults, p.release.custody, adminCapId, releaseCapType(p), p.deployment.packages.vault),
926
+ };
927
+ }
928
+
929
+ let pressingOut: AtomicPublicationResult["pressing"];
930
+ if (p.pressing && releaseOut) {
931
+ const id = derivePressingId(releaseOut.id, p.deployment.packages.pressing);
932
+ const adminCapId = derivePressingAdminCapId(id, p.deployment.packages.pressing);
933
+ pressingOut = {
934
+ id,
935
+ adminCapId,
936
+ authority: authorityOut(result, vaults, p.pressing.custody, adminCapId, pressingCapType(p), p.deployment.packages.vault),
937
+ };
938
+ }
939
+
940
+ return {
941
+ digest: result.digest,
942
+ gasUsed: result.gasUsed,
943
+ parties,
944
+ compositions,
945
+ recordings,
946
+ release: releaseOut,
947
+ pressing: pressingOut,
948
+ };
949
+ }