@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,297 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { ClientWithCoreApi } from "@mysten/sui/client";
5
+ import type { SuiGraphQLClient } from "@mysten/sui/graphql";
6
+ import { normalizeSuiAddress } from "@mysten/sui/utils";
7
+ import {
8
+ contracts,
9
+ extractTypeParams2,
10
+ getCompositionsByIds,
11
+ type Composition,
12
+ type Recording,
13
+ type Release,
14
+ type TrackState,
15
+ } from "@misofm/protocol";
16
+
17
+ export interface WorkShareTypes {
18
+ compositions: readonly string[];
19
+ recordings: readonly string[];
20
+ }
21
+
22
+ export interface WorkAddressesByShareType {
23
+ compositions: Partial<Record<string, string>>;
24
+ recordings: Partial<Record<string, string>>;
25
+ }
26
+
27
+ interface WorkAddressConnection {
28
+ nodes: Array<{
29
+ address: string;
30
+ asMoveObject?: {
31
+ contents?: { type?: { repr?: string } | null } | null;
32
+ } | null;
33
+ }>;
34
+ pageInfo?: { hasNextPage: boolean; endCursor: string | null };
35
+ }
36
+
37
+ /** Resolve all work share types in one aliased GraphQL request. */
38
+ export async function getWorkAddressesByShareTypes(
39
+ client: SuiGraphQLClient,
40
+ shareTypes: WorkShareTypes,
41
+ misoPackageId: string,
42
+ ): Promise<WorkAddressesByShareType> {
43
+ const compositions = [...new Set(shareTypes.compositions)];
44
+ const recordings = new Set(shareTypes.recordings);
45
+ const out: WorkAddressesByShareType = { compositions: {}, recordings: {} };
46
+ if (compositions.length === 0 && recordings.size === 0) return out;
47
+
48
+ const declarations: string[] = [];
49
+ const selections: string[] = [];
50
+ const variables: Record<string, string> = {};
51
+ compositions.forEach((shareType, index) => {
52
+ const variable = `compositionType${index}`;
53
+ declarations.push(`$${variable}: String!`);
54
+ selections.push(
55
+ `composition${index}: objects(first: 1, filter: { type: $${variable} }) { nodes { address } }`,
56
+ );
57
+ variables[variable] =
58
+ `${misoPackageId}::composition::Composition<${shareType}>`;
59
+ });
60
+ if (recordings.size > 0) {
61
+ declarations.push("$recordingType: String!");
62
+ selections.push(`recordings: objects(first: 50, filter: { type: $recordingType }) {
63
+ pageInfo { hasNextPage endCursor }
64
+ nodes { address asMoveObject { contents { type { repr } } } }
65
+ }`);
66
+ variables.recordingType = `${misoPackageId}::recording::Recording`;
67
+ }
68
+
69
+ const result = await client.query<
70
+ Record<string, WorkAddressConnection | null>,
71
+ Record<string, string>
72
+ >({
73
+ query: `query WorkAddressesByShareTypes(${declarations.join(", ")}) {
74
+ ${selections.join("\n")}
75
+ }`,
76
+ variables,
77
+ });
78
+ if (result.errors?.length) {
79
+ throw new AggregateError(
80
+ result.errors.map((error) => new Error(error.message)),
81
+ "Work type discovery failed",
82
+ );
83
+ }
84
+ compositions.forEach((shareType, index) => {
85
+ const address = result.data?.[`composition${index}`]?.nodes[0]?.address;
86
+ if (address) out.compositions[shareType] = address;
87
+ });
88
+
89
+ const readRecordingPage = (
90
+ page: WorkAddressConnection | null | undefined,
91
+ ) => {
92
+ for (const node of page?.nodes ?? []) {
93
+ const repr = node.asMoveObject?.contents?.type?.repr;
94
+ if (!repr) continue;
95
+ try {
96
+ const [shareType] = extractTypeParams2(repr);
97
+ if (recordings.has(shareType)) out.recordings[shareType] = node.address;
98
+ } catch {
99
+ // Ignore objects whose deployed type does not match the Recording ABI.
100
+ }
101
+ }
102
+ };
103
+
104
+ let page = result.data?.recordings;
105
+ readRecordingPage(page);
106
+ while (
107
+ page?.pageInfo?.hasNextPage &&
108
+ page.pageInfo.endCursor &&
109
+ Object.keys(out.recordings).length < recordings.size
110
+ ) {
111
+ const next = await client.query<
112
+ { recordings: WorkAddressConnection | null },
113
+ { recordingType: string; cursor: string }
114
+ >({
115
+ query: `query RecordingWorkAddresses($recordingType: String!, $cursor: String!) {
116
+ recordings: objects(first: 50, after: $cursor, filter: { type: $recordingType }) {
117
+ pageInfo { hasNextPage endCursor }
118
+ nodes { address asMoveObject { contents { type { repr } } } }
119
+ }
120
+ }`,
121
+ variables: {
122
+ recordingType: variables.recordingType!,
123
+ cursor: page.pageInfo.endCursor,
124
+ },
125
+ });
126
+ if (next.errors?.length) {
127
+ throw new AggregateError(
128
+ next.errors.map((error) => new Error(error.message)),
129
+ "Recording type discovery failed",
130
+ );
131
+ }
132
+ page = next.data?.recordings;
133
+ readRecordingPage(page);
134
+ }
135
+ return out;
136
+ }
137
+
138
+ export interface WorkIds {
139
+ compositions: readonly string[];
140
+ recordings: readonly string[];
141
+ releases: readonly string[];
142
+ }
143
+
144
+ export interface WorksById {
145
+ compositions: Partial<Record<string, Composition>>;
146
+ recordings: Partial<Record<string, Recording>>;
147
+ releases: Partial<Record<string, Release>>;
148
+ }
149
+
150
+ type Parsed = Record<string, any>;
151
+
152
+ function workState(value: Parsed): Composition["state"] {
153
+ return value?.$kind === "Published"
154
+ ? { type: "Published", timestampMs: Number(value.Published) }
155
+ : { type: "Initialized" };
156
+ }
157
+
158
+ function parseComposition(id: string, content: Uint8Array): Composition {
159
+ const value = contracts.composition.Composition.parse(content) as Parsed;
160
+ return {
161
+ id,
162
+ state: workState(value.state),
163
+ title: String(value.title),
164
+ royaltyRate: {
165
+ value: Number(
166
+ Array.isArray(value.royalty_rate)
167
+ ? value.royalty_rate[0]
168
+ : value.royalty_rate,
169
+ ),
170
+ },
171
+ };
172
+ }
173
+
174
+ function parseRecording(id: string, content: Uint8Array): Recording {
175
+ const value = contracts.recording.Recording.parse(content) as Parsed;
176
+ return {
177
+ id,
178
+ state: workState(value.state),
179
+ compositionId: String(value.composition_id),
180
+ };
181
+ }
182
+
183
+ export function parseReleaseObject(
184
+ id: string,
185
+ content: Uint8Array,
186
+ ): Release {
187
+ const value = contracts.release.Release.parse(content) as Parsed;
188
+ return {
189
+ id,
190
+ state: workState(value.state),
191
+ title: String(value.title),
192
+ tracks: (value.tracks ?? []).map((track: Parsed) => ({
193
+ state: (track.state?.$kind ?? "Unassigned") as TrackState,
194
+ compositionId: String(track.composition_id),
195
+ recordingId: String(track.recording_id),
196
+ splitBps: {
197
+ value: Number(
198
+ Array.isArray(track.split_bps) ? track.split_bps[0] : track.split_bps,
199
+ ),
200
+ },
201
+ })),
202
+ };
203
+ }
204
+
205
+ /** Fetch and parse heterogeneous works through one Core bulk request. */
206
+ export async function getWorksByIds(
207
+ client: ClientWithCoreApi,
208
+ ids: WorkIds,
209
+ ): Promise<WorksById> {
210
+ const kinds = new Map<string, keyof WorksById>();
211
+ for (const [kind, objectIds] of Object.entries(ids) as Array<
212
+ [keyof WorksById, readonly string[]]
213
+ >) {
214
+ for (const objectId of objectIds) {
215
+ const normalized = normalizeSuiAddress(objectId);
216
+ const previous = kinds.get(normalized);
217
+ if (previous && previous !== kind) {
218
+ throw new Error(
219
+ `Work ${normalized} was requested as both ${previous} and ${kind}`,
220
+ );
221
+ }
222
+ kinds.set(normalized, kind);
223
+ }
224
+ }
225
+ const out: WorksById = { compositions: {}, recordings: {}, releases: {} };
226
+ if (kinds.size === 0) return out;
227
+ const { objects } = await client.core.getObjects({
228
+ objectIds: [...kinds.keys()],
229
+ include: { content: true },
230
+ });
231
+ for (const object of objects) {
232
+ if (object instanceof Error || !object.content) continue;
233
+ const kind = kinds.get(normalizeSuiAddress(object.objectId));
234
+ if (kind === "compositions")
235
+ out.compositions[object.objectId] = parseComposition(
236
+ object.objectId,
237
+ object.content,
238
+ );
239
+ else if (kind === "recordings")
240
+ out.recordings[object.objectId] = parseRecording(
241
+ object.objectId,
242
+ object.content,
243
+ );
244
+ else if (kind === "releases")
245
+ out.releases[object.objectId] = parseReleaseObject(
246
+ object.objectId,
247
+ object.content,
248
+ );
249
+ }
250
+ return out;
251
+ }
252
+
253
+ /** Resolve recording titles through each recording's canonical composition type. */
254
+ export async function getRecordingTitles(
255
+ client: ClientWithCoreApi,
256
+ graphql: SuiGraphQLClient,
257
+ recordingIds: readonly string[],
258
+ misoPackageId: string,
259
+ ): Promise<Record<string, string>> {
260
+ const ids = [...new Set(recordingIds)];
261
+ if (ids.length === 0) return {};
262
+
263
+ const { objects } = await client.core.getObjects({
264
+ objectIds: ids,
265
+ include: {},
266
+ });
267
+ const titles: Record<string, string> = {};
268
+ const compositionShareByRecording: Record<string, string> = {};
269
+
270
+ for (const object of objects) {
271
+ if (object instanceof Error) continue;
272
+ const [, compositionShareType] = extractTypeParams2(object.type);
273
+ compositionShareByRecording[object.objectId] = compositionShareType;
274
+ }
275
+
276
+ const compositionShareTypes = Object.values(compositionShareByRecording);
277
+ if (compositionShareTypes.length === 0) return titles;
278
+ const addresses = await getWorkAddressesByShareTypes(
279
+ graphql,
280
+ { compositions: compositionShareTypes, recordings: [] },
281
+ misoPackageId,
282
+ );
283
+ const compositions = await getCompositionsByIds(
284
+ client,
285
+ Object.values(addresses.compositions).filter((id): id is string => !!id),
286
+ );
287
+ for (const [recordingId, shareType] of Object.entries(
288
+ compositionShareByRecording,
289
+ )) {
290
+ const compositionId = addresses.compositions[shareType];
291
+ const title = compositionId
292
+ ? compositions[compositionId]?.title
293
+ : undefined;
294
+ if (title) titles[recordingId] = title;
295
+ }
296
+ return titles;
297
+ }
@@ -0,0 +1,256 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /** Cap-authorized builders and reads for data-only Recording metadata extensions. */
5
+
6
+ import { bcs } from "@mysten/sui/bcs";
7
+ import type { ClientWithCoreApi } from "@mysten/sui/client";
8
+ import type { Transaction, TransactionArgument, TransactionObjectArgument } from "@mysten/sui/transactions";
9
+ import { deriveDynamicFieldID } from "@mysten/sui/utils";
10
+ import type { TxThunk } from "./transactions.ts";
11
+ import { invokeWithAdminCap, type AdminCapAuthority, type ObjectInput } from "./vault.ts";
12
+ import * as advisory from "@misofm/protocol/contracts/recording_advisory/recording_advisory";
13
+ import * as language from "@misofm/protocol/contracts/recording_language/recording_language";
14
+ import * as walrusData from "@misofm/protocol/contracts/recording_master_reference/deps/ori/walrus_data";
15
+ import * as masterReference from "@misofm/protocol/contracts/recording_master_reference/recording_master_reference";
16
+ import * as preview from "@misofm/protocol/contracts/recording_preview/recording_preview";
17
+ import * as streamingTranscode from "@misofm/protocol/contracts/recording_streaming_transcode/recording_streaming_transcode";
18
+
19
+ export interface RecordingExtensionTarget {
20
+ readonly recordingId: ObjectInput;
21
+ readonly authority: AdminCapAuthority;
22
+ readonly recordingShareType: string;
23
+ readonly compositionShareType: string;
24
+ }
25
+
26
+ function object(tx: Transaction, value: ObjectInput): TransactionObjectArgument {
27
+ return typeof value === "string" ? tx.object(value) : value;
28
+ }
29
+
30
+ type Rating = "Explicit" | "NotExplicit" | "Cleaned";
31
+ export interface SetRecordingAdvisoryParams extends RecordingExtensionTarget {
32
+ readonly recordingAdvisoryPackageId: string;
33
+ readonly rating: Rating;
34
+ }
35
+
36
+ export function setRecordingAdvisory(p: SetRecordingAdvisoryParams): TxThunk {
37
+ return (tx) => {
38
+ const rating = tx.add(
39
+ p.rating === "Explicit" ? advisory.explicit({ package: p.recordingAdvisoryPackageId })
40
+ : p.rating === "NotExplicit" ? advisory.notExplicit({ package: p.recordingAdvisoryPackageId })
41
+ : advisory.cleaned({ package: p.recordingAdvisoryPackageId }),
42
+ );
43
+ invokeWithAdminCap(tx, p.authority, {
44
+ target: `${p.recordingAdvisoryPackageId}::recording_advisory::set_rating`,
45
+ typeArguments: [p.recordingShareType, p.compositionShareType],
46
+ arguments: [object(tx, p.recordingId), rating],
47
+ adminCapIndex: 1,
48
+ });
49
+ };
50
+ }
51
+
52
+ export interface SetRecordingLanguagesParams extends RecordingExtensionTarget {
53
+ readonly recordingLanguagePackageId: string;
54
+ /** A vector<LanguageCode> built by the language-code package in this PTB. */
55
+ readonly languages: TransactionArgument;
56
+ }
57
+
58
+ export function setRecordingLanguages(p: SetRecordingLanguagesParams): TxThunk {
59
+ return (tx) => {
60
+ invokeWithAdminCap(tx, p.authority, {
61
+ target: `${p.recordingLanguagePackageId}::recording_language::set_languages`,
62
+ typeArguments: [p.recordingShareType, p.compositionShareType],
63
+ arguments: [object(tx, p.recordingId), p.languages],
64
+ adminCapIndex: 1,
65
+ });
66
+ };
67
+ }
68
+
69
+ export function setRecordingInstrumental(p: Omit<SetRecordingLanguagesParams, "languages">): TxThunk {
70
+ return (tx) => {
71
+ invokeWithAdminCap(tx, p.authority, {
72
+ target: `${p.recordingLanguagePackageId}::recording_language::set_instrumental`,
73
+ typeArguments: [p.recordingShareType, p.compositionShareType],
74
+ arguments: [object(tx, p.recordingId)],
75
+ adminCapIndex: 1,
76
+ });
77
+ };
78
+ }
79
+
80
+ interface RecordingWalrusReferenceParams extends RecordingExtensionTarget {
81
+ /** An ori::WalrusData value assembled in the same PTB (must be a standalone blob on chain). */
82
+ readonly reference: TransactionArgument;
83
+ }
84
+
85
+ export interface SetRecordingMasterReferenceParams extends RecordingWalrusReferenceParams {
86
+ readonly recordingMasterReferencePackageId: string;
87
+ }
88
+ export function setRecordingMasterReference(p: SetRecordingMasterReferenceParams): TxThunk {
89
+ return (tx) => {
90
+ invokeWithAdminCap(tx, p.authority, {
91
+ target: `${p.recordingMasterReferencePackageId}::recording_master_reference::set_master_reference`,
92
+ typeArguments: [p.recordingShareType, p.compositionShareType],
93
+ arguments: [object(tx, p.recordingId), p.reference],
94
+ adminCapIndex: 1,
95
+ });
96
+ };
97
+ }
98
+
99
+ export interface SetRecordingStreamingTranscodeParams extends RecordingExtensionTarget {
100
+ readonly recordingStreamingTranscodePackageId: string;
101
+ /** External `ori` package used to construct the complete Walrus Quilt reference. */
102
+ readonly oriPackageId: string;
103
+ /** Complete Walrus Quilt ID as its on-chain `u256` value. */
104
+ readonly quiltId: bigint | string;
105
+ }
106
+
107
+ /** Sets or replaces the complete streaming-transcode Quilt attached to a Recording. */
108
+ export function setRecordingStreamingTranscode(
109
+ p: SetRecordingStreamingTranscodeParams,
110
+ ): TxThunk {
111
+ return (tx) => {
112
+ const quilt = tx.moveCall({
113
+ target: `${p.oriPackageId}::data::new_quilt`,
114
+ arguments: [tx.pure.u256(p.quiltId)],
115
+ });
116
+ const transcode = tx.add(streamingTranscode._new({
117
+ package: p.recordingStreamingTranscodePackageId,
118
+ arguments: [quilt],
119
+ }));
120
+ invokeWithAdminCap(tx, p.authority, {
121
+ target: `${p.recordingStreamingTranscodePackageId}::recording_streaming_transcode::set_streaming_transcode`,
122
+ typeArguments: [p.recordingShareType, p.compositionShareType],
123
+ arguments: [object(tx, p.recordingId), transcode],
124
+ adminCapIndex: 1,
125
+ });
126
+ };
127
+ }
128
+
129
+ export type UnsetRecordingStreamingTranscodeParams = Omit<
130
+ SetRecordingStreamingTranscodeParams,
131
+ "oriPackageId" | "quiltId"
132
+ >;
133
+
134
+ /** Removes the Recording's streaming-transcode reference, if present. */
135
+ export function unsetRecordingStreamingTranscode(
136
+ p: UnsetRecordingStreamingTranscodeParams,
137
+ ): TxThunk {
138
+ return (tx) => {
139
+ invokeWithAdminCap(tx, p.authority, {
140
+ target: `${p.recordingStreamingTranscodePackageId}::recording_streaming_transcode::unset_streaming_transcode`,
141
+ typeArguments: [p.recordingShareType, p.compositionShareType],
142
+ arguments: [object(tx, p.recordingId)],
143
+ adminCapIndex: 1,
144
+ });
145
+ };
146
+ }
147
+
148
+ export interface SetRecordingPreviewParams extends RecordingWalrusReferenceParams {
149
+ readonly recordingPreviewPackageId: string;
150
+ }
151
+ export function setRecordingPreview(p: SetRecordingPreviewParams): TxThunk {
152
+ return (tx) => {
153
+ invokeWithAdminCap(tx, p.authority, {
154
+ target: `${p.recordingPreviewPackageId}::recording_preview::set_preview`,
155
+ typeArguments: [p.recordingShareType, p.compositionShareType],
156
+ arguments: [object(tx, p.recordingId), p.reference],
157
+ adminCapIndex: 1,
158
+ });
159
+ };
160
+ }
161
+
162
+ // ── Master-reference reads ───────────────────────────────────────────────────
163
+
164
+ // recording_master_reference stores the ori::WalrusData value inline in a
165
+ // dynamic field on the Recording. Its empty ExtensionKey serializes to one false
166
+ // byte, so the field object id can be derived without listing the Recording's
167
+ // dynamic fields.
168
+ const MasterReferenceField = bcs.struct("Field", {
169
+ id: bcs.Address,
170
+ name: masterReference.ExtensionKey,
171
+ value: walrusData.WalrusData,
172
+ });
173
+ const MASTER_REFERENCE_KEY_BYTES = masterReference.ExtensionKey.serialize([
174
+ false,
175
+ ]).toBytes();
176
+
177
+ /** Parse an attached master reference's standalone Walrus blob id. */
178
+ export function parseRecordingMasterReferenceContent(
179
+ content: Uint8Array,
180
+ ): string | null {
181
+ const reference = MasterReferenceField.parse(content).value as
182
+ | { $kind: "Blob"; Blob: [string | number | bigint, unknown] }
183
+ | {
184
+ $kind: "QuiltPatch";
185
+ QuiltPatch: [string | number | bigint, number, number, number];
186
+ };
187
+ return reference.$kind === "Blob" ? String(reference.Blob[0]) : null;
188
+ }
189
+
190
+ /** Deterministic dynamic-field id for a Recording's master reference. */
191
+ export function recordingMasterReferenceFieldId(
192
+ recordingId: string,
193
+ recordingMasterReferencePackageId: string,
194
+ ): string {
195
+ return deriveDynamicFieldID(
196
+ recordingId,
197
+ `${recordingMasterReferencePackageId}::recording_master_reference::ExtensionKey`,
198
+ MASTER_REFERENCE_KEY_BYTES,
199
+ );
200
+ }
201
+
202
+ /** Read one Recording's master-reference blob id, or null when absent. */
203
+ export async function getRecordingMasterReference(
204
+ client: ClientWithCoreApi,
205
+ recordingId: string,
206
+ recordingMasterReferencePackageId: string,
207
+ ): Promise<string | null> {
208
+ return (
209
+ (
210
+ await getRecordingMasterReferencesByIds(
211
+ client,
212
+ [recordingId],
213
+ recordingMasterReferencePackageId,
214
+ )
215
+ )[recordingId] ?? null
216
+ );
217
+ }
218
+
219
+ /**
220
+ * Read master-reference blob ids for many Recordings in one Core request.
221
+ *
222
+ * Missing fields and malformed individual objects are omitted so one Recording
223
+ * without a playable master cannot make the rest of an album unplayable.
224
+ */
225
+ export async function getRecordingMasterReferencesByIds(
226
+ client: ClientWithCoreApi,
227
+ recordingIdsInput: readonly string[],
228
+ recordingMasterReferencePackageId: string,
229
+ ): Promise<Partial<Record<string, string>>> {
230
+ const recordingIds = [...new Set(recordingIdsInput)];
231
+ const targets = recordingIds.map((recordingId) => ({
232
+ recordingId,
233
+ fieldId: recordingMasterReferenceFieldId(
234
+ recordingId,
235
+ recordingMasterReferencePackageId,
236
+ ),
237
+ }));
238
+ if (targets.length === 0) return {};
239
+
240
+ const { objects } = await client.core.getObjects({
241
+ objectIds: targets.map((target) => target.fieldId),
242
+ include: { content: true },
243
+ });
244
+ const out: Partial<Record<string, string>> = {};
245
+ objects.forEach((object, index) => {
246
+ const target = targets[index];
247
+ if (!target || object instanceof Error || !object.content) return;
248
+ try {
249
+ const blobId = parseRecordingMasterReferenceContent(object.content);
250
+ if (blobId) out[target.recordingId] = blobId;
251
+ } catch {
252
+ // Extension metadata is soft: retain valid tracks when one field is stale.
253
+ }
254
+ });
255
+ return out;
256
+ }