@misofm/sdk 0.2.1 → 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/README.md +68 -5
- package/dist/auth.d.ts +73 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +193 -0
- package/dist/auth.js.map +1 -0
- package/dist/contracts/miso_drop/drop.d.ts +550 -0
- package/dist/contracts/miso_drop/drop.d.ts.map +1 -0
- package/dist/contracts/miso_drop/drop.js +523 -0
- package/dist/contracts/miso_drop/drop.js.map +1 -0
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -0
- package/dist/contracts.js.map +1 -1
- package/dist/drop.d.ts +120 -0
- package/dist/drop.d.ts.map +1 -0
- package/dist/drop.js +152 -0
- package/dist/drop.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/read/artist.d.ts +24 -0
- package/dist/read/artist.d.ts.map +1 -0
- package/dist/read/artist.js +114 -0
- package/dist/read/artist.js.map +1 -0
- package/dist/read/catalog.d.ts +63 -0
- package/dist/read/catalog.d.ts.map +1 -0
- package/dist/read/catalog.js +252 -0
- package/dist/read/catalog.js.map +1 -0
- package/dist/read/client.d.ts +36 -0
- package/dist/read/client.d.ts.map +1 -0
- package/dist/read/client.js +46 -0
- package/dist/read/client.js.map +1 -0
- package/dist/read/config.d.ts +83 -0
- package/dist/read/config.d.ts.map +1 -0
- package/dist/read/config.js +76 -0
- package/dist/read/config.js.map +1 -0
- package/dist/read/genres.d.ts +7 -0
- package/dist/read/genres.d.ts.map +1 -0
- package/dist/read/genres.js +38 -0
- package/dist/read/genres.js.map +1 -0
- package/dist/read/index.d.ts +12 -0
- package/dist/read/index.d.ts.map +1 -0
- package/dist/read/index.js +23 -0
- package/dist/read/index.js.map +1 -0
- package/dist/read/internal/scalars.d.ts +23 -0
- package/dist/read/internal/scalars.d.ts.map +1 -0
- package/dist/read/internal/scalars.js +56 -0
- package/dist/read/internal/scalars.js.map +1 -0
- package/dist/read/internal/walrus.d.ts +4 -0
- package/dist/read/internal/walrus.d.ts.map +1 -0
- package/dist/read/internal/walrus.js +23 -0
- package/dist/read/internal/walrus.js.map +1 -0
- package/dist/read/receipts.d.ts +22 -0
- package/dist/read/receipts.d.ts.map +1 -0
- package/dist/read/receipts.js +233 -0
- package/dist/read/receipts.js.map +1 -0
- package/dist/read/types.d.ts +279 -0
- package/dist/read/types.d.ts.map +1 -0
- package/dist/read/types.js +21 -0
- package/dist/read/types.js.map +1 -0
- package/dist/read/wallet.d.ts +58 -0
- package/dist/read/wallet.d.ts.map +1 -0
- package/dist/read/wallet.js +325 -0
- package/dist/read/wallet.js.map +1 -0
- package/dist/read/works.d.ts +31 -0
- package/dist/read/works.d.ts.map +1 -0
- package/dist/read/works.js +106 -0
- package/dist/read/works.js.map +1 -0
- package/package.json +19 -2
- package/src/auth.ts +275 -0
- package/src/contracts/miso_drop/drop.ts +798 -0
- package/src/contracts.ts +4 -0
- package/src/drop.ts +266 -0
- package/src/index.ts +5 -0
- package/src/read/artist.ts +136 -0
- package/src/read/catalog.ts +293 -0
- package/src/read/client.ts +79 -0
- package/src/read/config.ts +164 -0
- package/src/read/genres.ts +37 -0
- package/src/read/index.ts +53 -0
- package/src/read/internal/scalars.ts +57 -0
- package/src/read/internal/walrus.ts +32 -0
- package/src/read/receipts.ts +266 -0
- package/src/read/types.ts +361 -0
- package/src/read/wallet.ts +360 -0
- package/src/read/works.ts +164 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Catalog reads: pressings, releases, covers, credits, tracklists, and the
|
|
5
|
+
// Discover shelf. Everything here is PUBLIC — no address is involved and no two
|
|
6
|
+
// callers get different answers, which is what makes this layer edge-cacheable.
|
|
7
|
+
//
|
|
8
|
+
// The composition of these reads is the point. A pressing page used to cost the
|
|
9
|
+
// browser five sequential round-trips to the chain (drop → release → cover →
|
|
10
|
+
// credits → recordings); the same work happens once here, inside one datacenter,
|
|
11
|
+
// and every subsequent visitor is served from cache.
|
|
12
|
+
|
|
13
|
+
import { getDrop, getCurrentDrop, type DropView } from "../drop.ts";
|
|
14
|
+
import { getReleaseCover, type ReleaseCoverView, type CoverImageRef } from "../cover.ts";
|
|
15
|
+
import { getReleaseCredits, getRecordingCredits, type CreditView } from "../credits.ts";
|
|
16
|
+
import { getReleaseById, isNotFound } from "@misonetwork/sdk";
|
|
17
|
+
import type { Release } from "@misonetwork/sdk";
|
|
18
|
+
import type { MisoClient } from "./client.ts";
|
|
19
|
+
import { getRecordingTitles } from "./works.ts";
|
|
20
|
+
import { gte, int, ms, msOrNull, u64, u64OrNull } from "./internal/scalars.ts";
|
|
21
|
+
import { quiltPatchId, u256ToB64Url } from "./internal/walrus.ts";
|
|
22
|
+
import type {
|
|
23
|
+
Cover,
|
|
24
|
+
CoverImage,
|
|
25
|
+
Credit,
|
|
26
|
+
Currency,
|
|
27
|
+
DiscoverItem,
|
|
28
|
+
DropPreview,
|
|
29
|
+
PressingDetail,
|
|
30
|
+
PressingView,
|
|
31
|
+
Price,
|
|
32
|
+
RecordAlbum,
|
|
33
|
+
ReleaseDetail,
|
|
34
|
+
TrackCredits,
|
|
35
|
+
TrackView,
|
|
36
|
+
WorkState,
|
|
37
|
+
} from "./types.ts";
|
|
38
|
+
|
|
39
|
+
// ── Walrus URLs ──────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/** Aggregator URL for a cover image ref, whichever Walrus variant it is. */
|
|
42
|
+
function imageUrl(aggregator: string, ref: CoverImageRef): string {
|
|
43
|
+
const base = aggregator.replace(/\/$/, "");
|
|
44
|
+
if (ref.kind === "blob") return `${base}/v1/blobs/${u256ToB64Url(ref.blobId)}`;
|
|
45
|
+
const patch = quiltPatchId(ref.quiltId, ref.version, ref.startIndex, ref.endIndex);
|
|
46
|
+
return `${base}/v1/blobs/by-quilt-patch-id/${patch}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toCover(aggregator: string, view: ReleaseCoverView | null): Cover | null {
|
|
50
|
+
if (!view) return null;
|
|
51
|
+
const image = (ref: CoverImageRef): CoverImage => ({ kind: ref.kind, url: imageUrl(aggregator, ref) });
|
|
52
|
+
return { still: image(view.still), animated: view.animated ? image(view.animated) : null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Currency ─────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Symbol + decimals for a coin type. FakeUsd (the testnet dollar) is 6dp; SUI is
|
|
59
|
+
* 9dp; anything else falls back to its module name at 9dp, which is the Sui
|
|
60
|
+
* default and the honest guess for a coin we have no table entry for.
|
|
61
|
+
*/
|
|
62
|
+
export function currencyInfo(type: string | null): Currency {
|
|
63
|
+
if (!type) return { type: null, symbol: "COIN", decimals: 9 };
|
|
64
|
+
if (/::fakeusd::/i.test(type)) return { type, symbol: "FAKEUSD", decimals: 6 };
|
|
65
|
+
if (type === "0x2::sui::SUI") return { type, symbol: "SUI", decimals: 9 };
|
|
66
|
+
return { type, symbol: (type.split("::").pop() ?? "COIN").toUpperCase(), decimals: 9 };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Projections ──────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function toPressingView(drop: DropView): PressingView {
|
|
72
|
+
const quantitySold = u64(drop.quantitySold);
|
|
73
|
+
const maxSupply = u64OrNull(drop.maxSupply);
|
|
74
|
+
const price: Price = { kind: drop.price.kind, amount: u64(drop.price.amount) };
|
|
75
|
+
return {
|
|
76
|
+
id: drop.id,
|
|
77
|
+
releaseId: drop.releaseId,
|
|
78
|
+
edition: int(drop.edition),
|
|
79
|
+
price,
|
|
80
|
+
currency: currencyInfo(drop.currencyType),
|
|
81
|
+
quantitySold,
|
|
82
|
+
maxSupply,
|
|
83
|
+
startTimestampMs: ms(drop.startTimestampMs),
|
|
84
|
+
endTimestampMs: msOrNull(drop.endTimestampMs),
|
|
85
|
+
soldOut: maxSupply !== null && gte(quantitySold, maxSupply),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function toWorkState(state: Release["state"]): WorkState {
|
|
90
|
+
return state.type === "Published" ? { type: "Published", timestampMs: state.timestampMs } : { type: "Initialized" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function toCredits(credits: CreditView[] | null): Credit[] {
|
|
94
|
+
return (credits ?? []).map((c) => ({ partyId: c.partyId, displayName: c.displayName, roles: [...c.roles] }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The release's PRIMARY credits, in chain order — a release's artist line. */
|
|
98
|
+
export function primaryArtistNames(credits: Credit[]): string[] {
|
|
99
|
+
return credits.filter((c) => c.roles.includes("Primary")).map((c) => c.displayName);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Number the protocol's flat tracklist. Display grouping such as discs belongs
|
|
104
|
+
* to metadata extensions and can be layered onto this projection later.
|
|
105
|
+
*/
|
|
106
|
+
function toTracks(release: Release, titles: Record<string, string>): TrackView[] {
|
|
107
|
+
return release.tracks.map((track, index) => ({
|
|
108
|
+
no: `${index + 1}`,
|
|
109
|
+
title: titles[track.recordingId] ?? "Untitled",
|
|
110
|
+
recordingId: track.recordingId,
|
|
111
|
+
splitBps: int(track.splitBps.value),
|
|
112
|
+
disc: 1,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Cover ────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A release's cover, trying the current `release_cover_art` package and then each
|
|
120
|
+
* legacy one in turn.
|
|
121
|
+
*
|
|
122
|
+
* Covers set before the cover_art package split live under a different
|
|
123
|
+
* `CoverArtKey` TYPE (different package address), so a current-package read
|
|
124
|
+
* simply misses them rather than failing. Ported verbatim from miso-app's
|
|
125
|
+
* `lib/pressing.ts:readReleaseCover`.
|
|
126
|
+
*/
|
|
127
|
+
export async function readReleaseCover(client: MisoClient, releaseId: string): Promise<Cover | null> {
|
|
128
|
+
const { releaseCoverArt, legacyReleaseCoverArt } = client.config.protocol;
|
|
129
|
+
for (const pkg of [releaseCoverArt, ...legacyReleaseCoverArt]) {
|
|
130
|
+
const cover = await getReleaseCover(client.protocol, releaseId, pkg).catch(() => null);
|
|
131
|
+
if (cover) return toCover(client.config.walrusAggregatorUrl, cover);
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Release ──────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A release with its cover, credits, and resolved tracklist.
|
|
140
|
+
*
|
|
141
|
+
* Identity (the release object) is HARD — a failure here is a failed read.
|
|
142
|
+
* Decoration (cover, credits) is SOFT: a release with no cover extension set is a
|
|
143
|
+
* normal state, not a broken page, so those reads swallow their errors. That
|
|
144
|
+
* split is inherited from `lib/drops.ts` and is what keeps a half-configured
|
|
145
|
+
* release renderable.
|
|
146
|
+
*/
|
|
147
|
+
export async function getReleaseDetail(client: MisoClient, releaseId: string): Promise<ReleaseDetail> {
|
|
148
|
+
const { releaseCredits } = client.config.protocol;
|
|
149
|
+
|
|
150
|
+
const [release, cover, credits] = await Promise.all([
|
|
151
|
+
getReleaseById(client.protocol, releaseId),
|
|
152
|
+
readReleaseCover(client, releaseId),
|
|
153
|
+
getReleaseCredits(client.protocol, releaseId, releaseCredits).catch(() => null),
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
157
|
+
const titles = await getRecordingTitles(client.protocol, client.graphql, recordingIds, client.config.protocol.miso);
|
|
158
|
+
|
|
159
|
+
const creditViews = toCredits(credits);
|
|
160
|
+
return {
|
|
161
|
+
id: release.id,
|
|
162
|
+
title: release.title,
|
|
163
|
+
subtitle: null,
|
|
164
|
+
state: toWorkState(release.state),
|
|
165
|
+
publishedAtMs: release.state.type === "Published" ? release.state.timestampMs : null,
|
|
166
|
+
cover,
|
|
167
|
+
credits: creditViews,
|
|
168
|
+
primaryArtists: primaryArtistNames(creditViews),
|
|
169
|
+
discCount: release.tracks.length > 0 ? 1 : 0,
|
|
170
|
+
tracks: toTracks(release, titles),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Per-track credits for a release, keyed by recording id. A track with no credits
|
|
176
|
+
* set maps to an empty entry rather than being absent, so a caller can tell
|
|
177
|
+
* "nothing credited" from "no such track".
|
|
178
|
+
*/
|
|
179
|
+
export async function getTrackCredits(client: MisoClient, releaseId: string): Promise<Record<string, TrackCredits>> {
|
|
180
|
+
const { recordingCredits } = client.config.protocol;
|
|
181
|
+
const release = await getReleaseById(client.protocol, releaseId);
|
|
182
|
+
const recordingIds = release.tracks.map((track) => track.recordingId);
|
|
183
|
+
|
|
184
|
+
const entries = await Promise.all(
|
|
185
|
+
recordingIds.map(async (id) => {
|
|
186
|
+
const view = await getRecordingCredits(client.protocol, id, recordingCredits).catch(() => null);
|
|
187
|
+
const credits: TrackCredits = {
|
|
188
|
+
credits: toCredits(view?.credits ?? null),
|
|
189
|
+
primaryArtistIds: view?.primaryArtistIds ?? [],
|
|
190
|
+
featuredArtistIds: view?.featuredArtistIds ?? [],
|
|
191
|
+
};
|
|
192
|
+
return [id, credits] as const;
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
return Object.fromEntries(entries);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Pressing ─────────────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/** Everything the pressing buy page renders. `null` when no such pressing exists. */
|
|
201
|
+
export async function getPressingDetail(client: MisoClient, pressingId: string): Promise<PressingDetail | null> {
|
|
202
|
+
const pressing = await getDrop(client.protocol, pressingId);
|
|
203
|
+
if (!pressing) return null;
|
|
204
|
+
const release = await getReleaseDetail(client, pressing.releaseId);
|
|
205
|
+
return { pressing: toPressingView(pressing), release };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The confirmation preview behind "paste a pressing id to pin it". Verifies the
|
|
210
|
+
* object really is a `Drop` before spending reads on it — a release id or a
|
|
211
|
+
* record id pasted by mistake must come back as `null`, not as a half-built card.
|
|
212
|
+
*/
|
|
213
|
+
export async function getDropPreview(client: MisoClient, dropId: string): Promise<DropPreview | null> {
|
|
214
|
+
const { object } = await client.protocol.core.getObject({ objectId: dropId, include: { content: true } }).catch(() => ({ object: null }));
|
|
215
|
+
if (!object || !/::drop::Drop</.test(object.type ?? "")) return null;
|
|
216
|
+
|
|
217
|
+
const pressing = await getDrop(client.protocol, dropId);
|
|
218
|
+
if (!pressing?.currencyType) return null;
|
|
219
|
+
|
|
220
|
+
const [release, cover] = await Promise.all([
|
|
221
|
+
getReleaseById(client.protocol, pressing.releaseId),
|
|
222
|
+
readReleaseCover(client, pressing.releaseId),
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
pressingId: dropId,
|
|
227
|
+
currency: currencyInfo(pressing.currencyType),
|
|
228
|
+
title: release.title,
|
|
229
|
+
subtitle: null,
|
|
230
|
+
coverUrl: cover?.still.url ?? null,
|
|
231
|
+
price: { kind: pressing.price.kind, amount: u64(pressing.price.amount) },
|
|
232
|
+
trackCount: release.tracks.length,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Discover ─────────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The records currently on sale.
|
|
240
|
+
*
|
|
241
|
+
* Configured by RELEASE id, never by drop id: a release id is permanent, while a
|
|
242
|
+
* drop is superseded (and the old `Drop` destroyed) whenever a new edition opens.
|
|
243
|
+
* Following the release's `CurrentDropKey` pointer keeps this list pointing at the
|
|
244
|
+
* live edition without anyone touching the config.
|
|
245
|
+
*/
|
|
246
|
+
export async function getDiscoverShelf(client: MisoClient): Promise<DiscoverItem[]> {
|
|
247
|
+
const items = await Promise.all(client.config.discoverReleaseIds.map((id) => resolveDiscoverItem(client, id)));
|
|
248
|
+
return items.filter((d): d is DiscoverItem => d !== null);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function resolveDiscoverItem(client: MisoClient, releaseId: string): Promise<DiscoverItem | null> {
|
|
252
|
+
const drop = await getCurrentDrop(client.protocol, {
|
|
253
|
+
releaseId,
|
|
254
|
+
misoDropPackageId: client.config.protocol.drop,
|
|
255
|
+
});
|
|
256
|
+
if (!drop) return null;
|
|
257
|
+
|
|
258
|
+
const [release, cover, credits] = await Promise.all([
|
|
259
|
+
getReleaseById(client.protocol, releaseId),
|
|
260
|
+
readReleaseCover(client, releaseId),
|
|
261
|
+
getReleaseCredits(client.protocol, releaseId, client.config.protocol.releaseCredits).catch(() => null),
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
pressing: toPressingView(drop),
|
|
266
|
+
releaseId,
|
|
267
|
+
title: release.title,
|
|
268
|
+
artist: primaryArtistNames(toCredits(credits)).join(", "),
|
|
269
|
+
coverUrl: cover?.still.url ?? null,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Record → release ─────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* A record's parent release. Both live `Record` struct layouts expose it — card
|
|
277
|
+
* checkout's `{ release_id, number }` and the SDK view's
|
|
278
|
+
* `{ release_id, edition, variant }` — so we probe both spellings.
|
|
279
|
+
*
|
|
280
|
+
* This answer is IMMUTABLE for the life of the record, which is what lets the
|
|
281
|
+
* endpoint in front of it cache for a year.
|
|
282
|
+
*/
|
|
283
|
+
export async function getRecordAlbum(client: MisoClient, recordId: string): Promise<RecordAlbum | null> {
|
|
284
|
+
try {
|
|
285
|
+
const { object } = await client.protocol.core.getObject({ objectId: recordId, include: { json: true } });
|
|
286
|
+
const json = (object?.json ?? {}) as Record<string, unknown>;
|
|
287
|
+
const raw = json.release_id ?? json.releaseId;
|
|
288
|
+
return { recordId, releaseId: typeof raw === "string" && raw ? raw : null };
|
|
289
|
+
} catch (e) {
|
|
290
|
+
if (isNotFound(e)) return null;
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// The one client every read in this SDK goes through.
|
|
5
|
+
//
|
|
6
|
+
// Two transports, because Sui splits the job:
|
|
7
|
+
// core (gRPC) the data plane — objects by id, objects by owner, balances,
|
|
8
|
+
// transactions. Everything cheap and current.
|
|
9
|
+
// graphql the two questions gRPC cannot answer: "which object has type X"
|
|
10
|
+
// (share type → work id, for the studio catalog and the receipt's
|
|
11
|
+
// royalty hop) and "what did this pruned transaction do".
|
|
12
|
+
//
|
|
13
|
+
// It also carries the resolved `MisoConfig`, so a read function takes ONE argument
|
|
14
|
+
// and never has a package id threaded through its signature.
|
|
15
|
+
//
|
|
16
|
+
// The casts below are load-bearing and deliberate: @misonetwork/miso-protocol and
|
|
17
|
+
// @misonetwork/miso-party each type against their own installed copy of
|
|
18
|
+
// @mysten/sui. Those copies are structurally identical but nominally distinct
|
|
19
|
+
// (private class fields), so TypeScript refuses the assignment even though the
|
|
20
|
+
// runtime object is the same one. miso-app solved this with a cast at each call
|
|
21
|
+
// site (`lib/pressing.ts:25`, `lib/party.ts:55`); here it happens ONCE, and
|
|
22
|
+
// nothing downstream of this file casts anything.
|
|
23
|
+
|
|
24
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
25
|
+
import { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
26
|
+
import type { SuiClientRegistration } from "@mysten/sui/client";
|
|
27
|
+
import { party, type PartyClient } from "@misonetwork/miso-party/client";
|
|
28
|
+
import { misoConfig, networkFrom, type MisoConfig, type MisoConfigOverrides, type Network } from "./config.ts";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The structural slice of a Sui client the protocol SDK's read helpers want. They
|
|
32
|
+
* declare `ClientWithCoreApi` against their own @mysten/sui; this alias names the
|
|
33
|
+
* same shape from our side so the one cast in `createMisoClient` is explicit
|
|
34
|
+
* rather than an `any` smeared across the codebase.
|
|
35
|
+
*/
|
|
36
|
+
export type ProtocolClient = Parameters<typeof import("@misonetwork/sdk").getReleaseById>[0];
|
|
37
|
+
|
|
38
|
+
/** A GraphQL client in the shape the protocol SDK's type-discovery queries want. */
|
|
39
|
+
export type ProtocolGraphQLClient = SuiGraphQLClient;
|
|
40
|
+
|
|
41
|
+
export interface MisoClient {
|
|
42
|
+
config: MisoConfig;
|
|
43
|
+
/** gRPC data plane, extended with the party read API (`sui.party.getPartyById(…)`). */
|
|
44
|
+
sui: SuiGrpcClient & { party: PartyClient };
|
|
45
|
+
/** The same client, typed for the protocol SDK's read helpers. */
|
|
46
|
+
protocol: ProtocolClient;
|
|
47
|
+
/** GraphQL RPC, typed for the protocol SDK's type-discovery queries. */
|
|
48
|
+
graphql: ProtocolGraphQLClient;
|
|
49
|
+
/** The raw GraphQL client, for this SDK's own queries (the pruned-transaction read). */
|
|
50
|
+
graphqlRaw: SuiGraphQLClient;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface CreateMisoClientOptions extends MisoConfigOverrides {
|
|
54
|
+
/** "testnet" | "mainnet". A bare string (e.g. a Worker's `NETWORK` var) is accepted. */
|
|
55
|
+
network?: Network | string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build the client for a network. Endpoint overrides let a deployment point at a
|
|
60
|
+
* private fullnode or an indexer without forking the config.
|
|
61
|
+
*/
|
|
62
|
+
export function createMisoClient(options: CreateMisoClientOptions = {}): MisoClient {
|
|
63
|
+
const { network, ...overrides } = options;
|
|
64
|
+
const config = misoConfig(networkFrom(network), overrides);
|
|
65
|
+
|
|
66
|
+
const grpc = new SuiGrpcClient({ baseUrl: config.grpcUrl, network: config.network });
|
|
67
|
+
const graphqlRaw = new SuiGraphQLClient({ url: config.graphqlUrl, network: config.network });
|
|
68
|
+
|
|
69
|
+
const registration = party(config.party) as unknown as SuiClientRegistration<typeof grpc, "party", PartyClient>;
|
|
70
|
+
const sui = grpc.$extend(registration);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
config,
|
|
74
|
+
sui,
|
|
75
|
+
protocol: sui as unknown as ProtocolClient,
|
|
76
|
+
graphql: graphqlRaw as unknown as ProtocolGraphQLClient,
|
|
77
|
+
graphqlRaw,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Per-network on-chain identity: which packages the reads address, which objects
|
|
5
|
+
// they read, and which endpoints they read through. Every id that used to be a
|
|
6
|
+
// hard-coded constant inside miso-app (`lib/party.ts`, `lib/release.ts`,
|
|
7
|
+
// `lib/money.ts`, `lib/drops.ts`) lives here instead, so a redeploy is a change
|
|
8
|
+
// in ONE file that every surface picks up.
|
|
9
|
+
//
|
|
10
|
+
// Testnet values are the deployed ones (verified against chain 2026-08-09).
|
|
11
|
+
// Mainnet is deliberately EMPTY: `misoConfig("mainnet")` throws rather than
|
|
12
|
+
// silently reading testnet ids on a mainnet deployment.
|
|
13
|
+
|
|
14
|
+
export type Network = "testnet" | "mainnet";
|
|
15
|
+
|
|
16
|
+
/** Anything but "mainnet" — including unset — is testnet. Mirrors miso-api's `networkFromEnv`. */
|
|
17
|
+
export function networkFrom(value: string | undefined): Network {
|
|
18
|
+
return value === "mainnet" ? "mainnet" : "testnet";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Package ids for the miso protocol core and the extensions the read layer touches. */
|
|
22
|
+
export interface ProtocolIds {
|
|
23
|
+
/** `miso` core — Composition / Recording / Release, and the origin of every derived admin cap. */
|
|
24
|
+
miso: string;
|
|
25
|
+
/** `miso_drop` — `drop::buy` and the `Drop` objects the pressing pages read. */
|
|
26
|
+
drop: string;
|
|
27
|
+
/** `miso_record` shared `Settings` object — the mint witness authorizer. */
|
|
28
|
+
recordSettings: string;
|
|
29
|
+
/** `release_cover_art` — the release cover extension currently written to. */
|
|
30
|
+
releaseCoverArt: string;
|
|
31
|
+
/**
|
|
32
|
+
* Cover-art packages from before the `cover_art` package split. A cover set
|
|
33
|
+
* under one of these is invisible to a current-package read, so cover reads
|
|
34
|
+
* fall back through this list in order. Drop an entry once every cover it
|
|
35
|
+
* holds has been re-set under `releaseCoverArt`.
|
|
36
|
+
*/
|
|
37
|
+
legacyReleaseCoverArt: readonly string[];
|
|
38
|
+
/** `composition_credits` / `recording_credits` / `release_credits` extensions. */
|
|
39
|
+
compositionCredits: string;
|
|
40
|
+
recordingCredits: string;
|
|
41
|
+
releaseCredits: string;
|
|
42
|
+
/** `miso_credit` — the shared `Credit<Role>` value type credits are built from. */
|
|
43
|
+
credit: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Package ids for PartyOS core and every party extension the artist page reads. */
|
|
47
|
+
export interface PartyIds {
|
|
48
|
+
partyPackageId: string;
|
|
49
|
+
partyProfilePackageId: string;
|
|
50
|
+
countryCodePackageId: string;
|
|
51
|
+
languageCodePackageId: string;
|
|
52
|
+
partyMediaPackageId: string;
|
|
53
|
+
partyRolesPackageId: string;
|
|
54
|
+
partyTagsPackageId: string;
|
|
55
|
+
partyGenrePackageId: string;
|
|
56
|
+
partyCtaPackageId: string;
|
|
57
|
+
partyPlatformLinkPackageId: string;
|
|
58
|
+
partySocialPackageId: string;
|
|
59
|
+
partyMusicPackageId: string;
|
|
60
|
+
partyProLinkPackageId: string;
|
|
61
|
+
partyFeaturedDropPackageId: string;
|
|
62
|
+
genrePackageId: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The currency the app prices records in, and where test dollars come from. */
|
|
66
|
+
export interface MoneyIds {
|
|
67
|
+
/** Coin type balances, drops, and purchases are denominated in. */
|
|
68
|
+
usdCoinType: string;
|
|
69
|
+
usdDecimals: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MisoConfig {
|
|
73
|
+
network: Network;
|
|
74
|
+
protocol: ProtocolIds;
|
|
75
|
+
party: PartyIds;
|
|
76
|
+
money: MoneyIds;
|
|
77
|
+
/** Sui gRPC-web endpoint the data plane reads through. */
|
|
78
|
+
grpcUrl: string;
|
|
79
|
+
/**
|
|
80
|
+
* Sui GraphQL RPC endpoint. Needed for the two things gRPC cannot answer:
|
|
81
|
+
* "which object has type X" (share type → work id) and reading a transaction
|
|
82
|
+
* the fullnode has already pruned.
|
|
83
|
+
*/
|
|
84
|
+
graphqlUrl: string;
|
|
85
|
+
/** Walrus aggregator serving cover art and party media for this network. */
|
|
86
|
+
walrusAggregatorUrl: string;
|
|
87
|
+
/**
|
|
88
|
+
* Public origin of the Miso API, used to build avatar URLs (`/media/avatar/…`).
|
|
89
|
+
* Avatars are an R2-backed API lane, not on-chain data, so the SDK needs the
|
|
90
|
+
* public host to hand back a URL a browser can actually fetch.
|
|
91
|
+
*/
|
|
92
|
+
apiBaseUrl: string;
|
|
93
|
+
/** Releases the Discover shelf lists, in display order. Resolved to their live drop at read time. */
|
|
94
|
+
discoverReleaseIds: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const TESTNET: MisoConfig = {
|
|
98
|
+
network: "testnet",
|
|
99
|
+
protocol: {
|
|
100
|
+
miso: "0x648c9dfba8c800ebd5e608599551f51e1a157942e29d59a1c35e14c44367acf0",
|
|
101
|
+
drop: "0xfc2b51f068dee9d5482d39fa017164f6a8c3601cabb59084a518de5f609ef1c7",
|
|
102
|
+
recordSettings: "0xadf53b5be0d9eea43712b0c73d0ce55d1fb990ba06e35e12a9a371b2dfe3fd35",
|
|
103
|
+
releaseCoverArt: "0x1ee2cae0da7595d7973c310c912434ef531589b468cc57839296810c1385f7f6",
|
|
104
|
+
legacyReleaseCoverArt: ["0x7d4a205a68f8e768a408c9f4c45a4d4076722c5edeba9068c0ac058f554e964e"],
|
|
105
|
+
compositionCredits: "0x25ba72b5737cac577ca8c92d0f1962efbe5d688a1088cfdc580740605086b366",
|
|
106
|
+
recordingCredits: "0xd272b1960e5fae15ae2a5425b910a96619727c85acd0c0095b6089531fdb32a9",
|
|
107
|
+
releaseCredits: "0xed764634b9c9a6af04e3c7f60f657c8e2129baf9b43fb9aee6f3523a423228e4",
|
|
108
|
+
credit: "0x96c82da135bbd534850ca0a2445fdd2531dd4e0947ffa14645a2eb6636d83646",
|
|
109
|
+
},
|
|
110
|
+
party: {
|
|
111
|
+
partyPackageId: "0xd1c0253f837c57bd0a572673a28374e32c118059356e0c9db26784897175197e",
|
|
112
|
+
partyProfilePackageId: "0x6f8841bb252d3ba0f59f9027546be7a0e0a59d84757214df216eb14ef8a9719e",
|
|
113
|
+
countryCodePackageId: "0x6c3a53f228ccd089825d0fb5ee1f0465a4b7a438bc1b1735b4e2f01df8d056e9",
|
|
114
|
+
languageCodePackageId: "0xa18b786807cfc45488691f93aa647800b77cf9993e420d110afd7024c5b15948",
|
|
115
|
+
partyMediaPackageId: "0xc04dbdc05076f642fa8dc79257de9d45ee71fc8fee4a4fe1c05c6bb27e8982ee",
|
|
116
|
+
partyRolesPackageId: "0x18e6d59fe3a6496bd42273fd97e6e4b0ae7d3a8a876a6bbe4f6ae1b6057a6f38",
|
|
117
|
+
partyTagsPackageId: "0x5a5ed50aac1146b34490949ab33fd29dc2ba13ddf1b8f90b4797e67e1301609d",
|
|
118
|
+
partyGenrePackageId: "0x2d489291ce41392ae887e2e048f752a9bd6bea9f540b2c3d2356e36f0e624e29",
|
|
119
|
+
partyCtaPackageId: "0xbf4b04afa5b9c87b7385c6a1f116165a6e3f9bed3e16ea064fed7be4da14db7f",
|
|
120
|
+
partyPlatformLinkPackageId: "0x2bf95053493e640e3641abef2a9d6080ac563da62a5ca8e878e6d3459fdf27c8",
|
|
121
|
+
partySocialPackageId: "0x24bdc5f4cdf68d5cbd6f2b4b791f8b0311060a7fac7e54c10d3a101aa7d6b56d",
|
|
122
|
+
partyMusicPackageId: "0x40d8c48aa7cd8ade4ccfc7eb68de958403b08a897589934c191993b25961c453",
|
|
123
|
+
partyProLinkPackageId: "0xe2f0160eeac627378538b82209c2cd58b6a73827dae46f5f92368a6c7e84ee8d",
|
|
124
|
+
partyFeaturedDropPackageId: "0x5205a9f88702ed2dadd95e4d0096c691a40c96d8bb05d77127f1a62a6eb72cf7",
|
|
125
|
+
genrePackageId: "0x7ee69a2f1440152ecdacad997364fb28fe1fc140fd720e4c6d37ad78e29a376d",
|
|
126
|
+
},
|
|
127
|
+
money: {
|
|
128
|
+
usdCoinType: "0x77774cb7b8cb5622b4ef2658101bf5f1e965418297fe874b683df8f760b6e749::fakeusd::FakeUsd",
|
|
129
|
+
usdDecimals: 6,
|
|
130
|
+
},
|
|
131
|
+
grpcUrl: "https://fullnode.testnet.sui.io",
|
|
132
|
+
graphqlUrl: "https://graphql.testnet.sui.io/graphql",
|
|
133
|
+
walrusAggregatorUrl: "https://aggregator.walrus-testnet.walrus.space",
|
|
134
|
+
apiBaseUrl: "https://api.testnet.miso.fm",
|
|
135
|
+
discoverReleaseIds: [
|
|
136
|
+
// AMIANGELIKA — BLCK SUN
|
|
137
|
+
"0xa16e7ef7af5104690117321c749995db1164db668a5559cf6a0706b26141c327",
|
|
138
|
+
// Gateway Girl — Between the Doors
|
|
139
|
+
"0x7144600de3bb037f23b9371a2bcd5c0d84a80abc381a9d03b00ff7e03f412623",
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Fields a deployment may override without forking the whole config (endpoints, shelf). */
|
|
144
|
+
export type MisoConfigOverrides = Partial<
|
|
145
|
+
Pick<MisoConfig, "grpcUrl" | "graphqlUrl" | "walrusAggregatorUrl" | "apiBaseUrl" | "discoverReleaseIds">
|
|
146
|
+
>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The config for `network`. Mainnet throws until its packages are deployed and
|
|
150
|
+
* filled in here — a mainnet worker must fail loudly, never read testnet ids.
|
|
151
|
+
*/
|
|
152
|
+
export function misoConfig(network: Network, overrides: MisoConfigOverrides = {}): MisoConfig {
|
|
153
|
+
if (network === "mainnet") {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"@misofm/sdk/read: no mainnet configuration yet. Deploy the Move packages, then fill in the mainnet block in src/read/config.ts.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return { ...TESTNET, ...stripUndefined(overrides) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** `{ a: undefined }` must not clobber a real default when spread. */
|
|
162
|
+
function stripUndefined<T extends object>(o: T): Partial<T> {
|
|
163
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>;
|
|
164
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Genre name resolution. `party.getGenres` returns `Genre` OBJECT IDS; each Genre
|
|
5
|
+
// object carries a screaming-snake name ("HIP_HOP"). There is no id→name table
|
|
6
|
+
// anywhere off-chain, so the names are read from the objects and humanized.
|
|
7
|
+
|
|
8
|
+
import type { MisoClient } from "./client.ts";
|
|
9
|
+
|
|
10
|
+
/** "HIP_HOP" → "Hip Hop". */
|
|
11
|
+
function humanize(name: string): string {
|
|
12
|
+
return name
|
|
13
|
+
.split("_")
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((w) => w.charAt(0) + w.slice(1).toLowerCase())
|
|
16
|
+
.join(" ");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve `Genre` object ids to display names. Best-effort: an id that fails to
|
|
21
|
+
* read is dropped rather than failing the artist page around it.
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveGenreNames(client: MisoClient, ids: string[]): Promise<string[]> {
|
|
24
|
+
if (ids.length === 0) return [];
|
|
25
|
+
try {
|
|
26
|
+
const { objects } = await client.protocol.core.getObjects({ objectIds: ids, include: { json: true } });
|
|
27
|
+
const out: string[] = [];
|
|
28
|
+
for (const o of objects) {
|
|
29
|
+
if (o instanceof Error) continue;
|
|
30
|
+
const name = (o.json as { name?: unknown } | null)?.name;
|
|
31
|
+
if (typeof name === "string" && name) out.push(humanize(name));
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// @misofm/sdk/read — Miso's high-level platform read surface.
|
|
5
|
+
//
|
|
6
|
+
// One place that knows how to turn Sui objects into the things Miso talks about:
|
|
7
|
+
// a pressing, a release, an artist, a library, a receipt. It composes
|
|
8
|
+
// @misonetwork/miso-protocol and @misonetwork/miso-party, owns the per-network id
|
|
9
|
+
// manifest, and returns view types that survive JSON.
|
|
10
|
+
//
|
|
11
|
+
// Browser- and server-compatible. The HTTP API is a thin cached transport over
|
|
12
|
+
// this same surface; direct clients can use it without going through the API.
|
|
13
|
+
//
|
|
14
|
+
// const miso = createMisoClient({ network: "testnet" })
|
|
15
|
+
// const pressing = await getPressingDetail(miso, pressingId)
|
|
16
|
+
|
|
17
|
+
export { createMisoClient } from "./client.ts";
|
|
18
|
+
export type { MisoClient, CreateMisoClientOptions } from "./client.ts";
|
|
19
|
+
|
|
20
|
+
export { misoConfig, networkFrom } from "./config.ts";
|
|
21
|
+
export type { MisoConfig, MisoConfigOverrides, MoneyIds, Network, PartyIds, ProtocolIds } from "./config.ts";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
currencyInfo,
|
|
25
|
+
getDiscoverShelf,
|
|
26
|
+
getDropPreview,
|
|
27
|
+
getPressingDetail,
|
|
28
|
+
getRecordAlbum,
|
|
29
|
+
getReleaseDetail,
|
|
30
|
+
getTrackCredits,
|
|
31
|
+
primaryArtistNames,
|
|
32
|
+
readReleaseCover,
|
|
33
|
+
} from "./catalog.ts";
|
|
34
|
+
|
|
35
|
+
export { getArtistProfile, getFeaturedRelease, getPartySummaries, partyAvatarUrl } from "./artist.ts";
|
|
36
|
+
export type { ArtistInclude, GetArtistOptions } from "./artist.ts";
|
|
37
|
+
|
|
38
|
+
export { resolveGenreNames } from "./genres.ts";
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
RECORD_TYPE_SUFFIX,
|
|
42
|
+
getBalance,
|
|
43
|
+
getOwnedParties,
|
|
44
|
+
getOwnedRecords,
|
|
45
|
+
getOwnedWorks,
|
|
46
|
+
getWorkByCap,
|
|
47
|
+
ownsParty,
|
|
48
|
+
ownsRecord,
|
|
49
|
+
} from "./wallet.ts";
|
|
50
|
+
|
|
51
|
+
export { breakdown, getPurchaseReceipt } from "./receipts.ts";
|
|
52
|
+
|
|
53
|
+
export type * from "./types.ts";
|