@misofm/platform 0.0.0 → 0.19.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 +459 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +783 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/contracts.d.ts +33 -0
  16. package/dist/contracts.d.ts.map +1 -0
  17. package/dist/contracts.js +48 -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 +221 -0
  28. package/dist/deployments.d.ts.map +1 -0
  29. package/dist/deployments.js +176 -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 +215 -0
  52. package/dist/publication.d.ts.map +1 -0
  53. package/dist/publication.js +864 -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 +58 -0
  108. package/dist/recording-extensions.d.ts.map +1 -0
  109. package/dist/recording-extensions.js +148 -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 +1310 -0
  139. package/src/contracts.ts +53 -0
  140. package/src/cover.ts +240 -0
  141. package/src/credits.ts +906 -0
  142. package/src/deployments.ts +354 -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 +1205 -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 +241 -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,542 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Transaction builders follow the Sui SDK thunk pattern: a builder returns a
5
+ // `(tx: Transaction) => …` thunk that adds commands to a caller-owned
6
+ // `Transaction`, so a platform call composes with protocol calls and anything
7
+ // else in the same PTB.
8
+ //
9
+ // This module holds the OPINIONATED publish flows. `@misofm/protocol` does the
10
+ // minimum the Move semantics FORCE and returns anything a caller could
11
+ // legitimately route elsewhere; deciding where those values GO is the
12
+ // platform-layer call that lives here:
13
+ //
14
+ // - minting a work's share supply is a protocol primitive
15
+ // (`createComposition`/`createRecording`) — allocating it to raw address
16
+ // balances or composable Stake objects, publishing (sharing) the work, and
17
+ // transferring its admin cap is an opinion about economics;
18
+ // - minting a release is a platform primitive: core's `release::new` takes an
19
+ // unconstructible PTB `&mut UID`, so core `release::new(registry, …)` is the
20
+ // canonical client path. Choosing a track-assembly strategy, publishing
21
+ // (sharing) it, and picking a recipient for its `ReleaseAdminCap` is the
22
+ // opinion, so `finalizeRelease` / `publishRelease` live here.
23
+ //
24
+ // Every `finalize*` here MUST run in the same PTB as the `create*` that produced
25
+ // its parts — `Composition`, `Recording`, and `Release` are all `key`-only with
26
+ // no `drop`, so none can outlive its creating transaction. That is a same-PTB
27
+ // constraint, not a same-function one, which is exactly what makes this split
28
+ // possible.
29
+ //
30
+ // Composes with `@misofm/protocol`'s calls in the same PTB (the
31
+ // transaction-thunk composition pattern, just crossing a package boundary now).
32
+
33
+ import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions";
34
+ import {
35
+ contracts,
36
+ createComposition,
37
+ createRecording,
38
+ type CompositionParts,
39
+ type CreateCompositionParams,
40
+ type CreateRecordingParams,
41
+ type RecordingParts,
42
+ type ShareCurrencyBinding,
43
+ type TxThunk,
44
+ } from "@misofm/protocol";
45
+ import { asU64, directAdminCap, disposeNewAdminCap, invokeWithAdminCap, type AdminCapAuthority, type AdminCapCustody, type U64Input } from "./vault.ts";
46
+ import * as royaltyPool from "@misofm/protocol/contracts/royalty_pool/pool";
47
+ import * as royaltyPoolStake from "@misofm/protocol/contracts/royalty_pool/stake";
48
+
49
+ const { track, release } = contracts;
50
+
51
+ interface ReleaseParts {
52
+ release: TransactionObjectArgument;
53
+ adminCap: TransactionObjectArgument;
54
+ }
55
+
56
+ // `TxThunk` is the protocol SDK's type, re-exported so platform consumers get
57
+ // the same nominal shape rather than a structurally-identical twin.
58
+ export type { TxThunk };
59
+
60
+ /** New custody is explicit; the legacy address shape remains mutually exclusive. */
61
+ export type AdminCustodyInput =
62
+ | { readonly adminCustody: AdminCapCustody; readonly adminAddress?: never }
63
+ | { readonly adminCustody?: never; readonly adminAddress: string };
64
+ export type RecordingAuthorityInput =
65
+ | { readonly recordingAuthority: AdminCapAuthority; readonly recordingAdminCapId?: never }
66
+ | { readonly recordingAuthority?: never; readonly recordingAdminCapId: string };
67
+
68
+ export function custodyOf(input: AdminCustodyInput): AdminCapCustody {
69
+ if (input.adminCustody !== undefined) return input.adminCustody;
70
+ if (input.adminAddress !== undefined) return { kind: "direct", owner: input.adminAddress };
71
+ throw new Error("admin custody is required");
72
+ }
73
+ export function recordingAuthorityOf(input: RecordingAuthorityInput): AdminCapAuthority {
74
+ if (input.recordingAuthority !== undefined) return input.recordingAuthority;
75
+ if (input.recordingAdminCapId !== undefined) return directAdminCap(input.recordingAdminCapId);
76
+ throw new Error("recording authority is required");
77
+ }
78
+
79
+ /** Reject a malformed multi-result command before it becomes a PTB argument. */
80
+ export function requiredCommandResult<T>(
81
+ result: { readonly [index: number]: T | undefined },
82
+ index: number,
83
+ command: string,
84
+ ): T {
85
+ const value = result[index];
86
+ if (value === undefined) {
87
+ throw new Error(`${command} did not return result ${index}`);
88
+ }
89
+ return value;
90
+ }
91
+
92
+ // ============================================================================
93
+ // Share dispersal (minato)
94
+ // ============================================================================
95
+
96
+ export interface ShareRecipient {
97
+ address: string;
98
+ value: U64Input;
99
+ }
100
+
101
+ export interface CreateShareStakeParams {
102
+ readonly balance: TransactionObjectArgument;
103
+ readonly shareType: string;
104
+ readonly value: U64Input;
105
+ readonly royaltyPoolPackageId: string;
106
+ }
107
+
108
+ /** Split shares from a Balance and return a fresh, unregistered Stake object. */
109
+ export function createShareStake(
110
+ tx: Transaction,
111
+ params: CreateShareStakeParams,
112
+ ): TransactionObjectArgument {
113
+ const value = asU64("share stake value", params.value);
114
+ if (value === 0n) throw new Error("share stake value must be greater than zero");
115
+ const stakeBalance = tx.moveCall({
116
+ target: "0x2::balance::split",
117
+ typeArguments: [params.shareType],
118
+ arguments: [params.balance, tx.pure.u64(value)],
119
+ });
120
+ return tx.add(
121
+ royaltyPoolStake._new({
122
+ package: params.royaltyPoolPackageId,
123
+ typeArguments: [params.shareType],
124
+ arguments: [stakeBalance],
125
+ }),
126
+ );
127
+ }
128
+
129
+ export interface RegisterShareStakeParams {
130
+ readonly stake: TransactionObjectArgument;
131
+ readonly pool: TransactionObjectArgument;
132
+ readonly shareType: string;
133
+ readonly currencyType: string;
134
+ readonly royaltyPoolPackageId: string;
135
+ }
136
+
137
+ /** Register an existing Stake with one RoyaltyPool. */
138
+ export function registerShareStake(
139
+ tx: Transaction,
140
+ params: RegisterShareStakeParams,
141
+ ): void {
142
+ tx.add(
143
+ royaltyPool.registerStake({
144
+ package: params.royaltyPoolPackageId,
145
+ typeArguments: [params.shareType, params.currencyType],
146
+ arguments: [params.pool, params.stake],
147
+ }),
148
+ );
149
+ }
150
+
151
+ export interface CreateShareStakesParams {
152
+ readonly balance: TransactionObjectArgument;
153
+ readonly shareType: string;
154
+ readonly recipients: readonly ShareRecipient[];
155
+ readonly royaltyPoolPackageId: string;
156
+ }
157
+
158
+ /**
159
+ * Convert an entire share Balance into one unregistered Stake per recipient.
160
+ * The emptied Balance is destroyed, so recipient values must consume it exactly.
161
+ * Returned stakes remain caller-controlled for optional registration and transfer.
162
+ */
163
+ export function createShareStakes(
164
+ tx: Transaction,
165
+ params: CreateShareStakesParams,
166
+ ): TransactionObjectArgument[] {
167
+ if (params.recipients.length === 0) {
168
+ throw new Error("share stake recipients must not be empty");
169
+ }
170
+ const values = params.recipients.map((recipient) => {
171
+ const value = asU64("share stake value", recipient.value);
172
+ if (value === 0n) throw new Error("share stake value must be greater than zero");
173
+ return value;
174
+ });
175
+ const stakes = params.recipients.map((recipient, index) => createShareStake(tx, {
176
+ balance: params.balance,
177
+ shareType: params.shareType,
178
+ value: values[index]!,
179
+ royaltyPoolPackageId: params.royaltyPoolPackageId,
180
+ }));
181
+ tx.moveCall({
182
+ target: "0x2::balance::destroy_zero",
183
+ typeArguments: [params.shareType],
184
+ arguments: [params.balance],
185
+ });
186
+ return stakes;
187
+ }
188
+
189
+ export interface ShareRoyaltyPoolParams {
190
+ readonly pool: TransactionObjectArgument;
191
+ readonly shareType: string;
192
+ readonly currencyType: string;
193
+ readonly royaltyPoolPackageId: string;
194
+ }
195
+
196
+ /** Consume an unshared RoyaltyPool and make it globally accessible. */
197
+ export function shareRoyaltyPool(tx: Transaction, params: ShareRoyaltyPoolParams): void {
198
+ tx.add(
199
+ royaltyPool.share({
200
+ package: params.royaltyPoolPackageId,
201
+ typeArguments: [params.shareType, params.currencyType],
202
+ arguments: [params.pool],
203
+ }),
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Splits a share `Balance` across `recipients` via `minato::disperse_balance`, then
209
+ * destroys the emptied balance. Exposed for consumers assembling custom share
210
+ * distributions in their own PTBs.
211
+ */
212
+ export function disperseShares(
213
+ tx: Transaction,
214
+ minatoPackageId: string,
215
+ shareType: string,
216
+ balance: TransactionObjectArgument,
217
+ recipients: ShareRecipient[],
218
+ ) {
219
+ tx.moveCall({
220
+ target: `${minatoPackageId}::minato::disperse_balance`,
221
+ typeArguments: [shareType],
222
+ arguments: [
223
+ balance,
224
+ tx.makeMoveVec({ type: "u64", elements: recipients.map((r) => tx.pure.u64(asU64("share recipient value", r.value)))}),
225
+ tx.makeMoveVec({ type: "address", elements: recipients.map((r) => tx.pure.address(r.address)) }),
226
+ ],
227
+ });
228
+ tx.moveCall({ target: "0x2::balance::destroy_zero", typeArguments: [shareType], arguments: [balance] });
229
+ }
230
+
231
+ // ============================================================================
232
+ // Share currency (external share / framework)
233
+ // ============================================================================
234
+
235
+ export interface PackageBytecode {
236
+ modules: string[];
237
+ dependencies: string[];
238
+ digest: number[];
239
+ }
240
+
241
+ /** Publish a share package and permanently destroy its UpgradeCap in the same PTB. */
242
+ export function publishShareCurrency(bytecode: PackageBytecode): TxThunk {
243
+ return (tx) => {
244
+ const upgradeCap = tx.publish(bytecode);
245
+ tx.moveCall({ target: "0x2::package::make_immutable", arguments: [upgradeCap] });
246
+ };
247
+ }
248
+
249
+ const SUI_COIN_REGISTRY_ID = "0xc";
250
+
251
+ export interface InitializeShareCurrencyParams {
252
+ shareCurrencyPackageId: string;
253
+ name: string;
254
+ description: string;
255
+ iconUrl: string;
256
+ treasuryCapRecipient: string;
257
+ }
258
+
259
+ export function initializeShareCurrency(params: InitializeShareCurrencyParams): TxThunk {
260
+ const { shareCurrencyPackageId, name, description, iconUrl, treasuryCapRecipient } = params;
261
+ return (tx) => {
262
+ const treasuryCap = tx.moveCall({
263
+ target: `${shareCurrencyPackageId}::share::initialize`,
264
+ arguments: [tx.pure.string(name), tx.pure.string(description), tx.pure.string(iconUrl), tx.object(SUI_COIN_REGISTRY_ID)],
265
+ });
266
+ tx.transferObjects([treasuryCap], treasuryCapRecipient);
267
+ };
268
+ }
269
+
270
+ // ============================================================================
271
+ // Composition
272
+ // ============================================================================
273
+
274
+ interface FinalizeCompositionParamsBase extends CompositionParts {
275
+ /** The composition's `share::Share` type. */
276
+ shareType: string;
277
+ shareRecipients: ShareRecipient[];
278
+ misoPackageId: string;
279
+ minatoPackageId: string;
280
+ }
281
+ export type FinalizeCompositionParams = FinalizeCompositionParamsBase & AdminCustodyInput;
282
+
283
+ /**
284
+ * The opinionated finish for a composition: disperse its share supply to
285
+ * `shareRecipients`, publish (share) it, and transfer its admin cap to
286
+ * `adminAddress`. Consumers that want different economics skip this and act on
287
+ * the `CompositionParts` from `@misofm/protocol`'s `createComposition` directly.
288
+ */
289
+ export function finalizeComposition(tx: Transaction, params: FinalizeCompositionParams): void {
290
+ disperseShares(tx, params.minatoPackageId, params.shareType, params.balance, params.shareRecipients);
291
+ tx.add(contracts.composition.publish({ package: params.misoPackageId, typeArguments: [params.shareType], arguments: [params.composition, params.adminCap] }));
292
+ disposeNewAdminCap(tx, params.adminCap, custodyOf(params));
293
+ }
294
+
295
+ interface PublishCompositionParamsBase extends ShareCurrencyBinding {
296
+ title: string;
297
+ royaltyRateBps: number;
298
+ shareRecipients: ShareRecipient[];
299
+ misoPackageId: string;
300
+ minatoPackageId: string;
301
+ }
302
+ export type PublishCompositionParams = PublishCompositionParamsBase & AdminCustodyInput;
303
+
304
+ /** Convenience: publish a composition end-to-end (createComposition → finalizeComposition). */
305
+ export function publishComposition(params: PublishCompositionParams): TxThunk {
306
+ return (tx) => {
307
+ const parts = createComposition(tx, {
308
+ shareType: params.shareType,
309
+ shareCurrencyId: params.shareCurrencyId,
310
+ shareTreasuryCapId: params.shareTreasuryCapId,
311
+ title: params.title,
312
+ royaltyRateBps: params.royaltyRateBps,
313
+ misoPackageId: params.misoPackageId,
314
+ } satisfies CreateCompositionParams);
315
+ finalizeComposition(tx, {
316
+ ...parts,
317
+ shareType: params.shareType,
318
+ shareRecipients: params.shareRecipients,
319
+ adminCustody: custodyOf(params),
320
+ misoPackageId: params.misoPackageId,
321
+ minatoPackageId: params.minatoPackageId,
322
+ });
323
+ };
324
+ }
325
+
326
+ // ============================================================================
327
+ // Recording
328
+ // ============================================================================
329
+
330
+ interface FinalizeRecordingParamsBase extends RecordingParts {
331
+ /** The recording's own `share::Share` type. */
332
+ recordingShareType: string;
333
+ /** Share type of the parent composition. */
334
+ compositionShareType: string;
335
+ shareRecipients: ShareRecipient[];
336
+ misoPackageId: string;
337
+ minatoPackageId: string;
338
+ }
339
+ export type FinalizeRecordingParams = FinalizeRecordingParamsBase & AdminCustodyInput;
340
+
341
+ /**
342
+ * The opinionated finish for a recording: publish (share) it, disperse its
343
+ * share supply, and transfer its admin cap to `adminAddress`. A recording has
344
+ * no embedded metadata to set — naming lives in the metadata extension.
345
+ */
346
+ export function finalizeRecording(tx: Transaction, params: FinalizeRecordingParams): void {
347
+ const typeArguments: [string, string] = [params.recordingShareType, params.compositionShareType];
348
+ tx.add(contracts.recording.publish({ package: params.misoPackageId, typeArguments, arguments: [params.recording, params.adminCap] }));
349
+ disperseShares(tx, params.minatoPackageId, params.recordingShareType, params.balance, params.shareRecipients);
350
+ disposeNewAdminCap(tx, params.adminCap, custodyOf(params));
351
+ }
352
+
353
+ interface PublishRecordingParamsBase extends ShareCurrencyBinding {
354
+ /**
355
+ * Parent composition, referenced as an on-chain object. Read-only at
356
+ * `recording::new` (only its royalty rate and id are read).
357
+ */
358
+ compositionId: string;
359
+ /** Share type of the parent composition (the recording's `CompositionShare` phantom). */
360
+ compositionShareType: string;
361
+ shareRecipients: ShareRecipient[];
362
+ misoPackageId: string;
363
+ minatoPackageId: string;
364
+ }
365
+ export type PublishRecordingParams = PublishRecordingParamsBase & AdminCustodyInput;
366
+
367
+ /** Convenience: publish a recording against an already-on-chain composition. */
368
+ export function publishRecording(params: PublishRecordingParams): TxThunk {
369
+ return (tx) => {
370
+ const parts = createRecording(tx, {
371
+ shareType: params.shareType,
372
+ shareCurrencyId: params.shareCurrencyId,
373
+ shareTreasuryCapId: params.shareTreasuryCapId,
374
+ compositionShareType: params.compositionShareType,
375
+ composition: tx.object(params.compositionId),
376
+ misoPackageId: params.misoPackageId,
377
+ } satisfies CreateRecordingParams);
378
+ finalizeRecording(tx, {
379
+ ...parts,
380
+ recordingShareType: params.shareType,
381
+ compositionShareType: params.compositionShareType,
382
+ shareRecipients: params.shareRecipients,
383
+ adminCustody: custodyOf(params),
384
+ misoPackageId: params.misoPackageId,
385
+ minatoPackageId: params.minatoPackageId,
386
+ });
387
+ };
388
+ }
389
+
390
+ // ============================================================================
391
+ // Composition + Recording (single PTB)
392
+ // ============================================================================
393
+
394
+ export interface PublishCompositionAndRecordingParams {
395
+ title: string;
396
+ royaltyRateBps: number;
397
+ /** Share-currency binding for the composition. */
398
+ composition: ShareCurrencyBinding & { shareRecipients: ShareRecipient[] } & AdminCustodyInput;
399
+ /** Share-currency binding for the recording. */
400
+ recording: ShareCurrencyBinding & { shareRecipients: ShareRecipient[] } & AdminCustodyInput;
401
+ misoPackageId: string;
402
+ minatoPackageId: string;
403
+ }
404
+
405
+ /**
406
+ * Publishes a composition and its recording in a single atomic PTB.
407
+ *
408
+ * The ordering is load-bearing: `recording::new` borrows the composition by
409
+ * immutable reference, so it must run while the composition is still an unshared,
410
+ * transaction-local value — i.e. AFTER `composition::new` but BEFORE
411
+ * `composition::publish` (which moves the composition into `share_object`). Hence:
412
+ * `composition::new` → `recording::new(&comp)` → finalize composition (publish) →
413
+ * finalize recording (publish).
414
+ */
415
+ export function publishCompositionAndRecording(params: PublishCompositionAndRecordingParams): TxThunk {
416
+ return (tx) => {
417
+ const comp = createComposition(tx, {
418
+ shareType: params.composition.shareType,
419
+ shareCurrencyId: params.composition.shareCurrencyId,
420
+ shareTreasuryCapId: params.composition.shareTreasuryCapId,
421
+ title: params.title,
422
+ royaltyRateBps: params.royaltyRateBps,
423
+ misoPackageId: params.misoPackageId,
424
+ } satisfies CreateCompositionParams);
425
+
426
+ // Borrow the still-unshared composition into recording::new before publishing it.
427
+ // The composition's royalty rate is immutable once set at `composition::new`,
428
+ // so `recording::new` reads exactly the rate this PTB just created it with.
429
+ const rec = createRecording(tx, {
430
+ shareType: params.recording.shareType,
431
+ shareCurrencyId: params.recording.shareCurrencyId,
432
+ shareTreasuryCapId: params.recording.shareTreasuryCapId,
433
+ compositionShareType: params.composition.shareType,
434
+ composition: comp.composition,
435
+ misoPackageId: params.misoPackageId,
436
+ } satisfies CreateRecordingParams);
437
+
438
+ finalizeComposition(tx, {
439
+ ...comp,
440
+ shareType: params.composition.shareType,
441
+ shareRecipients: params.composition.shareRecipients,
442
+ adminCustody: custodyOf(params.composition),
443
+ misoPackageId: params.misoPackageId,
444
+ minatoPackageId: params.minatoPackageId,
445
+ });
446
+
447
+ finalizeRecording(tx, {
448
+ ...rec,
449
+ recordingShareType: params.recording.shareType,
450
+ compositionShareType: params.composition.shareType,
451
+ shareRecipients: params.recording.shareRecipients,
452
+ adminCustody: custodyOf(params.recording),
453
+ misoPackageId: params.misoPackageId,
454
+ minatoPackageId: params.minatoPackageId,
455
+ });
456
+ };
457
+ }
458
+
459
+ // ============================================================================
460
+ // Release
461
+ // ============================================================================
462
+
463
+ interface FinalizeReleaseParamsBase extends ReleaseParts {
464
+ /** Explicit direct delivery or Vault custody for the ReleaseAdminCap. */
465
+ misoPackageId: string;
466
+ }
467
+ export type FinalizeReleaseParams = FinalizeReleaseParamsBase & AdminCustodyInput;
468
+
469
+ /**
470
+ * The opinionated finish for a release: publish (share) it and transfer its
471
+ * admin cap to `adminAddress`.
472
+ *
473
+ * MUST run in the same PTB as `release::new(registry, …)` that produced
474
+ * these parts —
475
+ * `Release` is `key`-only with no `drop`, so an unpublished release cannot
476
+ * outlive its transaction. Splitting create from finalize is what lets a caller
477
+ * do something else with the cap (route it to a vault, hand it to another
478
+ * package, keep it for later commands) instead of a plain address transfer.
479
+ */
480
+ export function finalizeRelease(tx: Transaction, params: FinalizeReleaseParams): void {
481
+ tx.add(release.publish({ package: params.misoPackageId, arguments: [params.release, params.adminCap] }));
482
+ disposeNewAdminCap(tx, params.adminCap, custodyOf(params));
483
+ }
484
+
485
+ interface TrackInputBase {
486
+ recordingId: string;
487
+ /** Explicit legacy direct cap or Vault custody authority for this recording. */
488
+ /** Share type of the recording (the track `RecordingShare` phantom). */
489
+ recordingShareType: string;
490
+ /** Share type of the parent composition (the track `CompositionShare` phantom). */
491
+ compositionShareType: string;
492
+ splitBps: number;
493
+ }
494
+ export type TrackInput = TrackInputBase & RecordingAuthorityInput;
495
+
496
+ interface PublishReleaseParamsBase {
497
+ title: string;
498
+ /** The ordered tracklist. Display grouping (discs/sides) is extension data. */
499
+ tracks: TrackInput[];
500
+ /** Shared core `miso::release::ReleaseRegistry` object. */
501
+ releaseRegistryId: string;
502
+ releaseId: string;
503
+ releaseNonce: string;
504
+ misoPackageId: string;
505
+ }
506
+ export type PublishReleaseParams = PublishReleaseParamsBase & AdminCustodyInput;
507
+
508
+ function buildTrackVec(
509
+ tx: Transaction,
510
+ misoPackageId: string,
511
+ trackArgs: TransactionObjectArgument[],
512
+ ) {
513
+ return tx.makeMoveVec({ type: `${misoPackageId}::track::Track`, elements: trackArgs });
514
+ }
515
+
516
+ /**
517
+ * Convenience: publish a release end-to-end, assembling its tracklist from
518
+ * recording admin caps held by the sender.
519
+ */
520
+ export function publishRelease(params: PublishReleaseParams): TxThunk {
521
+ return (tx) => {
522
+ const { misoPackageId } = params;
523
+ const trackArgs = params.tracks.map((t) => {
524
+ const typeArguments: [string, string] = [t.recordingShareType, t.compositionShareType];
525
+ return invokeWithAdminCap(tx, recordingAuthorityOf(t), {
526
+ target: `${misoPackageId}::track::new`,
527
+ typeArguments,
528
+ arguments: [tx.object(t.recordingId), tx.pure.id(params.releaseId), tx.pure.u16(t.splitBps)],
529
+ adminCapIndex: 0,
530
+ });
531
+ });
532
+ const created = tx.moveCall({
533
+ target: `${misoPackageId}::release::new`,
534
+ arguments: [tx.object(params.releaseRegistryId), tx.pure.string(params.title), buildTrackVec(tx, misoPackageId, trackArgs), tx.pure.u256(BigInt(params.releaseNonce))],
535
+ });
536
+ const parts: ReleaseParts = {
537
+ release: requiredCommandResult(created, 0, "release::new"),
538
+ adminCap: requiredCommandResult(created, 1, "release::new"),
539
+ };
540
+ finalizeRelease(tx, { ...parts, ...params, misoPackageId });
541
+ };
542
+ }