@misofm/sdk 0.5.0 → 0.7.1

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 (78) hide show
  1. package/README.md +13 -9
  2. package/dist/client.d.ts +17 -1
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +22 -3
  5. package/dist/client.js.map +1 -1
  6. package/dist/contracts/release_registry/release_registry.d.ts +114 -0
  7. package/dist/contracts/release_registry/release_registry.d.ts.map +1 -0
  8. package/dist/contracts/release_registry/release_registry.js +119 -0
  9. package/dist/contracts/release_registry/release_registry.js.map +1 -0
  10. package/dist/contracts/royalty_pool/pool.d.ts +28 -2
  11. package/dist/contracts/royalty_pool/pool.d.ts.map +1 -1
  12. package/dist/contracts/royalty_pool/pool.js +28 -2
  13. package/dist/contracts/royalty_pool/pool.js.map +1 -1
  14. package/dist/contracts/utils/index.d.ts +44 -0
  15. package/dist/contracts/utils/index.d.ts.map +1 -1
  16. package/dist/contracts/utils/index.js +104 -2
  17. package/dist/contracts/utils/index.js.map +1 -1
  18. package/dist/contracts.d.ts +1 -0
  19. package/dist/contracts.d.ts.map +1 -1
  20. package/dist/contracts.js +4 -1
  21. package/dist/contracts.js.map +1 -1
  22. package/dist/cover.d.ts +3 -6
  23. package/dist/cover.d.ts.map +1 -1
  24. package/dist/cover.js +7 -16
  25. package/dist/cover.js.map +1 -1
  26. package/dist/credits.d.ts.map +1 -1
  27. package/dist/credits.js +17 -43
  28. package/dist/credits.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/read/catalog.d.ts +2 -12
  33. package/dist/read/catalog.d.ts.map +1 -1
  34. package/dist/read/catalog.js +9 -24
  35. package/dist/read/catalog.js.map +1 -1
  36. package/dist/read/config.d.ts +3 -8
  37. package/dist/read/config.d.ts.map +1 -1
  38. package/dist/read/config.js +15 -50
  39. package/dist/read/config.js.map +1 -1
  40. package/dist/read/index.d.ts +1 -1
  41. package/dist/read/index.d.ts.map +1 -1
  42. package/dist/read/index.js +1 -1
  43. package/dist/read/index.js.map +1 -1
  44. package/dist/read/types.d.ts +10 -1
  45. package/dist/read/types.d.ts.map +1 -1
  46. package/dist/read/wallet.d.ts +13 -8
  47. package/dist/read/wallet.d.ts.map +1 -1
  48. package/dist/read/wallet.js +39 -38
  49. package/dist/read/wallet.js.map +1 -1
  50. package/dist/read/works.d.ts +2 -5
  51. package/dist/read/works.d.ts.map +1 -1
  52. package/dist/read/works.js +7 -37
  53. package/dist/read/works.js.map +1 -1
  54. package/dist/release-graph.d.ts +6 -19
  55. package/dist/release-graph.d.ts.map +1 -1
  56. package/dist/release-graph.js +29 -43
  57. package/dist/release-graph.js.map +1 -1
  58. package/dist/transactions.d.ts +12 -28
  59. package/dist/transactions.d.ts.map +1 -1
  60. package/dist/transactions.js +23 -28
  61. package/dist/transactions.js.map +1 -1
  62. package/package.json +4 -4
  63. package/src/client.ts +33 -2
  64. package/src/contracts/release_registry/release_registry.ts +160 -0
  65. package/src/contracts/royalty_pool/pool.ts +28 -2
  66. package/src/contracts/utils/index.ts +156 -4
  67. package/src/contracts.ts +5 -1
  68. package/src/cover.ts +8 -19
  69. package/src/credits.ts +23 -63
  70. package/src/index.ts +3 -1
  71. package/src/read/catalog.ts +9 -29
  72. package/src/read/config.ts +18 -58
  73. package/src/read/index.ts +1 -1
  74. package/src/read/types.ts +11 -1
  75. package/src/read/wallet.ts +56 -38
  76. package/src/read/works.ts +5 -42
  77. package/src/release-graph.ts +40 -75
  78. package/src/transactions.ts +33 -62
@@ -21,12 +21,17 @@ import {
21
21
  } from "@misonetwork/sdk";
