@misonetwork/sdk 0.3.0 → 0.4.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.
package/src/queries.ts CHANGED
@@ -22,13 +22,19 @@
22
22
  import type { ClientWithCoreApi } from "@mysten/sui/client";
23
23
  import type { SuiGraphQLClient } from "@mysten/sui/graphql";
24
24
  import { graphql } from "@mysten/sui/graphql/schema";
25
- import { deriveObjectID } from "@mysten/sui/utils";
25
+ import { deriveObjectID, normalizeSuiAddress } from "@mysten/sui/utils";
26
26
 
27
27
  import { Composition as CompositionBcs } from "./contracts/miso/composition.ts";
28
28
  import { Deal as DealBcs } from "./contracts/miso/deal.ts";
29
29
  import { Recording as RecordingBcs } from "./contracts/miso/recording.ts";
30
30
  import { Release as ReleaseBcs } from "./contracts/miso/release.ts";
31
- import { mapBps, mapComposition, mapRecording, mapRelease } from "./internal.ts";
31
+ import {
32
+ mapBps,
33
+ mapComposition,
34
+ mapDiscReleaseJson,
35
+ mapRecording,
36
+ mapRelease,
37
+ } from "./internal.ts";
32
38
  import type {
33
39
  Composition,
34
40
  CompositionAdminCap,
@@ -50,7 +56,8 @@ import type {
50
56
  */
51
57
  export function extractTypeParam(objectType: string): string {
52
58
  const match = objectType.match(/<(.+)>$/);
53
- if (!match?.[1]) throw new Error(`Could not extract type parameter from: ${objectType}`);
59
+ if (!match?.[1])
60
+ throw new Error(`Could not extract type parameter from: ${objectType}`);
54
61
  return match[1];
55
62
  }
56
63
 
@@ -62,7 +69,8 @@ export function extractTypeParams2(objectType: string): [string, string] {
62
69
  const ch = inner[i];
63
70
  if (ch === "<") depth++;
64
71
  else if (ch === ">") depth--;
65
- else if (ch === "," && depth === 0) return [inner.slice(0, i).trim(), inner.slice(i + 1).trim()];
72
+ else if (ch === "," && depth === 0)
73
+ return [inner.slice(0, i).trim(), inner.slice(i + 1).trim()];
66
74
  }
67
75
  throw new Error(`Expected two type parameters in: ${objectType}`);
68
76
  }
@@ -90,13 +98,20 @@ export function extractTypeParams2(objectType: string): [string, string] {
90
98
  export function isNotFound(e: unknown): boolean {
91
99
  if (typeof e === "object" && e !== null && "code" in e) {
92
100
  const code = (e as { code: unknown }).code;
93
- if (code === "notExists" || code === "deleted" || code === "dynamicFieldNotFound" || code === "notFound") {
101
+ if (
102
+ code === "notExists" ||
103
+ code === "deleted" ||
104
+ code === "dynamicFieldNotFound" ||
105
+ code === "notFound"
106
+ ) {
94
107
  return true;
95
108
  }
96
109
  }
97
110
  const msg = e instanceof Error ? e.message : String(e);
98
111
  return (
99
- /\bobject\b[\s\S]*\b(?:not\s?found|does not exist|has been deleted)\b/i.test(msg) ||
112
+ /\bobject\b[\s\S]*\b(?:not\s?found|does not exist|has been deleted)\b/i.test(
113
+ msg,
114
+ ) ||
100
115
  /\bdynamic field\b[\s\S]*\bnot\s?found\b/i.test(msg) ||
101
116
  /\bno object\b/i.test(msg)
102
117
  );
@@ -106,8 +121,14 @@ export function isNotFound(e: unknown): boolean {
106
121
  const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
107
122
 
108
123
  /** Fetches one object's BCS content bytes (or null if absent). */
109
- async function getContent(client: ClientWithCoreApi, objectId: string): Promise<Uint8Array | null> {
110
- const { object } = await client.core.getObject({ objectId, include: { content: true } });
124
+ async function getContent(
125
+ client: ClientWithCoreApi,
126
+ objectId: string,
127
+ ): Promise<Uint8Array | null> {
128
+ const { object } = await client.core.getObject({
129
+ objectId,
130
+ include: { content: true },
131
+ });
111
132
  return object.content ?? null;
112
133
  }
113
134
 
@@ -119,7 +140,9 @@ async function getContent(client: ClientWithCoreApi, objectId: string): Promise<
119
140
  const AddressesByTypeQuery = graphql(`
120
141
  query AddressesByType($type: String!) {
121
142
  objects(filter: { type: $type }) {
122
- nodes { address }
143
+ nodes {
144
+ address
145
+ }
123
146
  }
124
147
  }
125
148
  `);
@@ -136,11 +159,217 @@ const AddressesByTypeQuery = graphql(`
136
159
  const AddressesAndTypesByTypeQuery = graphql(`
137
160
  query AddressesAndTypesByType($type: String!) {
138
161
  objects(filter: { type: $type }) {
139
- nodes { address asMoveObject { contents { type { repr } } } }
162
+ nodes {
163
+ address
164
+ asMoveObject {
165
+ contents {
166
+ type {
167
+ repr
168
+ }
169
+ }
170
+ }
171
+ }
140
172
  }
141
173
  }
142
174
  `);
143
175
 
176
+ export interface WorkShareTypes {
177
+ compositions: readonly string[];
178
+ recordings: readonly string[];
179
+ }
180
+
181
+ export interface WorkAddressesByShareType {
182
+ compositions: Partial<Record<string, string>>;
183
+ recordings: Partial<Record<string, string>>;
184
+ }
185
+
186
+ interface WorkAddressConnection {
187
+ nodes: Array<{
188
+ address: string;
189
+ asMoveObject?: {
190
+ contents?: { type?: { repr?: string } | null } | null;
191
+ } | null;
192
+ }>;
193
+ pageInfo?: { hasNextPage: boolean; endCursor: string | null };
194
+ }
195
+
196
+ /**
197
+ * Resolve many work share types in one GraphQL request.
198
+ *
199
+ * Compositions can be queried by their exact one-parameter type. Recordings
200
+ * carry both RecordingShare and CompositionShare, while an admin cap only
201
+ * exposes the first, so one bare Recording scan is shared by every requested
202
+ * recording type and filtered client-side.
203
+ */
204
+ export async function getWorkAddressesByShareTypes(
205
+ client: SuiGraphQLClient,
206
+ shareTypes: WorkShareTypes,
207
+ misoPackageId: string,
208
+ ): Promise<WorkAddressesByShareType> {
209
+ const compositions = [...new Set(shareTypes.compositions)];
210
+ const recordings = new Set(shareTypes.recordings);
211
+ const out: WorkAddressesByShareType = { compositions: {}, recordings: {} };
212
+ if (compositions.length === 0 && recordings.size === 0) return out;
213
+
214
+ const declarations: string[] = [];
215
+ const selections: string[] = [];
216
+ const variables: Record<string, string> = {};
217
+
218
+ compositions.forEach((shareType, index) => {
219
+ const variable = `compositionType${index}`;
220
+ declarations.push(`$${variable}: String!`);
221
+ selections.push(
222
+ `composition${index}: objects(first: 1, filter: { type: $${variable} }) { nodes { address } }`,
223
+ );
224
+ variables[variable] =
225
+ `${misoPackageId}::composition::Composition<${shareType}>`;
226
+ });
227
+
228
+ if (recordings.size > 0) {
229
+ declarations.push("$recordingType: String!");
230
+ selections.push(`recordings: objects(first: 50, filter: { type: $recordingType }) {
231
+ pageInfo { hasNextPage endCursor }
232
+ nodes { address asMoveObject { contents { type { repr } } } }
233
+ }`);
234
+ variables.recordingType = `${misoPackageId}::recording::Recording`;
235
+ }
236
+
237
+ const result = await client.query<
238
+ Record<string, WorkAddressConnection | null>,
239
+ Record<string, string>
240
+ >({
241
+ query: `query WorkAddressesByShareTypes(${declarations.join(", ")}) {
242
+ ${selections.join("\n")}
243
+ }`,
244
+ variables,
245
+ });
246
+ if (result.errors?.length) {
247
+ throw new AggregateError(
248
+ result.errors.map((error) => new Error(error.message)),
249
+ "Work type discovery failed",
250
+ );
251
+ }
252
+
253
+ compositions.forEach((shareType, index) => {
254
+ const address = result.data?.[`composition${index}`]?.nodes[0]?.address;
255
+ if (address) out.compositions[shareType] = address;
256
+ });
257
+
258
+ const readRecordingPage = (
259
+ page: WorkAddressConnection | null | undefined,
260
+ ) => {
261
+ for (const node of page?.nodes ?? []) {
262
+ const repr = node.asMoveObject?.contents?.type?.repr;
263
+ if (!repr) continue;
264
+ try {
265
+ const [recordingShareType] = extractTypeParams2(repr);
266
+ if (recordings.has(recordingShareType)) {
267
+ out.recordings[recordingShareType] = node.address;
268
+ }
269
+ } catch {
270
+ // Ignore a live object whose type does not match the deployed Recording ABI.
271
+ }
272
+ }
273
+ };
274
+
275
+ let recordingPage = result.data?.recordings;
276
+ readRecordingPage(recordingPage);
277
+ while (
278
+ recordingPage?.pageInfo?.hasNextPage &&
279
+ recordingPage.pageInfo.endCursor &&
280
+ Object.keys(out.recordings).length < recordings.size
281
+ ) {
282
+ const next = await client.query<
283
+ { recordings: WorkAddressConnection | null },
284
+ { recordingType: string; cursor: string }
285
+ >({
286
+ query: `query RecordingWorkAddresses($recordingType: String!, $cursor: String!) {
287
+ recordings: objects(first: 50, after: $cursor, filter: { type: $recordingType }) {
288
+ pageInfo { hasNextPage endCursor }
289
+ nodes { address asMoveObject { contents { type { repr } } } }
290
+ }
291
+ }`,
292
+ variables: {
293
+ recordingType: variables.recordingType!,
294
+ cursor: recordingPage.pageInfo.endCursor,
295
+ },
296
+ });
297
+ if (next.errors?.length) {
298
+ throw new AggregateError(
299
+ next.errors.map((error) => new Error(error.message)),
300
+ "Recording type discovery failed",
301
+ );
302
+ }
303
+ recordingPage = next.data?.recordings;
304
+ readRecordingPage(recordingPage);
305
+ }
306
+
307
+ return out;
308
+ }
309
+
310
+ export interface WorkIds {
311
+ compositions: readonly string[];
312
+ recordings: readonly string[];
313
+ releases: readonly string[];
314
+ }
315
+
316
+ export interface WorksById {
317
+ compositions: Partial<Record<string, Composition>>;
318
+ recordings: Partial<Record<string, Recording>>;
319
+ releases: Partial<Record<string, Release>>;
320
+ }
321
+
322
+ /** Fetch and parse heterogeneous work objects through one Core bulk request. */
323
+ export async function getWorksByIds(
324
+ client: ClientWithCoreApi,
325
+ ids: WorkIds,
326
+ ): Promise<WorksById> {
327
+ const kinds = new Map<string, keyof WorksById>();
328
+ for (const [kind, objectIds] of Object.entries(ids) as Array<
329
+ [keyof WorksById, readonly string[]]
330
+ >) {
331
+ for (const objectId of objectIds) {
332
+ const normalized = normalizeSuiAddress(objectId);
333
+ const previous = kinds.get(normalized);
334
+ if (previous && previous !== kind) {
335
+ throw new Error(
336
+ `Work ${normalized} was requested as both ${previous} and ${kind}`,
337
+ );
338
+ }
339
+ kinds.set(normalized, kind);
340
+ }
341
+ }
342
+
343
+ const out: WorksById = { compositions: {}, recordings: {}, releases: {} };
344
+ if (kinds.size === 0) return out;
345
+
346
+ const { objects } = await client.core.getObjects({
347
+ objectIds: [...kinds.keys()],
348
+ include: { content: true, json: true },
349
+ });
350
+ for (const obj of objects) {
351
+ if (obj instanceof Error || !obj.content) continue;
352
+ const kind = kinds.get(normalizeSuiAddress(obj.objectId));
353
+ if (kind === "compositions") {
354
+ out.compositions[obj.objectId] = mapComposition(
355
+ obj.objectId,
356
+ CompositionBcs.parse(obj.content),
357
+ );
358
+ } else if (kind === "recordings") {
359
+ out.recordings[obj.objectId] = mapRecording(
360
+ obj.objectId,
361
+ RecordingBcs.parse(obj.content),
362
+ );
363
+ } else if (kind === "releases") {
364
+ const json = obj.json as { discs?: unknown[] } | null;
365
+ out.releases[obj.objectId] = json?.discs
366
+ ? mapDiscReleaseJson(obj.objectId, json)
367
+ : mapRelease(obj.objectId, ReleaseBcs.parse(obj.content));
368
+ }
369
+ }
370
+ return out;
371
+ }
372
+
144
373
  // ============================================================================
145
374
  // Composition
146
375
  // ============================================================================
@@ -151,24 +380,36 @@ export async function getCompositionsByIds(
151
380
  compositionIds: string[],
152
381
  ): Promise<Record<string, Composition>> {
153
382
  if (compositionIds.length === 0) return {};
154
- const { objects } = await client.core.getObjects({ objectIds: compositionIds, include: { content: true } });
383
+ const { objects } = await client.core.getObjects({
384
+ objectIds: compositionIds,
385
+ include: { content: true },
386
+ });
155
387
  const out: Record<string, Composition> = {};
156
388
  for (const obj of objects) {
157
389
  if (obj instanceof Error || !obj.content) continue;
158
- out[obj.objectId] = mapComposition(obj.objectId, CompositionBcs.parse(obj.content));
390
+ out[obj.objectId] = mapComposition(
391
+ obj.objectId,
392
+ CompositionBcs.parse(obj.content),
393
+ );
159
394
  }
160
395
  return out;
161
396
  }
162
397
 
163
398
  /** Fetches a composition by its object ID. */
164
- export async function getCompositionById(client: ClientWithCoreApi, compositionId: string): Promise<Composition> {
399
+ export async function getCompositionById(
400
+ client: ClientWithCoreApi,
401
+ compositionId: string,
402
+ ): Promise<Composition> {
165
403
  const content = await getContent(client, compositionId);
166
404
  if (!content) throw new Error(`Composition not found: ${compositionId}`);
167
405
  return mapComposition(compositionId, CompositionBcs.parse(content));
168
406
  }
169
407
 
170
408
  /** Extracts the share type `T` from a `Composition<T>` object. */
171
- export async function getCompositionShareType(client: ClientWithCoreApi, compositionId: string): Promise<string> {
409
+ export async function getCompositionShareType(
410
+ client: ClientWithCoreApi,
411
+ compositionId: string,
412
+ ): Promise<string> {
172
413
  const { object } = await client.core.getObject({ objectId: compositionId });
173
414
  return extractTypeParam(object.type);
174
415
  }
@@ -180,12 +421,32 @@ export async function getCompositionByShareType(
180
421
  shareType: string,
181
422
  misoPackageId: string,
182
423
  ): Promise<Composition> {
183
- const type = `${misoPackageId}::composition::Composition<${shareType}>`;
184
- const address = await firstAddressOfType(graphqlClient, type);
185
- if (!address) throw new Error(`Composition not found for share type: ${shareType}`);
424
+ const address = await getCompositionAddressByShareType(
425
+ graphqlClient,
426
+ shareType,
427
+ misoPackageId,
428
+ );
429
+ if (!address)
430
+ throw new Error(`Composition not found for share type: ${shareType}`);
186
431
  return getCompositionById(client, address);
187
432
  }
188
433
 
434
+ /**
435
+ * Resolves a composition share type to its object address.
436
+ *
437
+ * This is the lightweight discovery primitive for callers that need the
438
+ * composition's identity but will read extension fields rather than the core
439
+ * Composition contents.
440
+ */
441
+ export async function getCompositionAddressByShareType(
442
+ graphqlClient: SuiGraphQLClient,
443
+ shareType: string,
444
+ misoPackageId: string,
445
+ ): Promise<string | null> {
446
+ const type = `${misoPackageId}::composition::Composition<${shareType}>`;
447
+ return firstAddressOfType(graphqlClient, type);
448
+ }
449
+
189
450
  export async function getCompositionAdminCapById(
190
451
  client: ClientWithCoreApi,
191
452
  adminCapId: string,
@@ -207,7 +468,10 @@ export async function getOwnedCompositionAdminCaps(
207
468
  misoPackageId: string,
208
469
  ): Promise<CompositionAdminCap[]> {
209
470
  const capType = `${misoPackageId}::composition::CompositionAdminCap`;
210
- const { objects } = await client.core.listOwnedObjects({ owner, type: capType });
471
+ const { objects } = await client.core.listOwnedObjects({
472
+ owner,
473
+ type: capType,
474
+ });
211
475
  const caps: CompositionAdminCap[] = [];
212
476
  for (const obj of objects) {
213
477
  const match = obj.type?.match(/<(.+)>$/);
@@ -216,8 +480,15 @@ export async function getOwnedCompositionAdminCaps(
216
480
  return caps;
217
481
  }
218
482
 
219
- export function deriveCompositionAdminCapId(compositionId: string, misoPackageId: string): string {
220
- return deriveObjectID(compositionId, `${misoPackageId}::composition::CompositionAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
483
+ export function deriveCompositionAdminCapId(
484
+ compositionId: string,
485
+ misoPackageId: string,
486
+ ): string {
487
+ return deriveObjectID(
488
+ compositionId,
489
+ `${misoPackageId}::composition::CompositionAdminCapKey`,
490
+ UNIT_STRUCT_KEY_BYTES,
491
+ );
221
492
  }
222
493
 
223
494
  // ============================================================================
@@ -229,16 +500,25 @@ export async function getRecordingsByIds(
229
500
  recordingIds: string[],
230
501
  ): Promise<Record<string, Recording>> {
231
502
  if (recordingIds.length === 0) return {};
232
- const { objects } = await client.core.getObjects({ objectIds: recordingIds, include: { content: true } });
503
+ const { objects } = await client.core.getObjects({
504
+ objectIds: recordingIds,
505
+ include: { content: true },
506
+ });
233
507
  const out: Record<string, Recording> = {};
234
508
  for (const obj of objects) {
235
509
  if (obj instanceof Error || !obj.content) continue;
236
- out[obj.objectId] = mapRecording(obj.objectId, RecordingBcs.parse(obj.content));
510
+ out[obj.objectId] = mapRecording(
511
+ obj.objectId,
512
+ RecordingBcs.parse(obj.content),
513
+ );
237
514
  }
238
515
  return out;
239
516
  }
240
517
 
241
- export async function getRecordingById(client: ClientWithCoreApi, recordingId: string): Promise<Recording> {
518
+ export async function getRecordingById(
519
+ client: ClientWithCoreApi,
520
+ recordingId: string,
521
+ ): Promise<Recording> {
242
522
  const content = await getContent(client, recordingId);
243
523
  if (!content) throw new Error(`Recording not found: ${recordingId}`);
244
524
  return mapRecording(recordingId, RecordingBcs.parse(content));
@@ -250,8 +530,14 @@ export async function getRecordingById(client: ClientWithCoreApi, recordingId: s
250
530
  * them and returns the first; use {@link getRecordingShareTypes} when the
251
531
  * parent composition's share type is needed too.
252
532
  */
253
- export async function getRecordingShareType(client: ClientWithCoreApi, recordingId: string): Promise<string> {
254
- const [recordingShareType] = await getRecordingShareTypes(client, recordingId);
533
+ export async function getRecordingShareType(
534
+ client: ClientWithCoreApi,
535
+ recordingId: string,
536
+ ): Promise<string> {
537
+ const [recordingShareType] = await getRecordingShareTypes(
538
+ client,
539
+ recordingId,
540
+ );
255
541
  return recordingShareType;
256
542
  }
257
543
 
@@ -275,8 +561,13 @@ export async function getRecordingByShareType(
275
561
  shareType: string,
276
562
  misoPackageId: string,
277
563
  ): Promise<Recording> {
278
- const address = await addressOfRecordingWithShareType(graphqlClient, misoPackageId, shareType);
279
- if (!address) throw new Error(`Recording not found for share type: ${shareType}`);
564
+ const address = await addressOfRecordingWithShareType(
565
+ graphqlClient,
566
+ misoPackageId,
567
+ shareType,
568
+ );
569
+ if (!address)
570
+ throw new Error(`Recording not found for share type: ${shareType}`);
280
571
  return getRecordingById(client, address);
281
572
  }
282
573
 
@@ -302,7 +593,10 @@ export async function getOwnedRecordingAdminCaps(
302
593
  misoPackageId: string,
303
594
  ): Promise<RecordingAdminCap[]> {
304
595
  const capType = `${misoPackageId}::recording::RecordingAdminCap`;
305
- const { objects } = await client.core.listOwnedObjects({ owner, type: capType });
596
+ const { objects } = await client.core.listOwnedObjects({
597
+ owner,
598
+ type: capType,
599
+ });
306
600
  const caps: RecordingAdminCap[] = [];
307
601
  for (const obj of objects) {
308
602
  const match = obj.type?.match(/<(.+)>$/);
@@ -311,8 +605,15 @@ export async function getOwnedRecordingAdminCaps(
311
605
  return caps;
312
606
  }
313
607
 
314
- export function deriveRecordingAdminCapId(recordingId: string, misoPackageId: string): string {
315
- return deriveObjectID(recordingId, `${misoPackageId}::recording::RecordingAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
608
+ export function deriveRecordingAdminCapId(
609
+ recordingId: string,
610
+ misoPackageId: string,
611
+ ): string {
612
+ return deriveObjectID(
613
+ recordingId,
614
+ `${misoPackageId}::recording::RecordingAdminCapKey`,
615
+ UNIT_STRUCT_KEY_BYTES,
616
+ );
316
617
  }
317
618
 
318
619
  // ============================================================================
@@ -320,11 +621,19 @@ export function deriveRecordingAdminCapId(recordingId: string, misoPackageId: st
320
621
  // ============================================================================
321
622
 
322
623
  /** Fetches a deal by its object ID (share types read from the type parameters). */
323
- export async function getDealById(client: ClientWithCoreApi, dealId: string): Promise<Deal> {
324
- const { object } = await client.core.getObject({ objectId: dealId, include: { content: true } });
624
+ export async function getDealById(
625
+ client: ClientWithCoreApi,
626
+ dealId: string,
627
+ ): Promise<Deal> {
628
+ const { object } = await client.core.getObject({
629
+ objectId: dealId,
630
+ include: { content: true },
631
+ });
325
632
  if (!object.content) throw new Error(`Deal not found: ${dealId}`);
326
633
  const d = DealBcs.parse(object.content);
327
- const [recordingShareType, compositionShareType] = extractTypeParams2(object.type);
634
+ const [recordingShareType, compositionShareType] = extractTypeParams2(
635
+ object.type,
636
+ );
328
637
  return {
329
638
  id: dealId,
330
639
  releaseId: d.release_id,
@@ -343,23 +652,44 @@ export async function getReleasesByIds(
343
652
  releaseIds: string[],
344
653
  ): Promise<Record<string, Release>> {
345
654
  if (releaseIds.length === 0) return {};
346
- const { objects } = await client.core.getObjects({ objectIds: releaseIds, include: { content: true } });
655
+ const { objects } = await client.core.getObjects({
656
+ objectIds: releaseIds,
657
+ include: { content: true, json: true },
658
+ });
347
659
  const out: Record<string, Release> = {};
348
660
  for (const obj of objects) {
349
661
  if (obj instanceof Error || !obj.content) continue;
350
- out[obj.objectId] = mapRelease(obj.objectId, ReleaseBcs.parse(obj.content));
662
+ const json = obj.json as { discs?: unknown[] } | null;
663
+ out[obj.objectId] = json?.discs
664
+ ? mapDiscReleaseJson(obj.objectId, json)
665
+ : mapRelease(obj.objectId, ReleaseBcs.parse(obj.content));
351
666
  }
352
667
  return out;
353
668
  }
354
669
 
355
- export async function getReleaseById(client: ClientWithCoreApi, releaseId: string): Promise<Release> {
356
- const content = await getContent(client, releaseId);
357
- if (!content) throw new Error(`Release not found: ${releaseId}`);
358
- return mapRelease(releaseId, ReleaseBcs.parse(content));
670
+ export async function getReleaseById(
671
+ client: ClientWithCoreApi,
672
+ releaseId: string,
673
+ ): Promise<Release> {
674
+ const { object } = await client.core.getObject({
675
+ objectId: releaseId,
676
+ include: { content: true, json: true },
677
+ });
678
+ if (!object.content) throw new Error(`Release not found: ${releaseId}`);
679
+ const json = object.json as { discs?: unknown[] } | null;
680
+ return json?.discs
681
+ ? mapDiscReleaseJson(releaseId, json)
682
+ : mapRelease(releaseId, ReleaseBcs.parse(object.content));
359
683
  }
360
684
 
361
- export async function getReleaseRegistry(client: SuiGraphQLClient, misoPackageId: string): Promise<string> {
362
- const address = await firstAddressOfType(client, `${misoPackageId}::release::ReleaseRegistry`);
685
+ export async function getReleaseRegistry(
686
+ client: SuiGraphQLClient,
687
+ misoPackageId: string,
688
+ ): Promise<string> {
689
+ const address = await firstAddressOfType(
690
+ client,
691
+ `${misoPackageId}::release::ReleaseRegistry`,
692
+ );
363
693
  if (!address) throw new Error("ReleaseRegistry not found");
364
694
  return address;
365
695
  }
@@ -368,9 +698,13 @@ export async function getReleaseAdminCapById(
368
698
  client: ClientWithCoreApi,
369
699
  adminCapId: string,
370
700
  ): Promise<ReleaseAdminCap> {
371
- const { object } = await client.core.getObject({ objectId: adminCapId, include: { json: true } });
701
+ const { object } = await client.core.getObject({
702
+ objectId: adminCapId,
703
+ include: { json: true },
704
+ });
372
705
  const json = object.json as { release_id: string } | null;
373
- if (!json?.release_id) throw new Error(`ReleaseAdminCap not found: ${adminCapId}`);
706
+ if (!json?.release_id)
707
+ throw new Error(`ReleaseAdminCap not found: ${adminCapId}`);
374
708
  return { id: adminCapId, releaseId: json.release_id };
375
709
  }
376
710
 
@@ -380,23 +714,29 @@ export async function getOwnedReleaseAdminCaps(
380
714
  misoPackageId: string,
381
715
  ): Promise<ReleaseAdminCap[]> {
382
716
  const capType = `${misoPackageId}::release::ReleaseAdminCap`;
383
- const { objects } = await client.core.listOwnedObjects({ owner, type: capType });
384
- if (objects.length === 0) return [];
385
- const { objects: details } = await client.core.getObjects({
386
- objectIds: objects.map((o) => o.objectId),
717
+ const { objects } = await client.core.listOwnedObjects({
718
+ owner,
719
+ type: capType,
387
720
  include: { json: true },
388
721
  });
389
722
  const caps: ReleaseAdminCap[] = [];
390
- for (const obj of details) {
391
- if (obj instanceof Error) continue;
723
+ for (const obj of objects) {
392
724
  const json = obj.json as { release_id: string } | null;
393
- if (json?.release_id) caps.push({ id: obj.objectId, releaseId: json.release_id });
725
+ if (json?.release_id)
726
+ caps.push({ id: obj.objectId, releaseId: json.release_id });
394
727
  }
395
728
  return caps;
396
729
  }
397
730
 
398
- export function deriveReleaseAdminCapId(releaseId: string, misoPackageId: string): string {
399
- return deriveObjectID(releaseId, `${misoPackageId}::release::ReleaseAdminCapKey`, UNIT_STRUCT_KEY_BYTES);
731
+ export function deriveReleaseAdminCapId(
732
+ releaseId: string,
733
+ misoPackageId: string,
734
+ ): string {
735
+ return deriveObjectID(
736
+ releaseId,
737
+ `${misoPackageId}::release::ReleaseAdminCapKey`,
738
+ UNIT_STRUCT_KEY_BYTES,
739
+ );
400
740
  }
401
741
 
402
742
  // ============================================================================
@@ -404,7 +744,10 @@ export function deriveReleaseAdminCapId(releaseId: string, misoPackageId: string
404
744
  // ============================================================================
405
745
 
406
746
  /** Extracts the share type `T` from a `Currency<T>` object. */
407
- export async function getShareCurrencyType(client: ClientWithCoreApi, shareCurrencyId: string): Promise<string> {
747
+ export async function getShareCurrencyType(
748
+ client: ClientWithCoreApi,
749
+ shareCurrencyId: string,
750
+ ): Promise<string> {
408
751
  const { object } = await client.core.getObject({ objectId: shareCurrencyId });
409
752
  return extractTypeParam(object.type);
410
753
  }
@@ -444,8 +787,14 @@ export async function getShareCurrencyTreasuryCap(
444
787
  // ============================================================================
445
788
 
446
789
  /** Returns the first object address of a fully-qualified type, or null. */
447
- async function firstAddressOfType(client: SuiGraphQLClient, type: string): Promise<string | null> {
448
- const result = await client.query({ query: AddressesByTypeQuery, variables: { type } });
790
+ async function firstAddressOfType(
791
+ client: SuiGraphQLClient,
792
+ type: string,
793
+ ): Promise<string | null> {
794
+ const result = await client.query({
795
+ query: AddressesByTypeQuery,
796
+ variables: { type },
797
+ });
449
798
  return result.data?.objects?.nodes?.[0]?.address ?? null;
450
799
  }
451
800