@misofm/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +248 -0
  3. package/dist/catalog.d.ts +31 -0
  4. package/dist/catalog.d.ts.map +1 -0
  5. package/dist/catalog.js +45 -0
  6. package/dist/catalog.js.map +1 -0
  7. package/dist/client.d.ts +134 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +113 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/contracts/miso_pressing/certificate.d.ts +78 -0
  12. package/dist/contracts/miso_pressing/certificate.d.ts.map +1 -0
  13. package/dist/contracts/miso_pressing/certificate.js +91 -0
  14. package/dist/contracts/miso_pressing/certificate.js.map +1 -0
  15. package/dist/contracts/miso_pressing/deps/std/type_name.d.ts +18 -0
  16. package/dist/contracts/miso_pressing/deps/std/type_name.d.ts.map +1 -0
  17. package/dist/contracts/miso_pressing/deps/std/type_name.js +20 -0
  18. package/dist/contracts/miso_pressing/deps/std/type_name.js.map +1 -0
  19. package/dist/contracts/miso_pressing/listing.d.ts +449 -0
  20. package/dist/contracts/miso_pressing/listing.d.ts.map +1 -0
  21. package/dist/contracts/miso_pressing/listing.js +416 -0
  22. package/dist/contracts/miso_pressing/listing.js.map +1 -0
  23. package/dist/contracts/miso_pressing/pressing.d.ts +365 -0
  24. package/dist/contracts/miso_pressing/pressing.d.ts.map +1 -0
  25. package/dist/contracts/miso_pressing/pressing.js +335 -0
  26. package/dist/contracts/miso_pressing/pressing.js.map +1 -0
  27. package/dist/contracts/utils/index.d.ts +31 -0
  28. package/dist/contracts/utils/index.d.ts.map +1 -0
  29. package/dist/contracts/utils/index.js +162 -0
  30. package/dist/contracts/utils/index.js.map +1 -0
  31. package/dist/execute.d.ts +17 -0
  32. package/dist/execute.d.ts.map +1 -0
  33. package/dist/execute.js +21 -0
  34. package/dist/execute.js.map +1 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +27 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/pressing.d.ts +223 -0
  40. package/dist/pressing.d.ts.map +1 -0
  41. package/dist/pressing.js +280 -0
  42. package/dist/pressing.js.map +1 -0
  43. package/dist/queries.d.ts +2 -0
  44. package/dist/queries.d.ts.map +1 -0
  45. package/dist/queries.js +8 -0
  46. package/dist/queries.js.map +1 -0
  47. package/dist/release-graph.d.ts +93 -0
  48. package/dist/release-graph.d.ts.map +1 -0
  49. package/dist/release-graph.js +143 -0
  50. package/dist/release-graph.js.map +1 -0
  51. package/dist/share-template.d.ts +3 -0
  52. package/dist/share-template.d.ts.map +1 -0
  53. package/dist/share-template.js +46 -0
  54. package/dist/share-template.js.map +1 -0
  55. package/dist/share.d.ts +66 -0
  56. package/dist/share.d.ts.map +1 -0
  57. package/dist/share.js +156 -0
  58. package/dist/share.js.map +1 -0
  59. package/dist/transactions.d.ts +177 -0
  60. package/dist/transactions.d.ts.map +1 -0
  61. package/dist/transactions.js +237 -0
  62. package/dist/transactions.js.map +1 -0
  63. package/package.json +99 -0
  64. package/src/catalog.ts +77 -0
  65. package/src/client.ts +249 -0
  66. package/src/contracts/miso_pressing/certificate.ts +129 -0
  67. package/src/contracts/miso_pressing/deps/std/type_name.ts +22 -0
  68. package/src/contracts/miso_pressing/listing.ts +621 -0
  69. package/src/contracts/miso_pressing/pressing.ts +454 -0
  70. package/src/contracts/utils/index.ts +229 -0
  71. package/src/execute.ts +34 -0
  72. package/src/index.ts +29 -0
  73. package/src/pressing.ts +497 -0
  74. package/src/queries.ts +9 -0
  75. package/src/release-graph.ts +279 -0
  76. package/src/share-template.ts +55 -0
  77. package/src/share.ts +258 -0
  78. package/src/transactions.ts +440 -0