22
22
  import type { MisoClient } from "./client.ts";
23
23
  import { int, u64 } from "./internal/scalars.ts";
24
- import type { Balance, OwnedParty, OwnedRecord, OwnedWork, Ownership, WorkDetail } from "./types.ts";
24
+ import type {
25
+ Balance,
26
+ OwnedParty,
27
+ OwnedRecord,
28
+ OwnedWork,
29
+ Ownership,
30
+ PendingMembership,
31
+ WorkDetail,
32
+ } from "./types.ts";
25
33
  import { getRecordingTitles, getWorkAddressesByShareTypes, getWorksByIds } from "./works.ts";
26
34
 
27
- /** The on-chain type suffix every record object shares, across both live packages. */
28
- export const RECORD_TYPE_SUFFIX = "::record::Record";
29
-
30
35
  /**
31
36
  * Page cap on the owned-objects scan, so a wallet holding a huge unrelated object
32
37
  * set can't spin forever. 50/page × 20 = 1000 objects — far beyond any realistic
@@ -44,53 +49,29 @@ function coerceNumber(v: unknown): number | null {
44
49
  }
45
50
 
46
51
  /**
47
- * This copy's number in its run, across both record struct layouts: card
48
- * checkout's `{ release_id, number }` and the SDK view's
49
- * `{ release_id, edition, variant: { Prototype|Production: { number } } }`.
50
- * The gRPC JSON enum shape varies by transport, so this probes defensively.
51
- *
52
- * Precedence is MOST SPECIFIC FIRST — a top-level `number`, then the variant's
53
- * `number`, and only then `edition`. miso-app's original
54
- * (`lib/records.ts:readRecordNumber`) checked `edition` before descending into
55
- * the variant, so an SDK-view record reported which EDITION it came from (2)
56
- * where the field means which COPY it is (12). `edition` survives as the last
57
- * resort for a layout that carries nothing better.
52
+ * This copy's number in its run. Fresh records expose the canonical
53
+ * `{ release_id, number }` shape; pre-publish layouts are intentionally not
54
+ * accepted by the pre-launch SDK.
58
55
  */
59
56
  function readRecordNumber(json: Record<string, unknown>): number | null {
60
- const direct = coerceNumber(json.number);
61
- if (direct != null) return direct;
62
-
63
- const variant = json.variant;
64
- if (variant && typeof variant === "object") {
65
- const v = variant as Record<string, unknown>;
66
- const top = coerceNumber(v.number);
67
- if (top != null) return top;
68
- for (const inner of Object.values(v)) {
69
- if (inner && typeof inner === "object") {
70
- const n = coerceNumber((inner as Record<string, unknown>).number);
71
- if (n != null) return n;
72
- }
73
- }
74
- }
75
- return coerceNumber(json.edition);
57
+ return coerceNumber(json.number);
76
58
  }
77
59
 
78
60
  function readReleaseId(json: Record<string, unknown>): string | null {
79
- const r = json.release_id ?? json.releaseId;
61
+ const r = json.release_id;
80
62
  return typeof r === "string" && r ? r : null;
81
63
  }
82
64
 
83
65
  /**
84
66
  * The records `owner` holds. Ownership is DIRECT — a record is an address-owned
85
- * `<pkg>::record::Record` with no pressing/license/receipt intermediary.
86
- *
87
- * There is deliberately no `type` filter on the listing: two record packages are
88
- * live, so we match the `::record::Record` SUFFIX client-side and catch both with
89
- * no package-id config. Objects that fail to parse are skipped, never thrown.
67
+ * `<miso_record>::record::Record` with no pressing/license/receipt intermediary.
68
+ * The server-side type filter and exact local check deliberately exclude records
69
+ * from any retired package namespace.
90
70
  */
91
71
  export async function getOwnedRecords(client: MisoClient, owner: string): Promise<OwnedRecord[]> {
92
72
  const out: OwnedRecord[] = [];
93
73
  let cursor: string | null = null;
74
+ const recordType = `${client.config.protocol.record}::record::Record`;
94
75
 
95
76
  for (let page = 0; page < MAX_PAGES; page++) {
96
77
  // Annotated: `cursor` is both an input and assigned from the result, which
@@ -98,12 +79,13 @@ export async function getOwnedRecords(client: MisoClient, owner: string): Promis
98
79
  const res: Awaited<ReturnType<typeof client.protocol.core.listOwnedObjects>> =
99
80
  await client.protocol.core.listOwnedObjects({
100
81
  owner,
82
+ type: recordType,
101
83
  cursor,
102
84
  limit: 50,
103
85
  include: { json: true },
104
86
  });
105
87
  for (const obj of res.objects) {
106
- if (!obj.type || !obj.type.endsWith(RECORD_TYPE_SUFFIX)) continue;
88
+ if (obj.type !== recordType) continue;
107
89
  const json = (obj.json ?? {}) as Record<string, unknown>;
108
90
  out.push({
109
91
  id: obj.objectId,
@@ -156,6 +138,42 @@ export async function getOwnedParties(client: MisoClient, owner: string): Promis
156
138
  });
157
139
  }
158
140
 
141
+ /**
142
+ * Pending group invitations for every individual party the wallet administers.
143
+ *
144
+ * The Party module maintains a member-side pending-membership index, so this
145
+ * reads only the wallet's controlled parties — never a global event scan. Group
146
+ * names are resolved in one batch so the API can render an inbox without extra
147
+ * browser reads.
148
+ */
149
+ export async function getPendingMemberships(
150
+ client: MisoClient,
151
+ owner: string,
152
+ ): Promise<PendingMembership[]> {
153
+ const controlled = await getOwnedParties(client, owner);
154
+ const individuals = controlled.filter((party) => party.kind === "individual");
155
+ if (individuals.length === 0) return [];
156
+
157
+ const invitations = await Promise.all(
158
+ individuals.map(async (member) => ({
159
+ member,
160
+ groupIds: await client.sui.party.getPendingMemberships(member.partyId),
161
+ })),
162
+ );
163
+ const groupIds = [...new Set(invitations.flatMap(({ groupIds }) => groupIds))];
164
+ if (groupIds.length === 0) return [];
165
+
166
+ const groups = await client.sui.party.getPartiesByIds(groupIds);
167
+ return invitations.flatMap(({ member, groupIds }) =>
168
+ groupIds.flatMap((groupId): PendingMembership[] => {
169
+ const group = groups[groupId];
170
+ return group?.kind === "group"
171
+ ? [{ memberPartyId: member.partyId, memberCapId: member.capId, groupId, groupName: group.name }]
172
+ : [];
173
+ }),
174
+ );
175
+ }
176
+
159
177
  // ── Works (studio catalog) ───────────────────────────────────────────────────
160
178
 
161
179
  /**
package/src/read/works.ts CHANGED
@@ -179,31 +179,7 @@ function parseRecording(id: string, content: Uint8Array): Recording {
179
179
  export function parseReleaseObject(
180
180
  id: string,
181
181
  content: Uint8Array,
182
- json: unknown,
183
182
  ): Release {
184
- const deployed = json as {
185
- discs?: Parsed[];
186
- state?: Parsed;
187
- title?: unknown;
188
- } | null;
189
- if (deployed?.discs) {
190
- const tracks = deployed.discs.flatMap((disc) => disc.tracks ?? []);
191
- const publishedAt =
192
- deployed.state?.["@variant"] === "Published" ? deployed.state.pos0 : null;
193
- return {
194
- id,
195
- state:
196
- publishedAt == null
197
- ? { type: "Initialized" }
198
- : { type: "Published", timestampMs: Number(publishedAt) },
199
- title: String(deployed.title ?? ""),
200
- tracks: tracks.map((track) => ({
201
- state: (track.state?.["@variant"] ?? "Unassigned") as TrackState,
202
- recordingId: String(track.recording_id),
203
- splitBps: { value: Number(track.split_bps?.pos0 ?? track.split_bps) },
204
- })),
205
- };
206
- }
207
183
  const value = contracts.release.Release.parse(content) as Parsed;
208
184
  return {
209
185
  id,
@@ -245,7 +221,7 @@ export async function getWorksByIds(
245
221
  if (kinds.size === 0) return out;
246
222
  const { objects } = await client.core.getObjects({
247
223
  objectIds: [...kinds.keys()],
248
- include: { content: true, json: true },
224
+ include: { content: true },
249
225
  });
250
226
  for (const object of objects) {
251
227
  if (object instanceof Error || !object.content) continue;
@@ -264,16 +240,12 @@ export async function getWorksByIds(
264
240
  out.releases[object.objectId] = parseReleaseObject(
265
241
  object.objectId,
266
242
  object.content,
267
- object.json,
268
243
  );
269
244
  }
270
245
  return out;
271
246
  }
272
247
 
273
- /**
274
- * Resolve display titles for both deployed recording layouts. Older recordings
275
- * carry `title` in JSON; current recordings inherit it from their composition.
276
- */
248
+ /** Resolve recording titles through each recording's canonical composition type. */
277
249
  export async function getRecordingTitles(
278
250
  client: ClientWithCoreApi,
279
251
  graphql: SuiGraphQLClient,
@@ -285,24 +257,15 @@ export async function getRecordingTitles(
285
257
 
286
258
  const { objects } = await client.core.getObjects({
287
259
  objectIds: ids,
288
- include: { json: true },
260
+ include: {},
289
261
  });
290
262
  const titles: Record<string, string> = {};
291
263
  const compositionShareByRecording: Record<string, string> = {};
292
264
 
293
265
  for (const object of objects) {
294
266
  if (object instanceof Error) continue;
295
- const json = object.json as { title?: unknown } | null;
296
- if (typeof json?.title === "string" && json.title) {
297
- titles[object.objectId] = json.title;
298
- continue;
299
- }
300
- try {
301
- const [, compositionShareType] = extractTypeParams2(object.type);
302
- compositionShareByRecording[object.objectId] = compositionShareType;
303
- } catch {
304
- // A deployed layout with no composition type and no JSON title remains Untitled.
305
- }
267
+ const [, compositionShareType] = extractTypeParams2(object.type);
268
+ compositionShareByRecording[object.objectId] = compositionShareType;
306
269
  }
307
270
 
308
271
  const compositionShareTypes = Object.values(compositionShareByRecording);
@@ -3,35 +3,33 @@
3
3
 
4
4
  // Single-PTB publisher for a whole release graph: creates every composition and
5
5
  // recording, optionally attaches royalty pools, derives the release id ON-CHAIN
6
- // (so recordings need not be shared first), builds the deals/tracks/release,
6
+ // (so recordings need not be shared first), builds the tracks/release,
7
7
  // then shares everything and transfers the admin caps — all in ONE transaction.
8
8
  //
9
9
  // Share currencies must already be published + initialized (their Currency objects
10
10
  // shared on-chain); their ids/types are passed in. Ordering is load-bearing:
11
11
  // 1. composition::new → recording::new(&comp) (borrow-before-share) + pool attach
12
- // 2. release::derive_release_id(<recording::id results>) → deal::new(cap, &rec,
13
- // derivedId, split) → track::new → release::new
12
+ // 2. release::derive_target_release_id(<recording::id results>) →
13
+ // track::new(cap, &rec, derivedId, split) → release_registry::new_release
14
14
  // 3. publish (share) comps + recs + release, disperse supply, transfer caps
15
15
  // Steps 1–2 borrow the works; step 3 consumes them into share_object.
16
16
  //
17
- // Release tracks come in three shapes (see TrackNode): recordings created in
18
- // THIS PTB, existing recordings authorized by their admin cap (deal created
19
- // inline), and existing recordings authorized by a pre-made Deal in hand.
20
- // Pre-made deals pin the release id, so they cannot coexist with fresh
21
- // recordings — the builder rejects that mix up front.
17
+ // Release tracks come in two shapes (see TrackNode): recordings created in THIS
18
+ // PTB, and existing recordings authorized by their admin cap.
22
19
  //
23
20
  // This is the whole-graph orchestration half of the opinionated publish flow —
24
- // it composes `@misonetwork/sdk`'s bare `composition`/`recording`/`deal`/`track`/
25
- // `release` call bindings and its `createRelease` primitive with this package's
21
+ // it composes `@misonetwork/sdk`'s bare `composition`/`recording`/`track`/
22
+ // `release` call bindings and this package's `release_registry` binding with its
26
23
  // own `disperseShares`/`finalizeRelease` and royalty-pool extension helpers, into
27
24
  // one PTB.
28
25
 
29
26
  import { Transaction, type TransactionObjectArgument } from "@mysten/sui/transactions";
30
- import { contracts, createRelease, PROTOCOL_MAX_ROYALTY_RATE_BPS } from "@misonetwork/sdk";
27
+ import { contracts, PROTOCOL_MAX_ROYALTY_RATE_BPS } from "@misonetwork/sdk";
31
28
  import { attachCompositionRoyaltyPool, attachRecordingRoyaltyPool } from "./extensions/royalty-pool.ts";
32
29
  import { disperseShares, finalizeRelease, type ShareRecipient } from "./transactions.ts";
30
+ import * as releaseRegistry from "./contracts/release_registry/release_registry.ts";
33
31
 
34
- const { composition, recording, deal, track, release } = contracts;
32
+ const { composition, recording, track, release } = contracts;
35
33
 
36
34
  /** A royalty pool to attach to a work, in the same PTB. */
37
35
  export interface RoyaltyPoolNode {
@@ -75,7 +73,7 @@ export interface RecordingNode {
75
73
  royaltyPool?: RoyaltyPoolNode;
76
74
  }
77
75
 
78
- /** A track whose recording is created in THIS PTB (its deal is created inline). */
76
+ /** A track whose recording is created in THIS PTB. */
79
77
  export interface FreshTrackNode {
80
78
  /** Index into `recordings[]`. */
81
79
  recordingIndex: number;
@@ -83,9 +81,8 @@ export interface FreshTrackNode {
83
81
  }
84
82
 
85
83
  /**
86
- * A track over an EXISTING recording, authorized by its admin cap (held by the
87
- * sender) the deal is created inline against the on-chain-derived release id.
88
- * This is the only way to mix existing recordings with ones created in this PTB.
84
+ * A track over an EXISTING recording, authorized by its admin cap held by the
85
+ * sender. This can coexist with recordings created in this PTB.
89
86
  */
90
87
  export interface CapTrackNode {
91
88
  recordingId: string;
@@ -95,25 +92,9 @@ export interface CapTrackNode {
95
92
  splitBps: number;
96
93
  }
97
94
 
98
- /**
99
- * A track over an EXISTING recording, authorized by a pre-made `Deal` held by
100
- * the sender. A deal pins the exact release id — a digest over ALL track
101
- * recording ids — so deal-backed tracks cannot coexist with `FreshTrackNode`s
102
- * (a fresh recording's id is unknowable when the deal was created).
103
- * `splitBps` must be the value stored in the deal (read it from the object).
104
- */
105
- export interface DealTrackNode {
106
- dealId: string;
107
- recordingId: string;
108
- recordingShareType: string;
109
- compositionShareType: string;
110
- splitBps: number;
111
- }
112
-
113
- export type TrackNode = FreshTrackNode | CapTrackNode | DealTrackNode;
95
+ export type TrackNode = FreshTrackNode | CapTrackNode;
114
96
 
115
97
  const isFresh = (t: TrackNode): t is FreshTrackNode => "recordingIndex" in t;
116
- const isDealBacked = (t: TrackNode): t is DealTrackNode => "dealId" in t;
117
98
 
118
99
  export interface ReleaseNode {
119
100
  title: string;
@@ -131,6 +112,8 @@ export interface PublishReleaseGraphParams {
131
112
  release?: ReleaseNode;
132
113
  misoPackageId: string;
133
114
  minatoPackageId: string;
115
+ /** `release_registry` extension package, distinct from its shared object id. */
116
+ releaseRegistryPackageId: string;
134
117
  }
135
118
 
136
119
  interface Parts {
@@ -141,7 +124,7 @@ interface Parts {
141
124
 
142
125
  /** Builds the entire graph in a single transaction (see file header for ordering). */
143
126
  export function publishReleaseGraph(params: PublishReleaseGraphParams): (tx: Transaction) => void {
144
- const { misoPackageId: pkg, minatoPackageId } = params;
127
+ const { misoPackageId: pkg, minatoPackageId, releaseRegistryPackageId } = params;
145
128
  return (tx) => {
146
129
  // ── 1) Create every composition, then every recording (borrow-before-share) ──
147
130
  const comps: Parts[] = params.compositions.map((c) => {
@@ -199,61 +182,43 @@ export function publishReleaseGraph(params: PublishReleaseGraphParams): (tx: Tra
199
182
  return parts;
200
183
  });
201
184
 
202
- // ── 2) Release: derive the id on-chain, then deals → tracks → release ──
185
+ // ── 2) Release: derive the id on-chain, then tracks → release ──────────
203
186
  if (params.release) {
204
187
  const rel = params.release;
205
188
  const ordered = rel.tracks;
206
- if (ordered.some(isDealBacked) && ordered.some(isFresh)) {
207
- throw new Error(
208
- "A release cannot mix pre-made deals with recordings created in this transaction: " +
209
- "a deal pins the exact release id, whose digest includes every track's recording id, " +
210
- "and a fresh recording's id is unknowable when the deal was created. " +
211
- "Authorize the existing recordings with their admin caps instead.",
212
- );
213
- }
214
-
215
- // The derived id feeds the deals created inline (fresh/cap-backed tracks).
216
- // Pre-made deals already store it, so an all-deal release skips the derive.
217
- let releaseId: TransactionObjectArgument | undefined;
218
- if (!ordered.every(isDealBacked)) {
219
- const idVec = tx.makeMoveVec({
220
- type: "0x2::object::ID",
221
- elements: ordered.map((t) =>
222
- isFresh(t)
223
- ? tx.add(recording.id({ package: pkg, typeArguments: recTypeArgs(t.recordingIndex), arguments: [recs[t.recordingIndex]!.work] }))
224
- : tx.pure.id(t.recordingId),
225
- ),
226
- });
227
- const splitVec = tx.makeMoveVec({ type: "u64", elements: ordered.map((t) => tx.pure.u64(t.splitBps)) });
228
- releaseId = tx.add(
229
- release.deriveReleaseId({ package: pkg, arguments: [idVec, splitVec, tx.pure.u256(BigInt(rel.nonce)), tx.object(rel.releaseRegistryId)] }),
230
- );
231
- }
189
+ const idVec = tx.makeMoveVec({
190
+ type: "0x2::object::ID",
191
+ elements: ordered.map((t) =>
192
+ isFresh(t)
193
+ ? tx.add(recording.id({ package: pkg, typeArguments: recTypeArgs(t.recordingIndex), arguments: [recs[t.recordingIndex]!.work] }))
194
+ : tx.pure.id(t.recordingId),
195
+ ),
196
+ });
197
+ const splitVec = tx.makeMoveVec({ type: "u64", elements: ordered.map((t) => tx.pure.u64(t.splitBps)) });
198
+ // This returns an ID value (not an object); pass the result straight to
199
+ // `track::new` so every track consents to this exact release target.
200
+ const releaseId = tx.add(
201
+ release.deriveTargetReleaseId({ package: pkg, arguments: [idVec, splitVec, tx.pure.u256(BigInt(rel.nonce)), tx.pure.id(rel.releaseRegistryId)] }),
202
+ );
232
203
 
233
204
  const trackArgs = ordered.map((t) => {
234
- if (isDealBacked(t)) {
235
- const typeArgs: [string, string] = [t.recordingShareType, t.compositionShareType];
236
- return tx.add(track._new({ package: pkg, typeArguments: typeArgs, arguments: [tx.object(t.dealId), tx.object(t.recordingId)] }));
237
- }
238
205
  if (isFresh(t)) {
239
206
  const typeArgs = recTypeArgs(t.recordingIndex);
240
207
  const parts = recs[t.recordingIndex]!;
241
- const dealArg = tx.add(deal._new({ package: pkg, typeArguments: typeArgs, arguments: [parts.adminCap, parts.work, releaseId!, tx.pure.u16(t.splitBps)] }));
242
- return tx.add(track._new({ package: pkg, typeArguments: typeArgs, arguments: [dealArg, parts.work] }));
208
+ return tx.add(track._new({ package: pkg, typeArguments: typeArgs, arguments: [parts.adminCap, parts.work, releaseId, tx.pure.u16(t.splitBps)] }));
243
209
  }
244
210
  const typeArgs: [string, string] = [t.recordingShareType, t.compositionShareType];
245
211
  const rec = tx.object(t.recordingId);
246
- const dealArg = tx.add(
247
- deal._new({ package: pkg, typeArguments: typeArgs, arguments: [tx.object(t.recordingAdminCapId), rec, releaseId!, tx.pure.u16(t.splitBps)] }),
248
- );
249
- return tx.add(track._new({ package: pkg, typeArguments: typeArgs, arguments: [dealArg, rec] }));
212
+ return tx.add(track._new({ package: pkg, typeArguments: typeArgs, arguments: [tx.object(t.recordingAdminCapId), rec, releaseId, tx.pure.u16(t.splitBps)] }));
250
213
  });
251
214
  const trackVec = tx.makeMoveVec({ type: `${pkg}::track::Track`, elements: trackArgs });
252
- const releaseParts = createRelease(
253
- tx,
254
- { title: rel.title, nonce: rel.nonce, releaseRegistryId: rel.releaseRegistryId, misoPackageId: pkg },
255
- trackVec,
215
+ const created = tx.add(
216
+ releaseRegistry.newRelease({
217
+ package: releaseRegistryPackageId,
218
+ arguments: [tx.object(rel.releaseRegistryId), tx.pure.string(rel.title), trackVec, tx.pure.u256(BigInt(rel.nonce))],
219
+ }),
256
220
  );
221
+ const releaseParts = { release: created[0]!, adminCap: created[1]! };
257
222
  finalizeRelease(tx, { ...releaseParts, adminAddress: rel.adminAddress, misoPackageId: pkg });
258
223
  }
259
224
 
@@ -15,10 +15,11 @@
15
15
  // (`createComposition`/`createRecording`) — dispersing it to recipients via
16
16
  // minato, publishing (sharing) the work, and transferring its admin cap is
17
17
  // an opinion about economics;
18
- // - minting a release is likewise a primitive (`createRelease`) choosing a
19
- // track-assembly strategy, publishing (sharing) it, and picking a recipient
20
- // for its `ReleaseAdminCap` is the opinion, so `finalizeRelease` /
21
- // `publishRelease` / `publishReleaseFromDeals` live here.
18
+ // - minting a release is a platform primitive: core's `release::new` takes an
19
+ // unconstructible PTB `&mut UID`, so `release_registry::new_release` 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.
22
23
  //
23
24
  // Every `finalize*` here MUST run in the same PTB as the `create*` that produced
24
25
  // its parts — `Composition`, `Recording`, and `Release` are all `key`-only with
@@ -34,17 +35,21 @@ import {
34
35
  contracts,
35
36
  createComposition,
36
37
  createRecording,
37
- createRelease,
38
38
  type CompositionParts,
39
39
  type CreateCompositionParams,
40
40
  type CreateRecordingParams,
41
41
  type RecordingParts,
42
- type ReleaseParts,
43
42
  type ShareCurrencyBinding,
44
43
  type TxThunk,
45
44
  } from "@misonetwork/sdk";
45
+ import * as releaseRegistry from "./contracts/release_registry/release_registry.ts";
46
46
 
47
- const { deal, track, release } = contracts;
47
+ const { track, release } = contracts;
48
+
49
+ interface ReleaseParts {
50
+ release: TransactionObjectArgument;
51
+ adminCap: TransactionObjectArgument;
52
+ }
48
53
 
49
54
  // `TxThunk` is the protocol SDK's type, re-exported so platform consumers get
50
55
  // the same nominal shape rather than a structurally-identical twin.
@@ -329,7 +334,8 @@ export interface FinalizeReleaseParams extends ReleaseParts {
329
334
  * The opinionated finish for a release: publish (share) it and transfer its
330
335
  * admin cap to `adminAddress`.
331
336
  *
332
- * MUST run in the same PTB as the `createRelease` that produced these parts —
337
+ * MUST run in the same PTB as the `release_registry::new_release` that produced
338
+ * these parts —
333
339
  * `Release` is `key`-only with no `drop`, so an unpublished release cannot
334
340
  * outlive its transaction. Splitting create from finalize is what lets a caller
335
341
  * do something else with the cap (route it to a vault, hand it to another
@@ -343,9 +349,9 @@ export function finalizeRelease(tx: Transaction, params: FinalizeReleaseParams):
343
349
  export interface TrackInput {
344
350
  recordingId: string;
345
351
  recordingAdminCapId: string;
346
- /** Share type of the recording (the deal/track `RecordingShare` phantom). */
352
+ /** Share type of the recording (the track `RecordingShare` phantom). */
347
353
  recordingShareType: string;
348
- /** Share type of the parent composition (the deal/track `CompositionShare` phantom). */
354
+ /** Share type of the parent composition (the track `CompositionShare` phantom). */
349
355
  compositionShareType: string;
350
356
  splitBps: number;
351
357
  }
@@ -354,6 +360,8 @@ export interface PublishReleaseParams {
354
360
  title: string;
355
361
  /** The ordered tracklist. Display grouping (discs/sides) is extension data. */
356
362
  tracks: TrackInput[];
363
+ /** `release_registry` extension package, distinct from its shared object id. */
364
+ releaseRegistryPackageId: string;
357
365
  releaseRegistryId: string;
358
366
  releaseId: string;
359
367
  releaseNonce: string;
@@ -371,70 +379,33 @@ function buildTrackVec(
371
379
 
372
380
  /**
373
381
  * Convenience: publish a release end-to-end, assembling its tracklist from
374
- * recording admin caps held by the sender (a deal is created inline per track).
382
+ * recording admin caps held by the sender.
375
383
  */
376
384
  export function publishRelease(params: PublishReleaseParams): TxThunk {
377
385
  return (tx) => {
378
- const { misoPackageId } = params;
386
+ const { misoPackageId, releaseRegistryPackageId } = params;
379
387
  const trackArgs = params.tracks.map((t) => {
380
388
  const typeArguments: [string, string] = [t.recordingShareType, t.compositionShareType];
381
- const dealArg = tx.add(
382
- deal._new({
389
+ return tx.add(
390
+ track._new({
383
391
  package: misoPackageId,
384
392
  typeArguments,
385
393
  arguments: [tx.object(t.recordingAdminCapId), tx.object(t.recordingId), tx.pure.id(params.releaseId), tx.pure.u16(t.splitBps)],
386
394
  }),
387
395
  );
388
- return tx.add(track._new({ package: misoPackageId, typeArguments, arguments: [dealArg, tx.object(t.recordingId)] }));
389
396
  });
390
- const parts = createRelease(
391
- tx,
392
- { title: params.title, nonce: params.releaseNonce, releaseRegistryId: params.releaseRegistryId, misoPackageId },
393
- buildTrackVec(tx, misoPackageId, trackArgs),
394
- );
395
- finalizeRelease(tx, { ...parts, adminAddress: params.adminAddress, misoPackageId });
396
- };
397
- }
398
-
399
- export interface DealInput {
400
- dealId: string;
401
- /** The recording the deal is for — `track::new` takes `&Recording` alongside the deal. */
402
- recordingId: string;
403
- /** Share type of the recording (the deal/track `RecordingShare` phantom). */
404
- recordingShareType: string;
405
- /** Share type of the parent composition (the deal/track `CompositionShare` phantom). */
406
- compositionShareType: string;
407
- }
408
-
409
- export interface PublishReleaseFromDealsParams {
410
- title: string;
411
- /** The ordered tracklist, one pre-made deal per track. */
412
- deals: DealInput[];
413
- releaseRegistryId: string;
414
- releaseNonce: string;
415
- misoPackageId: string;
416
- adminAddress: string;
417
- }
418
-
419
- /**
420
- * Convenience: publish a release end-to-end from pre-made `Deal`s held by the
421
- * sender (each deal already pins this release's id).
422
- */
423
- export function publishReleaseFromDeals(params: PublishReleaseFromDealsParams): TxThunk {
424
- return (tx) => {
425
- const { misoPackageId } = params;
426
- const trackArgs = params.deals.map((dl) =>
427
- tx.add(track._new({
428
- package: misoPackageId,
429
- typeArguments: [dl.recordingShareType, dl.compositionShareType],
430
- arguments: [tx.object(dl.dealId), tx.object(dl.recordingId)],
431
- })),
432
- );
433
- const parts = createRelease(
434
- tx,
435
- { title: params.title, nonce: params.releaseNonce, releaseRegistryId: params.releaseRegistryId, misoPackageId },
436
- buildTrackVec(tx, misoPackageId, trackArgs),
397
+ const created = tx.add(
398
+ releaseRegistry.newRelease({
399
+ package: releaseRegistryPackageId,
400
+ arguments: [
401
+ tx.object(params.releaseRegistryId),
402
+ tx.pure.string(params.title),
403
+ buildTrackVec(tx, misoPackageId, trackArgs),
404
+ tx.pure.u256(BigInt(params.releaseNonce)),
405
+ ],
406
+ }),
437
407
  );
408
+ const parts: ReleaseParts = { release: created[0]!, adminCap: created[1]! };
438
409
  finalizeRelease(tx, { ...parts, adminAddress: params.adminAddress, misoPackageId });
439
410
  };
440
411
  }