package/src/execute.ts ADDED
@@ -0,0 +1,34 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // `executeViaExecutor` — the one piece of transaction execution that's specific
5
+ // to this package's opinionated publish flows (batched, non-idempotent share-
6
+ // currency publishing in `share.ts`). Everything else — `buildTx`,
7
+ // `signAndExecute`, `toExecResult`, the object-change extractors — is generic
8
+ // PTB execution plumbing that stays in `@misonetwork/sdk`'s `execute.ts` and is
9
+ // re-exported from there; this module builds on top of it.
10
+
11
+ import type { ParallelTransactionExecutor } from "@mysten/sui/transactions";
12
+ import type { SuiClientTypes } from "@mysten/sui/client";
13
+ import { buildTx, toExecResult, FULL_INCLUDE, type ExecResult } from "@misonetwork/sdk";
14
+ import type { TxThunk } from "./transactions.ts";
15
+
16
+ type FullInclude = typeof FULL_INCLUDE;
17
+
18
+ /**
19
+ * Builds a transaction from thunks and executes it through the parallel executor,
20
+ * exactly ONCE.
21
+ *
22
+ * These PTBs are typically non-idempotent (publish a package, initialize a
23
+ * currency, consume a TreasuryCap). A thrown transport error is ambiguous — the tx
24
+ * may already have committed — so blindly rebuilding and re-submitting would risk
25
+ * double-execution or a guaranteed abort against already-consumed inputs. We
26
+ * therefore do NOT auto-retry: any error propagates and recovery is the caller's
27
+ * job (e.g. a resumable checkpoint that reconciles against on-chain state). A Move
28
+ * abort RESOLVES as a `FailedTransaction`, so `toExecResult` surfaces it too.
29
+ */
30
+ export async function executeViaExecutor(executor: ParallelTransactionExecutor, ...thunks: TxThunk[]): Promise<ExecResult> {
31
+ const tx = await buildTx(...thunks);
32
+ const res: SuiClientTypes.TransactionResult<FullInclude> = await executor.executeTransaction(tx, FULL_INCLUDE);
33
+ return toExecResult(res);
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // `@misofm/sdk` — the PLATFORM layer.
5
+ //
6
+ // Miso's own product sits on top of the permissionless protocol, and the two
7
+ // have different names on npm because they have different promises:
8
+ //
9
+ // @misonetwork/* PROTOCOL — Composition, Recording, Release. The open layer
10
+ // anyone can build on, and nobody needs Miso's permission to use.
11
+ // @misofm/* PLATFORM — Pressing, Listing, Record. How MISO sells copies of
12
+ // a release: the production line, its currencies, and the record
13
+ // a buyer walks away with.
14
+ //
15
+ // A release is protocol. Pressing a record off it and selling that record is
16
+ // platform. Keeping the split at the package boundary is what stops the protocol
17
+ // from quietly growing a storefront.
18
+
19
+ // The recommended entry point is the client extension (see ./client.ts); the bare
20
+ // builders and readers stay exported for callers that hold ids themselves.
21
+ export { misoPlatform, MisoPlatformClient } from "./client.ts";
22
+ export type { MisoPlatformConfig } from "./client.ts";
23
+ export * from "./pressing.ts";
24
+ export * from "./transactions.ts";
25
+ export * from "./execute.ts";
26
+ export * from "./share.ts";
27
+ export * from "./share-template.ts";
28
+ export * from "./release-graph.ts";
29
+ export * from "./catalog.ts";
@@ -0,0 +1,497 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Pressing — the record production line. A release has exactly ONE `Pressing`: a
5
+ // single uncapped run whose counter numbers every copy that release will ever sell,
6
+ // forever. There are no editions, no supply caps, no sold-out state, and no expiry —
7
+ // the run is `Scheduled → Active → Paused → Active` and nothing else. Selling in a
8
+ // currency is a `Listing<Currency>` derived off the pressing: one per currency, ever,
9
+ // permanent, edited in place rather than replaced. A sale needs both switches open.
10
+ //
11
+ // EVERYTHING IS ADDRESS MATH. The pressing's UID derives off its release's, each
12
+ // listing's off the pressing's. So there is no registry and no pointer to follow:
13
+ // `derivePressingId`/`deriveListingId` answer "where is it" offline, which is why the
14
+ // builders here take a RELEASE id and compute the rest. A caller cannot pass a
15
+ // listing that belongs to some other pressing, because it never picks one.
16
+
17
+ import type { ClientWithCoreApi } from "@mysten/sui/client";
18
+ import { deriveObjectID, normalizeStructTag } from "@mysten/sui/utils";
19
+ import type { TxThunk } from "./transactions.ts";
20
+ import { isNotFound } from "./queries.ts";
21
+ import * as listing from "./contracts/miso_pressing/listing.ts";
22
+ import * as pressing from "./contracts/miso_pressing/pressing.ts";
23
+
24
+ /** Pricing policy: pay exactly `amount` (fixed) or at least `amount` (floor). */
25
+ export type ListingPrice = { kind: "fixed" | "floor"; amount: bigint | number | string };
26
+
27
+ /** Whether a currency's listing takes payment. Below the pressing's own run state. */
28
+ export type ListingSwitch = "enabled" | "disabled";
29
+
30
+ /**
31
+ * The run's state, across every currency at once.
32
+ *
33
+ * `scheduled` is transitional: the first sale past `startTimestampMs` rewrites it to
34
+ * `active` inside `mint_next`, and the start time leaves the object with it. "When was
35
+ * this run scheduled for" is an event-log question once a run has sold anything.
36
+ */
37
+ export type PressingRunState =
38
+ | { kind: "scheduled"; startTimestampMs: bigint | number }
39
+ | { kind: "active" }
40
+ | { kind: "paused" };
41
+
42
+ // ============================================================================
43
+ // Address math
44
+ // ============================================================================
45
+
46
+ /** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
47
+ const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
48
+
49
+ /**
50
+ * Where `releaseId`'s pressing lives — computable before it has been opened, and
51
+ * stable forever after, because a release can only ever claim one.
52
+ *
53
+ * `misoPressingPackageId` must be the package's ORIGINAL (first-published) id, since
54
+ * that is the address a Move type tag canonicalises to. `miso_pressing` is published
55
+ * immutable, so its original and current ids are the same.
56
+ */
57
+ export function derivePressingId(releaseId: string, misoPressingPackageId: string): string {
58
+ return deriveObjectID(releaseId, `${misoPressingPackageId}::pressing::PressingKey`, UNIT_STRUCT_KEY_BYTES);
59
+ }
60
+
61
+ /**
62
+ * Where `pressingId`'s admin cap was minted. The cap is transferable, so this is not
63
+ * where it lives — only where it came from.
64
+ */
65
+ export function derivePressingAdminCapId(pressingId: string, misoPressingPackageId: string): string {
66
+ return deriveObjectID(
67
+ pressingId,
68
+ `${misoPressingPackageId}::pressing::PressingAdminCapKey`,
69
+ UNIT_STRUCT_KEY_BYTES,
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Where `currencyType`'s listing on `pressingId` lives. The key is phantom-typed, so
75
+ * the currency rides in the key's TYPE — one listing per (pressing, currency), ever.
76
+ */
77
+ export function deriveListingId(
78
+ pressingId: string,
79
+ currencyType: string,
80
+ misoPressingPackageId: string,
81
+ ): string {
82
+ return deriveObjectID(
83
+ pressingId,
84
+ `${misoPressingPackageId}::listing::ListingKey<${normalizeStructTag(currencyType)}>`,
85
+ UNIT_STRUCT_KEY_BYTES,
86
+ );
87
+ }
88
+
89
+ /** Both derived ids a sale needs, from the release id alone. */
90
+ export function deriveSaleIds(
91
+ releaseId: string,
92
+ currencyType: string,
93
+ misoPressingPackageId: string,
94
+ ): { pressingId: string; listingId: string } {
95
+ const pressingId = derivePressingId(releaseId, misoPressingPackageId);
96
+ return { pressingId, listingId: deriveListingId(pressingId, currencyType, misoPressingPackageId) };
97
+ }
98
+
99
+ // ============================================================================
100
+ // Term constructors (shared by the builders below)
101
+ // ============================================================================
102
+
103
+ type Tx = Parameters<TxThunk>[0];
104
+
105
+ function priceArg(tx: Tx, misoPressingPackageId: string, price: ListingPrice) {
106
+ const make = price.kind === "fixed" ? listing.newFixedPrice : listing.newFloorPrice;
107
+ return tx.add(make({ package: misoPressingPackageId, arguments: [BigInt(price.amount)] }));
108
+ }
109
+
110
+ function switchArg(tx: Tx, misoPressingPackageId: string, state: ListingSwitch) {
111
+ const make = state === "enabled" ? listing.newEnabledState : listing.newDisabledState;
112
+ return tx.add(make({ package: misoPressingPackageId }));
113
+ }
114
+
115
+ function runStateArg(tx: Tx, misoPressingPackageId: string, state: PressingRunState) {
116
+ const pkg = { package: misoPressingPackageId };
117
+ switch (state.kind) {
118
+ case "scheduled":
119
+ return tx.add(pressing.newScheduledState({ ...pkg, arguments: [BigInt(state.startTimestampMs)] }));
120
+ case "active":
121
+ return tx.add(pressing.newActiveState(pkg));
122
+ case "paused":
123
+ return tx.add(pressing.newPausedState(pkg));
124
+ }
125
+ }
126
+
127
+ // ============================================================================
128
+ // Opening the run
129
+ // ============================================================================
130
+
131
+ /** One currency's offer, as `openPressing` takes it. */
132
+ export interface ListingTerms {
133
+ /** The `Currency` type argument, e.g. `0x2::sui::SUI`. */
134
+ currencyType: string;
135
+ price: ListingPrice;
136
+ /** Defaults to `"enabled"` — a listing opened disabled takes no payment yet. */
137
+ state?: ListingSwitch;
138
+ }
139
+
140
+ export interface OpenPressingParams {
141
+ /** The `Release` this run presses copies of — also the pressing's derivation parent. */
142
+ releaseId: string;
143
+ /** The release's `ReleaseAdminCap`. Needed ONLY to open the run; every later change
144
+ * authorizes against the `PressingAdminCap` this mints. */
145
+ releaseAdminCapId: string;
146
+ /** Currencies to open the run in. May be empty — listings can be added later with
147
+ * {@link openListing} — but a run with no listings sells nothing. */
148
+ listings: ListingTerms[];
149
+ /** Run state at open. Defaults to `active` (sell immediately); `scheduled` is the
150
+ * trustless drop moment, and needs no later transaction to open. */
151
+ state?: PressingRunState;
152
+ /** Where to send the minted `PressingAdminCap` (usually the artist or their vault). */
153
+ adminCapRecipient: string;
154
+ misoPressingPackageId: string;
155
+ }
156
+
157
+ /**
158
+ * Opens a release's pressing and lists it, in ONE transaction.
159
+ *
160
+ * `pressing::new` hands the `Pressing` back UNSHARED precisely so the listings can be
161
+ * claimed off it before `share` puts it on chain — that ordering is the whole reason
162
+ * this is a single builder and not three. A release can only ever open one pressing,
163
+ * so this call is once-per-release forever; changing price or currency afterwards is
164
+ * an edit ({@link setListingPrice}, {@link openListing}), never a re-open.
165
+ */
166
+ export function openPressing(p: OpenPressingParams): TxThunk {
167
+ return (tx) => {
168
+ const pkg = p.misoPressingPackageId;
169
+ const [run, adminCap] = tx.add(
170
+ pressing._new({
171
+ package: pkg,
172
+ arguments: [p.releaseId, p.releaseAdminCapId, runStateArg(tx, pkg, p.state ?? { kind: "active" })],
173
+ }),
174
+ );
175
+ for (const terms of p.listings) {
176
+ tx.add(
177
+ listing._new({
178
+ package: pkg,
179
+ arguments: [
180
+ run!,
181
+ adminCap!,
182
+ priceArg(tx, pkg, terms.price),
183
+ switchArg(tx, pkg, terms.state ?? "enabled"),
184
+ ],
185
+ typeArguments: [terms.currencyType],
186
+ }),
187
+ );
188
+ }
189
+ tx.add(pressing.share({ package: pkg, arguments: [run!] }));
190
+ tx.transferObjects([adminCap!], p.adminCapRecipient);
191
+ };
192
+ }
193
+
194
+ export interface OpenListingParams {
195
+ /** The release whose pressing gains a currency. The pressing id derives from it. */
196
+ releaseId: string;
197
+ pressingAdminCapId: string;
198
+ terms: ListingTerms;
199
+ misoPressingPackageId: string;
200
+ }
201
+
202
+ /**
203
+ * Adds a currency to a live pressing. Once per currency, ever — the slot is a derived
204
+ * claim, so a second call for the same currency aborts. Everything after is an edit.
205
+ */
206
+ export function openListing(p: OpenListingParams): TxThunk {
207
+ return (tx) => {
208
+ const pkg = p.misoPressingPackageId;
209
+ tx.add(
210
+ listing._new({
211
+ package: pkg,
212
+ arguments: [
213
+ derivePressingId(p.releaseId, pkg),
214
+ p.pressingAdminCapId,
215
+ priceArg(tx, pkg, p.terms.price),
216
+ switchArg(tx, pkg, p.terms.state ?? "enabled"),
217
+ ],
218
+ typeArguments: [p.terms.currencyType],
219
+ }),
220
+ );
221
+ };
222
+ }
223
+
224
+ // ============================================================================
225
+ // Editing the offer
226
+ // ============================================================================
227
+
228
+ export interface SetListingPriceParams {
229
+ releaseId: string;
230
+ currencyType: string;
231
+ pressingAdminCapId: string;
232
+ price: ListingPrice;
233
+ misoPressingPackageId: string;
234
+ }
235
+
236
+ /**
237
+ * Reprices one currency in place, keeping the listing's address and identity. Safe
238
+ * against in-flight buys by construction: a `fixed` buyer pays exactly, so a stale
239
+ * payment aborts against the new price, and a `floor` buyer never pays more than the
240
+ * balance they sent.
241
+ */
242
+ export function setListingPrice(p: SetListingPriceParams): TxThunk {
243
+ return (tx) => {
244
+ const pkg = p.misoPressingPackageId;
245
+ const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
246
+ tx.add(
247
+ listing.setPrice({
248
+ package: pkg,
249
+ arguments: [listingId, p.pressingAdminCapId, priceArg(tx, pkg, p.price)],
250
+ typeArguments: [p.currencyType],
251
+ }),
252
+ );
253
+ };
254
+ }
255
+
256
+ export interface SetListingStateParams {
257
+ releaseId: string;
258
+ currencyType: string;
259
+ pressingAdminCapId: string;
260
+ state: ListingSwitch;
261
+ misoPressingPackageId: string;
262
+ }
263
+
264
+ /** Stops or resumes ONE currency. The run's other currencies carry on. */
265
+ export function setListingState(p: SetListingStateParams): TxThunk {
266
+ return (tx) => {
267
+ const pkg = p.misoPressingPackageId;
268
+ const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
269
+ tx.add(
270
+ listing.setState({
271
+ package: pkg,
272
+ arguments: [listingId, p.pressingAdminCapId, switchArg(tx, pkg, p.state)],
273
+ typeArguments: [p.currencyType],
274
+ }),
275
+ );
276
+ };
277
+ }
278
+
279
+ export interface SetPressingStateParams {
280
+ releaseId: string;
281
+ pressingAdminCapId: string;
282
+ state: PressingRunState;
283
+ misoPressingPackageId: string;
284
+ }
285
+
286
+ /**
287
+ * Moves the whole run between its three modes, over every currency at once. There is
288
+ * no "end" — ending a campaign is `{ kind: "paused" }`.
289
+ */
290
+ export function setPressingState(p: SetPressingStateParams): TxThunk {
291
+ return (tx) => {
292
+ const pkg = p.misoPressingPackageId;
293
+ tx.add(
294
+ pressing.setState({
295
+ package: pkg,
296
+ arguments: [
297
+ derivePressingId(p.releaseId, pkg),
298
+ p.pressingAdminCapId,
299
+ runStateArg(tx, pkg, p.state),
300
+ ],
301
+ }),
302
+ );
303
+ };
304
+ }
305
+
306
+ // ============================================================================
307
+ // Buying
308
+ // ============================================================================
309
+
310
+ export interface BuyRecordParams {
311
+ /**
312
+ * The `Release` being bought a copy of. The pressing and this currency's listing
313
+ * BOTH derive from it (see {@link deriveSaleIds}), so there is nothing to look up
314
+ * and no way to pair a listing with the wrong pressing.
315
+ */
316
+ releaseId: string;
317
+ /** Amount to pay in the currency's smallest unit (exact for `fixed`, ≥ for `floor`). */
318
+ amount: bigint | number | string;
319
+ /** The listing's `Currency` type, e.g. `0x2::sui::SUI`. */
320
+ currencyType: string;
321
+ /** The `miso_record` `Settings` shared object — it must authorize `miso_pressing`'s
322
+ * `MintWitness`, or the mint aborts. */
323
+ settingsId: string;
324
+ /** Where to send the pressed `Record` (usually the buyer). */
325
+ recipient: string;
326
+ misoPressingPackageId: string;
327
+ /**
328
+ * Set only when the buyer pays their OWN gas. Miso sponsors every purchase — the gas
329
+ * coin is the sponsor's, so drawing a payment out of it would spend the wrong
330
+ * wallet's money. Defaults to sponsored; bites only on SUI-priced listings.
331
+ */
332
+ useGasCoin?: boolean;
333
+ }
334
+
335
+ /**
336
+ * Buys one record: sources the payment, calls `listing::buy`, and sends the pressed
337
+ * `Record` to `recipient`. `buy` RETURNS the record rather than transferring it, so
338
+ * the transfer here is not optional. The `Clock` is auto-supplied.
339
+ *
340
+ * ONE payment path, no coin picking. `tx.balance()` resolves the money at build time,
341
+ * preferring the buyer's ADDRESS BALANCE — where value is assumed to live — and falling
342
+ * back to coin objects inside the SDK if it must. Callers never enumerate coins: that
343
+ * road showed a buyer their $1000 and then refused to spend a cent of it, because the
344
+ * listing only saw `Coin<T>` objects and the money was in the address balance.
345
+ *
346
+ * `listing::buy` takes a bare `Balance<Currency>`, which is what makes that resolution
347
+ * cheap: when the address balance covers the price, `tx.balance()` emits a single
348
+ * `balance::redeem_funds` straight off the accumulator and no coin object is minted,
349
+ * touched, or destroyed — leaving the sale free of any owned-object contention. Only
350
+ * when it must fall back to coins does it merge, split, and `coin::into_balance`.
351
+ */
352
+ export function buyRecord(p: BuyRecordParams): TxThunk {
353
+ return (tx) => {
354
+ const pkg = p.misoPressingPackageId;
355
+ const { pressingId, listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
356
+ const payment = tx.balance({
357
+ type: p.currencyType,
358
+ balance: BigInt(p.amount),
359
+ useGasCoin: p.useGasCoin ?? false,
360
+ });
361
+ const record = tx.add(
362
+ listing.buy({
363
+ package: pkg,
364
+ arguments: [listingId, pressingId, payment, p.settingsId],
365
+ typeArguments: [p.currencyType],
366
+ }),
367
+ );
368
+ tx.transferObjects([record], p.recipient);
369
+ };
370
+ }
371
+
372
+ // ============================================================================
373
+ // Reads
374
+ // ============================================================================
375
+
376
+ /** A parsed `Pressing` — the run itself. */
377
+ export interface PressingView {
378
+ id: string;
379
+ releaseId: string;
380
+ state: PressingRunState & { startTimestampMs?: bigint };
381
+ /** Records pressed so far; also the most recent number. Not a cap — nothing counts
382
+ * against it, and there is no sold-out state to reach. */
383
+ supply: bigint;
384
+ }
385
+
386
+ /** A parsed `Listing<Currency>` — one currency's offer on the run. */
387
+ export interface ListingView {
388
+ id: string;
389
+ releaseId: string;
390
+ pressingId: string;
391
+ price: { kind: "fixed" | "floor"; amount: bigint };
392
+ state: ListingSwitch;
393
+ /** The `Currency` type, parsed from the object's type tag (`null` if unavailable). */
394
+ currencyType: string | null;
395
+ }
396
+
397
+ /** Extracts `CURRENCY` from a `…::listing::Listing<CURRENCY>` type tag. */
398
+ function currencyFromType(type: string | null | undefined): string | null {
399
+ if (!type) return null;
400
+ const lt = type.indexOf("<");
401
+ return lt >= 0 && type.endsWith(">") ? type.slice(lt + 1, -1) : null;
402
+ }
403
+
404
+ /** Reads one object's BCS content + type, or `null` when it does not exist. */
405
+ async function fetch(
406
+ client: ClientWithCoreApi,
407
+ objectId: string,
408
+ ): Promise<{ content: Uint8Array; type?: string } | null> {
409
+ try {
410
+ const { object } = await client.core.getObject({ objectId, include: { content: true } });
411
+ return object?.content ? { content: object.content, type: object.type } : null;
412
+ } catch (e) {
413
+ if (isNotFound(e)) return null;
414
+ throw e;
415
+ }
416
+ }
417
+
418
+ // The generated `MoveEnum` parsers emit `{ $kind, <Variant>: … }`; older ones omitted
419
+ // `$kind`. Both shapes are accepted so a codegen bump cannot silently mis-read a price.
420
+ function variant<T extends string>(v: { $kind?: string } & Record<string, unknown>, ...names: T[]): T {
421
+ for (const name of names) if (v.$kind === name || v[name] != null) return name;
422
+ return names[0]!;
423
+ }
424
+
425
+ /**
426
+ * Fetches and parses a `Pressing`, or `null` if the release has never opened one.
427
+ *
428
+ * NOTE: like the old drop reader, this deliberately deviates from the core-getter
429
+ * convention in `./queries.ts` (throw on missing object). A pressing's address is
430
+ * computable for ANY release, so probing one that was never opened is a normal state,
431
+ * not a broken reference — absence is `null` and only transport failures throw.
432
+ */
433
+ export async function getPressing(client: ClientWithCoreApi, pressingId: string): Promise<PressingView | null> {
434
+ const got = await fetch(client, pressingId);
435
+ if (!got) return null;
436
+ const parsed = pressing.Pressing.parse(got.content);
437
+ const s = parsed.state as { $kind?: string; Scheduled?: { start_timestamp_ms: string } };
438
+ const kind = variant(s, "Scheduled", "Active", "Paused");
439
+ return {
440
+ id: pressingId,
441
+ releaseId: parsed.release_id,
442
+ state:
443
+ kind === "Scheduled"
444
+ ? { kind: "scheduled", startTimestampMs: BigInt(s.Scheduled?.start_timestamp_ms ?? "0") }
445
+ : kind === "Paused"
446
+ ? { kind: "paused" }
447
+ : { kind: "active" },
448
+ supply: BigInt(parsed.supply),
449
+ };
450
+ }
451
+
452
+ /** Fetches and parses a `Listing<Currency>`, or `null` if that currency is not listed. */
453
+ export async function getListing(client: ClientWithCoreApi, listingId: string): Promise<ListingView | null> {
454
+ const got = await fetch(client, listingId);
455
+ if (!got) return null;
456
+ const parsed = listing.Listing.parse(got.content);
457
+ const p = parsed.price as { $kind?: string; Fixed?: { amount: string }; Floor?: { amount: string } };
458
+ const priceKind = variant(p, "Fixed", "Floor");
459
+ const st = parsed.state as { $kind?: string; Enabled?: unknown; Disabled?: unknown };
460
+ return {
461
+ id: listingId,
462
+ releaseId: parsed.release_id,
463
+ pressingId: parsed.pressing_id,
464
+ price: {
465
+ kind: priceKind === "Fixed" ? "fixed" : "floor",
466
+ amount: BigInt((priceKind === "Fixed" ? p.Fixed?.amount : p.Floor?.amount) ?? "0"),
467
+ },
468
+ state: variant(st, "Enabled", "Disabled") === "Enabled" ? "enabled" : "disabled",
469
+ currencyType: currencyFromType(got.type),
470
+ };
471
+ }
472
+
473
+ export interface GetSaleParams {
474
+ /** The release to resolve a sale for. */
475
+ releaseId: string;
476
+ /** The currency the buyer intends to pay in. */
477
+ currencyType: string;
478
+ misoPressingPackageId: string;
479
+ }
480
+
481
+ /**
482
+ * Resolves a release's run AND one currency's offer in a single round trip, by pure
483
+ * address math — no registry, no pointer, no event scan. `pressing: null` means the
484
+ * release has never opened one; `listing: null` means it does not sell in that
485
+ * currency (the other currencies, if any, are unaffected).
486
+ */
487
+ export async function getSale(
488
+ client: ClientWithCoreApi,
489
+ p: GetSaleParams,
490
+ ): Promise<{ pressing: PressingView | null; listing: ListingView | null }> {
491
+ const { pressingId, listingId } = deriveSaleIds(p.releaseId, p.currencyType, p.misoPressingPackageId);
492
+ const [pressingView, listingView] = await Promise.all([
493
+ getPressing(client, pressingId),
494
+ getListing(client, listingId),
495
+ ]);
496
+ return { pressing: pressingView, listing: listingView };
497
+ }
package/src/queries.ts ADDED
@@ -0,0 +1,9 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Shared read plumbing for the platform reads.
5
+
6
+ // `isNotFound` is the protocol SDK's — the not-found taxonomy is a property of
7
+ // the Sui transports, not of any one package's objects, so it is defined once in
8
+ // `@misonetwork/sdk` and re-exported here for the platform readers.
9
+ export { isNotFound } from "@misonetwork/sdk";