@misofm/sdk 0.15.10 → 0.16.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 (145) hide show
  1. package/README.md +153 -56
  2. package/dist/client.d.ts +175 -47
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +302 -47
  5. package/dist/client.js.map +1 -1
  6. package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts +11 -11
  7. package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts.map +1 -1
  8. package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js +9 -9
  9. package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js.map +1 -1
  10. package/dist/contracts/miso_record/deps/std/type_name.d.ts.map +1 -0
  11. package/dist/contracts/miso_record/deps/std/type_name.js.map +1 -0
  12. package/dist/contracts/miso_record/deps/sui/vec_set.d.ts +21 -0
  13. package/dist/contracts/miso_record/deps/sui/vec_set.d.ts.map +1 -0
  14. package/dist/contracts/miso_record/deps/sui/vec_set.js +20 -0
  15. package/dist/contracts/miso_record/deps/sui/vec_set.js.map +1 -0
  16. package/dist/contracts/miso_record/pressing.d.ts +327 -0
  17. package/dist/contracts/miso_record/pressing.d.ts.map +1 -0
  18. package/dist/contracts/miso_record/pressing.js +345 -0
  19. package/dist/contracts/miso_record/pressing.js.map +1 -0
  20. package/dist/contracts/miso_record/record.d.ts +190 -0
  21. package/dist/contracts/miso_record/record.d.ts.map +1 -0
  22. package/dist/contracts/miso_record/record.js +226 -0
  23. package/dist/contracts/miso_record/record.js.map +1 -0
  24. package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts +18 -0
  25. package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts.map +1 -0
  26. package/dist/contracts/miso_record_shop/deps/std/type_name.js +20 -0
  27. package/dist/contracts/miso_record_shop/deps/std/type_name.js.map +1 -0
  28. package/dist/contracts/miso_record_shop/listing.d.ts +387 -0
  29. package/dist/contracts/miso_record_shop/listing.d.ts.map +1 -0
  30. package/dist/contracts/miso_record_shop/listing.js +380 -0
  31. package/dist/contracts/miso_record_shop/listing.js.map +1 -0
  32. package/dist/contracts/miso_record_shop/witness.d.ts +7 -0
  33. package/dist/contracts/miso_record_shop/witness.d.ts.map +1 -0
  34. package/dist/contracts/miso_record_shop/witness.js +9 -0
  35. package/dist/contracts/miso_record_shop/witness.js.map +1 -0
  36. package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts +10 -12
  37. package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts.map +1 -1
  38. package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js +8 -10
  39. package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js.map +1 -1
  40. package/dist/contracts/vault/vault.d.ts +149 -23
  41. package/dist/contracts/vault/vault.d.ts.map +1 -1
  42. package/dist/contracts/vault/vault.js +144 -18
  43. package/dist/contracts/vault/vault.js.map +1 -1
  44. package/dist/contracts.d.ts +3 -3
  45. package/dist/contracts.d.ts.map +1 -1
  46. package/dist/contracts.js +5 -4
  47. package/dist/contracts.js.map +1 -1
  48. package/dist/deployments.d.ts +47 -11
  49. package/dist/deployments.d.ts.map +1 -1
  50. package/dist/deployments.js +48 -9
  51. package/dist/deployments.js.map +1 -1
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +2 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/mix.d.ts +144 -0
  57. package/dist/mix.d.ts.map +1 -0
  58. package/dist/mix.js +656 -0
  59. package/dist/mix.js.map +1 -0
  60. package/dist/pressing.d.ts +72 -156
  61. package/dist/pressing.d.ts.map +1 -1
  62. package/dist/pressing.js +269 -261
  63. package/dist/pressing.js.map +1 -1
  64. package/dist/publication.d.ts +5 -2
  65. package/dist/publication.d.ts.map +1 -1
  66. package/dist/publication.js +63 -34
  67. package/dist/publication.js.map +1 -1
  68. package/dist/read/catalog.d.ts +8 -5
  69. package/dist/read/catalog.d.ts.map +1 -1
  70. package/dist/read/catalog.js +47 -35
  71. package/dist/read/catalog.js.map +1 -1
  72. package/dist/read/config.d.ts +6 -4
  73. package/dist/read/config.d.ts.map +1 -1
  74. package/dist/read/config.js +1 -2
  75. package/dist/read/config.js.map +1 -1
  76. package/dist/read/index.d.ts +1 -1
  77. package/dist/read/index.d.ts.map +1 -1
  78. package/dist/read/index.js +1 -1
  79. package/dist/read/index.js.map +1 -1
  80. package/dist/read/internal/scalars.d.ts.map +1 -1
  81. package/dist/read/internal/scalars.js +4 -3
  82. package/dist/read/internal/scalars.js.map +1 -1
  83. package/dist/read/internal/walrus.d.ts.map +1 -1
  84. package/dist/read/internal/walrus.js +2 -1
  85. package/dist/read/internal/walrus.js.map +1 -1
  86. package/dist/read/receipts.d.ts +21 -12
  87. package/dist/read/receipts.d.ts.map +1 -1
  88. package/dist/read/receipts.js +159 -56
  89. package/dist/read/receipts.js.map +1 -1
  90. package/dist/read/types.d.ts +40 -26
  91. package/dist/read/types.d.ts.map +1 -1
  92. package/dist/read/wallet.d.ts +3 -4
  93. package/dist/read/wallet.d.ts.map +1 -1
  94. package/dist/read/wallet.js +72 -53
  95. package/dist/read/wallet.js.map +1 -1
  96. package/dist/vault.d.ts +71 -15
  97. package/dist/vault.d.ts.map +1 -1
  98. package/dist/vault.js +135 -29
  99. package/dist/vault.js.map +1 -1
  100. package/package.json +10 -3
  101. package/src/client.ts +404 -69
  102. package/src/contracts/composition_royalty_pool/composition_royalty_pool.ts +15 -15
  103. package/src/contracts/miso_record/deps/sui/vec_set.ts +21 -0
  104. package/src/contracts/miso_record/pressing.ts +522 -0
  105. package/src/contracts/miso_record/record.ts +338 -0
  106. package/src/contracts/miso_record_shop/deps/std/type_name.ts +22 -0
  107. package/src/contracts/miso_record_shop/listing.ts +602 -0
  108. package/src/contracts/miso_record_shop/witness.ts +11 -0
  109. package/src/contracts/recording_royalty_pool/recording_royalty_pool.ts +14 -16
  110. package/src/contracts/vault/vault.ts +226 -21
  111. package/src/contracts.ts +5 -4
  112. package/src/deployments.ts +134 -38
  113. package/src/index.ts +2 -0
  114. package/src/mix.ts +910 -0
  115. package/src/pressing.ts +428 -423
  116. package/src/publication.ts +69 -36
  117. package/src/read/catalog.ts +49 -34
  118. package/src/read/config.ts +7 -7
  119. package/src/read/index.ts +9 -1
  120. package/src/read/internal/scalars.ts +4 -2
  121. package/src/read/internal/walrus.ts +2 -1
  122. package/src/read/receipts.ts +181 -66
  123. package/src/read/types.ts +41 -24
  124. package/src/read/wallet.ts +70 -57
  125. package/src/vault.ts +228 -41
  126. package/dist/contracts/miso_pressing/certificate.d.ts +0 -89
  127. package/dist/contracts/miso_pressing/certificate.d.ts.map +0 -1
  128. package/dist/contracts/miso_pressing/certificate.js +0 -108
  129. package/dist/contracts/miso_pressing/certificate.js.map +0 -1
  130. package/dist/contracts/miso_pressing/deps/std/type_name.d.ts.map +0 -1
  131. package/dist/contracts/miso_pressing/deps/std/type_name.js.map +0 -1
  132. package/dist/contracts/miso_pressing/listing.d.ts +0 -434
  133. package/dist/contracts/miso_pressing/listing.d.ts.map +0 -1
  134. package/dist/contracts/miso_pressing/listing.js +0 -401
  135. package/dist/contracts/miso_pressing/listing.js.map +0 -1
  136. package/dist/contracts/miso_pressing/pressing.d.ts +0 -334
  137. package/dist/contracts/miso_pressing/pressing.d.ts.map +0 -1
  138. package/dist/contracts/miso_pressing/pressing.js +0 -298
  139. package/dist/contracts/miso_pressing/pressing.js.map +0 -1
  140. package/src/contracts/miso_pressing/certificate.ts +0 -164
  141. package/src/contracts/miso_pressing/listing.ts +0 -592
  142. package/src/contracts/miso_pressing/pressing.ts +0 -399
  143. /package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.d.ts +0 -0
  144. /package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.js +0 -0
  145. /package/src/contracts/{miso_pressing → miso_record}/deps/std/type_name.ts +0 -0
package/src/pressing.ts CHANGED
@@ -1,294 +1,339 @@
1
1
  // Copyright (c) Miso Labs, Inc.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
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. The core `ReleaseRegistry` owns release creation,
13
- // but there is no pressing registry or pointer to follow:
14
- // `derivePressingId`/`deriveListingId` answer "where is it" offline, which is why the
15
- // builders here take a RELEASE id and compute the rest. A caller cannot pass a
16
- // listing that belongs to some other pressing, because it never picks one.
4
+ // Primary Record sales. `miso_record` owns concrete Records and edition-scoped
5
+ // Pressings; immutable `miso_record_shop` owns per-currency Listings and payment.
17
6
 
18
7
  import type { ClientWithCoreApi } from "@mysten/sui/client";
19
- import { deriveObjectID, normalizeStructTag, normalizeSuiAddress, parseStructTag } from "@mysten/sui/utils";
8
+ import {
9
+ deriveObjectID,
10
+ normalizeStructTag,
11
+ normalizeSuiAddress,
12
+ parseStructTag,
13
+ } from "@mysten/sui/utils";
20
14
  import type { TxThunk } from "./transactions.ts";
21
15
  import { isNotFound } from "./queries.ts";
22
16
  import { asU64, type U64Input } from "./vault.ts";
23
- import * as listing from "./contracts/miso_pressing/listing.ts";
24
- import * as pressing from "./contracts/miso_pressing/pressing.ts";
17
+ import * as pressingContract from "./contracts/miso_record/pressing.ts";
18
+ import * as recordContract from "./contracts/miso_record/record.ts";
19
+ import * as listingContract from "./contracts/miso_record_shop/listing.ts";
20
+
21
+ const MAX_U16 = 0xffff;
22
+ const MAX_U32 = 0xffff_ffff;
23
+ const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0]);
24
+
25
+ /** Fixed cross-client parity vector for the finalized Record/Record Shop
26
+ * derived-object ABI. Outputs are literals, not initialized through the
27
+ * derivation helpers they verify. */
28
+ export const RECORD_SALES_DERIVATION_VECTOR_V1 = {
29
+ releaseId:
30
+ "0x3333333333333333333333333333333333333333333333333333333333333333",
31
+ edition: 2,
32
+ currencyType: "0x2::sui::SUI",
33
+ recordPackageId:
34
+ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
35
+ recordShopPackageId:
36
+ "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
37
+ pressingId:
38
+ "0x3e74b7f16951f027684e3bc44dec1efa35a0754017417ad5d4d4131f92a20c16",
39
+ pressingAdminCapId:
40
+ "0xbaec8eab17a06cc3c3a5d13e4b5b636a210776bd60f86b423d245f70f8e965a6",
41
+ listingId:
42
+ "0x7fbf4cb7cce881a50bdc848ee65545299fec0887213524de29e039bed31e0e37",
43
+ recordNumber: 7,
44
+ recordId:
45
+ "0xd492af6fc9b88d71c6674e486ab7aeca911ab797f651bae891a1cf8e54f94f1a",
46
+ } as const;
47
+
48
+ export type ListingPrice = { kind: "fixed" | "floor"; amount: U64Input };
49
+ export type ListingSwitch = "enabled" | "disabled";
25
50
 
26
- /** Pricing policy: pay exactly `amount` (fixed) or at least `amount` (floor). */
27
- export type ListingPrice = {
28
- kind: "fixed" | "floor";
29
- amount: U64Input;
30
- };
51
+ function uint(label: string, value: number, max: number): number {
52
+ if (!Number.isSafeInteger(value) || value < 0 || value > max) {
53
+ throw new RangeError(`${label} must be an integer from 0 to ${max}`);
54
+ }
55
+ return value;
56
+ }
31
57
 
32
- /** Whether a currency's listing takes payment. Below the pressing's own run state. */
33
- export type ListingSwitch = "enabled" | "disabled";
58
+ function edition(value: number): number {
59
+ const out = uint("edition", value, MAX_U16);
60
+ if (out === 0) throw new RangeError("edition must be greater than zero");
61
+ return out;
62
+ }
63
+
64
+ function maxSupply(value: number | null | undefined): number | null {
65
+ if (value == null) return null;
66
+ const out = uint("max supply", value, MAX_U32);
67
+ if (out === 0) throw new RangeError("max supply must be greater than zero");
68
+ return out;
69
+ }
34
70
 
35
- /**
36
- * The run's state, across every currency at once.
37
- *
38
- * `scheduled` is transitional: the first sale past `startTimestampMs` rewrites it to
39
- * `active` inside `mint_next`, and the start time leaves the object with it. "When was
40
- * this run scheduled for" is an event-log question once a run has sold anything.
41
- */
42
- export type PressingRunState =
43
- | { kind: "scheduled"; startTimestampMs: U64Input }
44
- | { kind: "active" }
45
- | { kind: "paused" };
46
-
47
- // ============================================================================
48
- // Address math
49
- // ============================================================================
50
-
51
- /** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
52
- const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
53
-
54
- /**
55
- * Where `releaseId`'s pressing lives — computable before it has been opened, and
56
- * stable forever after, because a release can only ever claim one.
57
- *
58
- * `misoPressingPackageId` must be the package's ORIGINAL (first-published) id, since
59
- * that is the address a Move type tag canonicalises to. `miso_pressing` is published
60
- * immutable, so its original and current ids are the same.
61
- */
62
71
  export function derivePressingId(
63
72
  releaseId: string,
64
- misoPressingPackageId: string,
73
+ editionNumber: number,
74
+ recordPackageId: string,
65
75
  ): string {
76
+ const value = edition(editionNumber);
66
77
  return deriveObjectID(
67
78
  releaseId,
68
- `${misoPressingPackageId}::pressing::PressingKey`,
69
- UNIT_STRUCT_KEY_BYTES,
79
+ `${recordPackageId}::pressing::PressingKey`,
80
+ pressingContract.PressingKey.serialize([value]).toBytes(),
70
81
  );
71
82
  }
72
83
 
73
- /**
74
- * Where `pressingId`'s admin cap was minted. The cap is transferable, so this is not
75
- * where it lives — only where it came from.
76
- */
77
84
  export function derivePressingAdminCapId(
78
85
  pressingId: string,
79
- misoPressingPackageId: string,
86
+ recordPackageId: string,
80
87
  ): string {
81
88
  return deriveObjectID(
82
89
  pressingId,
83
- `${misoPressingPackageId}::pressing::PressingAdminCapKey`,
90
+ `${recordPackageId}::pressing::PressingAdminCapKey`,
84
91
  UNIT_STRUCT_KEY_BYTES,
85
92
  );
86
93
  }
87
94
 
88
- /**
89
- * Where `currencyType`'s listing on `pressingId` lives. The key is phantom-typed, so
90
- * the currency rides in the key's TYPE — one listing per (pressing, currency), ever.
91
- */
95
+ export function deriveRecordId(
96
+ pressingId: string,
97
+ number: number,
98
+ recordPackageId: string,
99
+ ): string {
100
+ const value = uint("record number", number, MAX_U32);
101
+ if (value === 0)
102
+ throw new RangeError("record number must be greater than zero");
103
+ return deriveObjectID(
104
+ pressingId,
105
+ `${recordPackageId}::record::RecordKey`,
106
+ recordContract.RecordKey.serialize([value]).toBytes(),
107
+ );
108
+ }
109
+
92
110
  export function deriveListingId(
93
111
  pressingId: string,
94
112
  currencyType: string,
95
- misoPressingPackageId: string,
113
+ recordShopPackageId: string,
96
114
  ): string {
97
115
  return deriveObjectID(
98
116
  pressingId,
99
- `${misoPressingPackageId}::listing::ListingKey<${normalizeStructTag(currencyType)}>`,
117
+ `${recordShopPackageId}::listing::ListingKey<${normalizeStructTag(currencyType)}>`,
100
118
  UNIT_STRUCT_KEY_BYTES,
101
119
  );
102
120
  }
103
121
 
104
- /** Both derived ids a sale needs, from the release id alone. */
105
122
  export function deriveSaleIds(
106
123
  releaseId: string,
124
+ editionNumber: number,
107
125
  currencyType: string,
108
- misoPressingPackageId: string,
126
+ recordPackageId: string,
127
+ recordShopPackageId: string,
109
128
  ): { pressingId: string; listingId: string } {
110
- const pressingId = derivePressingId(releaseId, misoPressingPackageId);
129
+ const pressingId = derivePressingId(
130
+ releaseId,
131
+ editionNumber,
132
+ recordPackageId,
133
+ );
111
134
  return {
112
135
  pressingId,
113
- listingId: deriveListingId(pressingId, currencyType, misoPressingPackageId),
136
+ listingId: deriveListingId(pressingId, currencyType, recordShopPackageId),
114
137
  };
115
138
  }
116
139
 
117
- // ============================================================================
118
- // Term constructors (shared by the builders below)
119
- // ============================================================================
120
-
121
140
  type Tx = Parameters<TxThunk>[0];
122
141
 
123
- function priceArg(tx: Tx, misoPressingPackageId: string, price: ListingPrice) {
124
- const make =
125
- price.kind === "fixed" ? listing.newFixedPrice : listing.newFloorPrice;
142
+ function priceArg(tx: Tx, packageId: string, price: ListingPrice) {
143
+ const fn =
144
+ price.kind === "fixed" ? listingContract.fixed : listingContract.floor;
126
145
  return tx.add(
127
- make({ package: misoPressingPackageId, arguments: [asU64("listing price", price.amount)] }),
146
+ fn({
147
+ package: packageId,
148
+ arguments: [asU64("listing price", price.amount)],
149
+ }),
128
150
  );
129
151
  }
130
152
 
131
- function switchArg(
132
- tx: Tx,
133
- misoPressingPackageId: string,
134
- state: ListingSwitch,
135
- ) {
136
- const make =
137
- state === "enabled" ? listing.newEnabledState : listing.newDisabledState;
138
- return tx.add(make({ package: misoPressingPackageId }));
139
- }
140
-
141
- function runStateArg(
142
- tx: Tx,
143
- misoPressingPackageId: string,
144
- state: PressingRunState,
145
- ) {
146
- const pkg = { package: misoPressingPackageId };
147
- switch (state.kind) {
148
- case "scheduled":
149
- return tx.add(
150
- pressing.newScheduledState({
151
- ...pkg,
152
- arguments: [asU64("pressing schedule timestamp", state.startTimestampMs)],
153
- }),
154
- );
155
- case "active":
156
- return tx.add(pressing.newActiveState(pkg));
157
- case "paused":
158
- return tx.add(pressing.newPausedState(pkg));
159
- }
153
+ function stateArg(tx: Tx, packageId: string, state: ListingSwitch) {
154
+ const fn =
155
+ state === "enabled" ? listingContract.enabled : listingContract.disabled;
156
+ return tx.add(fn({ package: packageId }));
160
157
  }
161
158
 
162
- // ============================================================================
163
- // Opening the run
164
- // ============================================================================
159
+ function witnessType(recordShopPackageId: string): string {
160
+ return `${recordShopPackageId}::witness::Witness`;
161
+ }
165
162
 
166
- /** One currency's offer, as `openPressing` takes it. */
167
163
  export interface ListingTerms {
168
- /** The `Currency` type argument, e.g. `0x2::sui::SUI`. */
169
164
  currencyType: string;
170
165
  price: ListingPrice;
171
- /** Defaults to `"enabled"` — a listing opened disabled takes no payment yet. */
172
166
  state?: ListingSwitch;
173
167
  }
174
168
 
175
169
  export interface OpenPressingParams {
176
- /** The `Release` this run presses copies of — also the pressing's derivation parent. */
177
170
  releaseId: string;
178
- /** The release's `ReleaseAdminCap`. Needed ONLY to open the run; every later change
179
- * authorizes against the `PressingAdminCap` this mints. */
180
171
  releaseAdminCapId: string;
181
- /** Currencies to open the run in. May be empty — listings can be added later with
182
- * {@link openListing} but a run with no listings sells nothing. */
183
- listings: ListingTerms[];
184
- /** Run state at open. Defaults to `active` (sell immediately); `scheduled` sets a
185
- * trustless opening time and needs no later transaction to open. */
186
- state?: PressingRunState;
187
- /** Where to send the minted `PressingAdminCap` (usually the artist or their vault). */
172
+ edition: number;
173
+ maxSupply?: number | null;
174
+ listings: readonly ListingTerms[];
188
175
  adminCapRecipient: string;
189
- misoPressingPackageId: string;
190
- }
191
-
192
- /**
193
- * Opens a release's pressing and lists it, in ONE transaction.
194
- *
195
- * `pressing::new` hands the `Pressing` back UNSHARED precisely so the listings can be
196
- * claimed off it before `share` puts it on chain — that ordering is the whole reason
197
- * this is a single builder and not three. A release can only ever open one pressing,
198
- * so this call is once-per-release forever; changing price or currency afterwards is
199
- * an edit ({@link setListingPrice}, {@link openListing}), never a re-open.
200
- */
176
+ recordPackageId: string;
177
+ recordShopPackageId: string;
178
+ }
179
+
180
+ /** Initial setup: create the Pressing, authorize Record Shop, open/share Listings,
181
+ * share the Pressing, and transfer its admin capability. */
201
182
  export function openPressing(p: OpenPressingParams): TxThunk {
202
183
  return (tx) => {
203
- const pkg = p.misoPressingPackageId;
204
- const [run, adminCap] = tx.add(
205
- pressing._new({
206
- package: pkg,
184
+ const [pressing, cap] = tx.add(
185
+ pressingContract._new({
186
+ package: p.recordPackageId,
207
187
  arguments: [
208
188
  p.releaseId,
209
189
  p.releaseAdminCapId,
210
- runStateArg(tx, pkg, p.state ?? { kind: "active" }),
190
+ edition(p.edition),
191
+ maxSupply(p.maxSupply),
211
192
  ],
212
193
  }),
213
194
  );
195
+ tx.add(
196
+ pressingContract.authorizeDistributor({
197
+ package: p.recordPackageId,
198
+ arguments: [pressing!, cap!],
199
+ typeArguments: [witnessType(p.recordShopPackageId)],
200
+ }),
201
+ );
214
202
  for (const terms of p.listings) {
215
- tx.add(
216
- listing._new({
217
- package: pkg,
203
+ const listing = tx.add(
204
+ listingContract._new({
205
+ package: p.recordShopPackageId,
218
206
  arguments: [
219
- run!,
220
- adminCap!,
221
- priceArg(tx, pkg, terms.price),
222
- switchArg(tx, pkg, terms.state ?? "enabled"),
207
+ pressing!,
208
+ cap!,
209
+ priceArg(tx, p.recordShopPackageId, terms.price),
223
210
  ],
224
211
  typeArguments: [terms.currencyType],
225
212
  }),
226
213
  );
214
+ if (terms.state === "disabled") {
215
+ tx.add(
216
+ listingContract.setState({
217
+ package: p.recordShopPackageId,
218
+ arguments: [
219
+ listing,
220
+ cap!,
221
+ stateArg(tx, p.recordShopPackageId, "disabled"),
222
+ ],
223
+ typeArguments: [terms.currencyType],
224
+ }),
225
+ );
226
+ }
227
+ tx.add(
228
+ listingContract.share({
229
+ package: p.recordShopPackageId,
230
+ arguments: [listing],
231
+ typeArguments: [terms.currencyType],
232
+ }),
233
+ );
227
234
  }
228
- tx.add(pressing.share({ package: pkg, arguments: [run!] }));
229
- tx.transferObjects([adminCap!], p.adminCapRecipient);
235
+ tx.add(
236
+ pressingContract.share({
237
+ package: p.recordPackageId,
238
+ arguments: [pressing!],
239
+ }),
240
+ );
241
+ tx.transferObjects([cap!], p.adminCapRecipient);
242
+ };
243
+ }
244
+
245
+ export interface PressingAdministrationParams {
246
+ pressingId: string;
247
+ pressingAdminCapId: string;
248
+ recordPackageId: string;
249
+ recordShopPackageId: string;
250
+ }
251
+
252
+ export function authorizeRecordShop(p: PressingAdministrationParams): TxThunk {
253
+ return (tx) => {
254
+ tx.add(
255
+ pressingContract.authorizeDistributor({
256
+ package: p.recordPackageId,
257
+ arguments: [p.pressingId, p.pressingAdminCapId],
258
+ typeArguments: [witnessType(p.recordShopPackageId)],
259
+ }),
260
+ );
261
+ };
262
+ }
263
+
264
+ export function revokeRecordShop(p: PressingAdministrationParams): TxThunk {
265
+ return (tx) => {
266
+ tx.add(
267
+ pressingContract.revokeDistributor({
268
+ package: p.recordPackageId,
269
+ arguments: [p.pressingId, p.pressingAdminCapId],
270
+ typeArguments: [witnessType(p.recordShopPackageId)],
271
+ }),
272
+ );
230
273
  };
231
274
  }
232
275
 
233
276
  export interface OpenListingParams {
234
- /** The release whose pressing gains a currency. The pressing id derives from it. */
235
- releaseId: string;
277
+ pressingId: string;
236
278
  pressingAdminCapId: string;
237
279
  terms: ListingTerms;
238
- misoPressingPackageId: string;
280
+ recordShopPackageId: string;
239
281
  }
240
282
 
241
- /**
242
- * Adds a currency to a live pressing. Once per currency, ever — the slot is a derived
243
- * claim, so a second call for the same currency aborts. Everything after is an edit.
244
- */
283
+ /** Add one permanent currency Listing. Authorization is deliberately separate. */
245
284
  export function openListing(p: OpenListingParams): TxThunk {
246
285
  return (tx) => {
247
- const pkg = p.misoPressingPackageId;
248
- tx.add(
249
- listing._new({
250
- package: pkg,
286
+ const listing = tx.add(
287
+ listingContract._new({
288
+ package: p.recordShopPackageId,
251
289
  arguments: [
252
- derivePressingId(p.releaseId, pkg),
290
+ p.pressingId,
253
291
  p.pressingAdminCapId,
254
- priceArg(tx, pkg, p.terms.price),
255
- switchArg(tx, pkg, p.terms.state ?? "enabled"),
292
+ priceArg(tx, p.recordShopPackageId, p.terms.price),
256
293
  ],
257
294
  typeArguments: [p.terms.currencyType],
258
295
  }),
259
296
  );
297
+ if (p.terms.state === "disabled") {
298
+ tx.add(
299
+ listingContract.setState({
300
+ package: p.recordShopPackageId,
301
+ arguments: [
302
+ listing,
303
+ p.pressingAdminCapId,
304
+ stateArg(tx, p.recordShopPackageId, "disabled"),
305
+ ],
306
+ typeArguments: [p.terms.currencyType],
307
+ }),
308
+ );
309
+ }
310
+ tx.add(
311
+ listingContract.share({
312
+ package: p.recordShopPackageId,
313
+ arguments: [listing],
314
+ typeArguments: [p.terms.currencyType],
315
+ }),
316
+ );
260
317
  };
261
318
  }
262
319
 
263
- // ============================================================================
264
- // Editing the offer
265
- // ============================================================================
266
-
267
320
  export interface SetListingPriceParams {
268
- releaseId: string;
321
+ pressingId: string;
269
322
  currencyType: string;
270
323
  pressingAdminCapId: string;
271
324
  price: ListingPrice;
272
- misoPressingPackageId: string;
325
+ recordShopPackageId: string;
273
326
  }
274
327
 
275
- /**
276
- * Reprices one currency in place, keeping the listing's address and identity. Safe
277
- * against in-flight buys by construction: a `fixed` buyer pays exactly, so a stale
278
- * payment aborts against the new price, and a `floor` buyer never pays more than the
279
- * balance they sent.
280
- */
281
328
  export function setListingPrice(p: SetListingPriceParams): TxThunk {
282
329
  return (tx) => {
283
- const pkg = p.misoPressingPackageId;
284
- const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
285
330
  tx.add(
286
- listing.setPrice({
287
- package: pkg,
331
+ listingContract.setPrice({
332
+ package: p.recordShopPackageId,
288
333
  arguments: [
289
- listingId,
334
+ deriveListingId(p.pressingId, p.currencyType, p.recordShopPackageId),
290
335
  p.pressingAdminCapId,
291
- priceArg(tx, pkg, p.price),
336
+ priceArg(tx, p.recordShopPackageId, p.price),
292
337
  ],
293
338
  typeArguments: [p.currencyType],
294
339
  }),
@@ -297,25 +342,22 @@ export function setListingPrice(p: SetListingPriceParams): TxThunk {
297
342
  }
298
343
 
299
344
  export interface SetListingStateParams {
300
- releaseId: string;
345
+ pressingId: string;
301
346
  currencyType: string;
302
347
  pressingAdminCapId: string;
303
348
  state: ListingSwitch;
304
- misoPressingPackageId: string;
349
+ recordShopPackageId: string;
305
350
  }
306
351
 
307
- /** Stops or resumes ONE currency. The run's other currencies carry on. */
308
352
  export function setListingState(p: SetListingStateParams): TxThunk {
309
353
  return (tx) => {
310
- const pkg = p.misoPressingPackageId;
311
- const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
312
354
  tx.add(
313
- listing.setState({
314
- package: pkg,
355
+ listingContract.setState({
356
+ package: p.recordShopPackageId,
315
357
  arguments: [
316
- listingId,
358
+ deriveListingId(p.pressingId, p.currencyType, p.recordShopPackageId),
317
359
  p.pressingAdminCapId,
318
- switchArg(tx, pkg, p.state),
360
+ stateArg(tx, p.recordShopPackageId, p.state),
319
361
  ],
320
362
  typeArguments: [p.currencyType],
321
363
  }),
@@ -323,93 +365,40 @@ export function setListingState(p: SetListingStateParams): TxThunk {
323
365
  };
324
366
  }
325
367
 
326
- export interface SetPressingStateParams {
327
- releaseId: string;
328
- pressingAdminCapId: string;
329
- state: PressingRunState;
330
- misoPressingPackageId: string;
331
- }
332
-
333
- /**
334
- * Moves the whole run between its three modes, over every currency at once. There is
335
- * no "end" — ending a campaign is `{ kind: "paused" }`.
336
- */
337
- export function setPressingState(p: SetPressingStateParams): TxThunk {
338
- return (tx) => {
339
- const pkg = p.misoPressingPackageId;
340
- tx.add(
341
- pressing.setState({
342
- package: pkg,
343
- arguments: [
344
- derivePressingId(p.releaseId, pkg),
345
- p.pressingAdminCapId,
346
- runStateArg(tx, pkg, p.state),
347
- ],
348
- }),
349
- );
350
- };
351
- }
352
-
353
- // ============================================================================
354
- // Buying
355
- // ============================================================================
356
-
357
- export interface BuyRecordParams {
358
- /**
359
- * The `Release` being bought a copy of. The pressing and this currency's listing
360
- * BOTH derive from it (see {@link deriveSaleIds}), so there is nothing to look up
361
- * and no way to pair a listing with the wrong pressing.
362
- */
368
+ export interface PurchaseRecordParams {
363
369
  releaseId: string;
364
- /** Amount to pay in the currency's smallest unit (exact for `fixed`, ≥ for `floor`). */
365
- amount: U64Input;
366
- /** The listing's `Currency` type, e.g. `0x2::sui::SUI`. */
370
+ edition: number;
371
+ paymentAmount: U64Input;
372
+ expectedPricing: ListingPrice;
367
373
  currencyType: string;
368
- /** Where to send the pressed `Record` (usually the buyer). */
369
374
  recipient: string;
370
- misoPressingPackageId: string;
371
- /**
372
- * Set only when the buyer pays their OWN gas. Miso sponsors every purchase — the gas
373
- * coin is the sponsor's, so drawing a payment out of it would spend the wrong
374
- * wallet's money. Defaults to sponsored; bites only on SUI-priced listings.
375
- */
376
- useGasCoin?: boolean;
377
- }
378
-
379
- /**
380
- * Buys one record: sources the payment, calls `listing::buy`, and sends the pressed
381
- * `Record` to `recipient`. `buy` RETURNS the record rather than transferring it, so
382
- * the transfer here is not optional. The `Clock` is auto-supplied.
383
- *
384
- * ONE payment path, no coin picking. `tx.balance()` resolves the money at build time,
385
- * preferring the buyer's ADDRESS BALANCE — where value is assumed to live — and falling
386
- * back to coin objects inside the SDK if it must. Callers never enumerate coins: that
387
- * road showed a buyer their $1000 and then refused to spend a cent of it, because the
388
- * listing only saw `Coin<T>` objects and the money was in the address balance.
389
- *
390
- * `listing::buy` takes a bare `Balance<Currency>`, which is what makes that resolution
391
- * cheap: when the address balance covers the price, `tx.balance()` emits a single
392
- * `balance::redeem_funds` straight off the accumulator and no coin object is minted,
393
- * touched, or destroyed — leaving the sale free of any owned-object contention. Only
394
- * when it must fall back to coins does it merge, split, and `coin::into_balance`.
395
- */
396
- export function buyRecord(p: BuyRecordParams): TxThunk {
375
+ recordPackageId: string;
376
+ recordShopPackageId: string;
377
+ }
378
+
379
+ export function purchaseRecord(p: PurchaseRecordParams): TxThunk {
397
380
  return (tx) => {
398
- const pkg = p.misoPressingPackageId;
399
381
  const { pressingId, listingId } = deriveSaleIds(
400
382
  p.releaseId,
383
+ p.edition,
401
384
  p.currencyType,
402
- pkg,
385
+ p.recordPackageId,
386
+ p.recordShopPackageId,
403
387
  );
404
388
  const payment = tx.balance({
389
+ balance: asU64("purchase amount", p.paymentAmount),
405
390
  type: p.currencyType,
406
- balance: asU64("purchase amount", p.amount),
407
- useGasCoin: p.useGasCoin ?? false,
391
+ useGasCoin: false,
408
392
  });
409
393
  const record = tx.add(
410
- listing.buy({
411
- package: pkg,
412
- arguments: [listingId, pressingId, payment],
394
+ listingContract.purchase({
395
+ package: p.recordShopPackageId,
396
+ arguments: [
397
+ listingId,
398
+ pressingId,
399
+ payment,
400
+ priceArg(tx, p.recordShopPackageId, p.expectedPricing),
401
+ ],
413
402
  typeArguments: [p.currencyType],
414
403
  }),
415
404
  );
@@ -417,32 +406,36 @@ export function buyRecord(p: BuyRecordParams): TxThunk {
417
406
  };
418
407
  }
419
408
 
420
- // ============================================================================
421
- // Reads
422
- // ============================================================================
423
-
424
- /** A parsed `Pressing` — the run itself. */
425
409
  export interface PressingView {
426
410
  id: string;
427
411
  releaseId: string;
428
- state: PressingRunState & { startTimestampMs?: bigint };
429
- /** Records pressed so far; also the most recent number. Not a cap — nothing counts
430
- * against it, and there is no sold-out state to reach. */
431
- supply: bigint;
412
+ edition: number;
413
+ supply: number;
414
+ maxSupply: number | null;
415
+ distributors: string[];
432
416
  }
433
417
 
434
- /** A parsed `Listing<Currency>` — one currency's offer on the run. */
435
418
  export interface ListingView {
436
419
  id: string;
437
420
  releaseId: string;
438
421
  pressingId: string;
439
- price: { kind: "fixed" | "floor"; amount: bigint };
422
+ pricing: { kind: "fixed" | "floor"; amount: string };
440
423
  state: ListingSwitch;
441
- /** The exact normalized `Currency` type from the validated outer type tag. */
442
424
  currencyType: string;
443
425
  }
444
426
 
445
- /** Reads one object's BCS content + type, or `null` only when it does not exist. */
427
+ export interface RecordView {
428
+ id: string;
429
+ releaseId: string;
430
+ pressingId: string;
431
+ edition: number;
432
+ number: number;
433
+ purchaseCurrency: string;
434
+ purchasePrice: string;
435
+ purchasedBy: string;
436
+ purchasedTimestampMs: string;
437
+ }
438
+
446
439
  async function fetch(
447
440
  client: ClientWithCoreApi,
448
441
  objectId: string,
@@ -453,13 +446,12 @@ async function fetch(
453
446
  include: { content: true },
454
447
  });
455
448
  if (!object) return null;
456
- if (!object.content || !object.type) {
449
+ if (!object.content || !object.type)
457
450
  throw new Error(`object ${objectId} has no BCS content or full type`);
458
- }
459
451
  return { content: object.content, type: object.type };
460
- } catch (e) {
461
- if (isNotFound(e)) return null;
462
- throw e;
452
+ } catch (error) {
453
+ if (isNotFound(error)) return null;
454
+ throw error;
463
455
  }
464
456
  }
465
457
 
@@ -468,233 +460,246 @@ function sameId(a: string, b: string): boolean {
468
460
  }
469
461
 
470
462
  function requireId(label: string, actual: string, expected: string): void {
471
- if (!sameId(actual, expected)) {
463
+ if (!sameId(actual, expected))
472
464
  throw new Error(`${label} ${actual} does not match expected ${expected}`);
473
- }
474
465
  }
475
466
 
476
- /** Require the exact configured package/module/struct outer tag. */
477
- function requirePressingType(type: string, misoPressingPackageId: string): void {
467
+ function requireExactType(type: string, expected: string, label: string): void {
478
468
  const actual = normalizeStructTag(type);
479
- const expected = normalizeStructTag(`${misoPressingPackageId}::pressing::Pressing`);
480
- if (actual !== expected) {
481
- throw new Error(`Pressing has type ${actual}, expected configured ${expected}`);
482
- }
469
+ const normalizedExpected = normalizeStructTag(expected);
470
+ if (actual !== normalizedExpected)
471
+ throw new Error(
472
+ `${label} has type ${actual}, expected ${normalizedExpected}`,
473
+ );
483
474
  }
484
475
 
485
- /** Validate the whole Listing<CURRENCY> tag and return its normalized Currency. */
486
- function requireListingCurrency(type: string, misoPressingPackageId: string): string {
476
+ function requireListingCurrency(
477
+ type: string,
478
+ recordShopPackageId: string,
479
+ ): string {
487
480
  const actual = normalizeStructTag(type);
488
481
  const tag = parseStructTag(actual);
489
482
  if (
490
- tag.address !== normalizeSuiAddress(misoPressingPackageId) ||
483
+ tag.address !== normalizeSuiAddress(recordShopPackageId) ||
491
484
  tag.module !== "listing" ||
492
485
  tag.name !== "Listing" ||
493
486
  tag.typeParams.length !== 1
494
- ) {
495
- throw new Error(`Listing has type ${actual}, not a configured miso_pressing::listing::Listing<CURRENCY>`);
496
- }
497
- const currencyType = normalizeStructTag(tag.typeParams[0]!);
498
- const expected = normalizeStructTag(
499
- `${misoPressingPackageId}::listing::Listing<${currencyType}>`,
500
- );
501
- if (actual !== expected) {
502
- throw new Error(`Listing has non-canonical type ${actual}, expected ${expected}`);
503
- }
504
- return currencyType;
487
+ )
488
+ throw new Error(
489
+ `Listing has type ${actual}, not the configured Record Shop Listing<Currency>`,
490
+ );
491
+ return normalizeStructTag(tag.typeParams[0]!);
505
492
  }
506
493
 
507
- // The generated `MoveEnum` parsers emit `{ $kind, <Variant>: … }`; older ones omitted
508
- // `$kind`. Both shapes are accepted so a codegen bump cannot silently mis-read a price.
509
- function variant<T extends string>(
510
- v: { $kind?: string } & Record<string, unknown>,
511
- ...names: T[]
512
- ): T {
494
+ function enumKind(
495
+ value: { $kind?: string } & Record<string, unknown>,
496
+ names: readonly string[],
497
+ ): string {
513
498
  for (const name of names)
514
- if (v.$kind === name || v[name] != null) return name;
515
- return names[0]!;
516
- }
517
-
518
- /**
519
- * Fetches and parses a `Pressing`, or `null` if the release has never opened one.
520
- *
521
- * This deliberately deviates from the core-getter convention in `./queries.ts`
522
- * (throw on missing object). A Pressing's address is computable for any release, so
523
- * probing one that was never opened is a normal state, not a broken reference —
524
- * absence is `null` and only transport failures throw.
525
- */
526
- export async function getPressing(
527
- client: ClientWithCoreApi,
528
- pressingId: string,
529
- misoPressingPackageId: string,
530
- ): Promise<PressingView | null> {
531
- const got = await fetch(client, pressingId);
532
- if (!got) return null;
533
- return parsePressing(pressingId, got.content, got.type, misoPressingPackageId);
499
+ if (value.$kind === name || Object.hasOwn(value, name)) return name;
500
+ throw new Error(`unknown enum variant; expected ${names.join(" or ")}`);
501
+ }
502
+
503
+ function parsePricing(value: unknown): ListingView["pricing"] {
504
+ if (!value || typeof value !== "object")
505
+ throw new Error("Listing has malformed pricing");
506
+ const v = value as { $kind?: string; Fixed?: string; Floor?: string };
507
+ const kind = enumKind(v as never, ["Fixed", "Floor"]);
508
+ const amount = kind === "Fixed" ? v.Fixed : v.Floor;
509
+ if (amount == null || BigInt(amount) <= 0n)
510
+ throw new Error("Listing has invalid pricing amount");
511
+ return { kind: kind === "Fixed" ? "fixed" : "floor", amount };
534
512
  }
535
513
 
536
514
  function parsePressing(
537
515
  pressingId: string,
538
516
  content: Uint8Array,
539
517
  type: string,
540
- misoPressingPackageId: string,
518
+ recordPackageId: string,
541
519
  ): PressingView {
542
- requirePressingType(type, misoPressingPackageId);
543
- const parsed = pressing.Pressing.parse(content);
520
+ requireExactType(type, `${recordPackageId}::pressing::Pressing`, "Pressing");
521
+ const parsed = pressingContract.Pressing.parse(content);
544
522
  requireId("Pressing UID", parsed.id, pressingId);
545
523
  requireId(
546
524
  "Pressing derived id",
547
525
  pressingId,
548
- derivePressingId(parsed.release_id, misoPressingPackageId),
526
+ derivePressingId(parsed.release_id, parsed.edition, recordPackageId),
549
527
  );
550
- const s = parsed.state as {
551
- $kind?: string;
552
- Scheduled?: { start_timestamp_ms: string };
553
- };
554
- const kind = variant(s, "Scheduled", "Active", "Paused");
555
528
  return {
556
529
  id: pressingId,
557
530
  releaseId: parsed.release_id,
558
- state:
559
- kind === "Scheduled"
560
- ? {
561
- kind: "scheduled",
562
- startTimestampMs: BigInt(s.Scheduled?.start_timestamp_ms ?? "0"),
563
- }
564
- : kind === "Paused"
565
- ? { kind: "paused" }
566
- : { kind: "active" },
567
- supply: BigInt(parsed.supply),
531
+ edition: parsed.edition,
532
+ supply: parsed.supply,
533
+ maxSupply: parsed.max_supply,
534
+ distributors: parsed.distributors.contents.map((item) =>
535
+ normalizeStructTag(item.name),
536
+ ),
568
537
  };
569
538
  }
570
539
 
571
- /** Fetches and parses a `Listing<Currency>`, or `null` if that currency is not listed. */
572
- export async function getListing(
573
- client: ClientWithCoreApi,
574
- listingId: string,
575
- misoPressingPackageId: string,
576
- ): Promise<ListingView | null> {
577
- const got = await fetch(client, listingId);
578
- if (!got) return null;
579
- return parseListing(listingId, got.content, got.type, misoPressingPackageId);
580
- }
581
-
582
540
  function parseListing(
583
541
  listingId: string,
584
542
  content: Uint8Array,
585
543
  type: string,
586
- misoPressingPackageId: string,
544
+ recordShopPackageId: string,
587
545
  ): ListingView {
588
- const currencyType = requireListingCurrency(type, misoPressingPackageId);
589
- const parsed = listing.Listing.parse(content);
546
+ const currencyType = requireListingCurrency(type, recordShopPackageId);
547
+ const parsed = listingContract.Listing.parse(content);
590
548
  requireId("Listing UID", parsed.id, listingId);
591
- requireId(
592
- "Listing pressing",
593
- parsed.pressing_id,
594
- derivePressingId(parsed.release_id, misoPressingPackageId),
595
- );
596
549
  requireId(
597
550
  "Listing derived id",
598
551
  listingId,
599
- deriveListingId(parsed.pressing_id, currencyType, misoPressingPackageId),
552
+ deriveListingId(parsed.pressing_id, currencyType, recordShopPackageId),
600
553
  );
601
- const p = parsed.price as {
602
- $kind?: string;
603
- Fixed?: { amount: string };
604
- Floor?: { amount: string };
605
- };
606
- const priceKind = variant(p, "Fixed", "Floor");
607
- const st = parsed.state as {
608
- $kind?: string;
609
- Enabled?: unknown;
610
- Disabled?: unknown;
611
- };
554
+ const state = enumKind(parsed.state as never, ["Enabled", "Disabled"]);
612
555
  return {
613
556
  id: listingId,
614
557
  releaseId: parsed.release_id,
615
558
  pressingId: parsed.pressing_id,
616
- price: {
617
- kind: priceKind === "Fixed" ? "fixed" : "floor",
618
- amount: BigInt(
619
- (priceKind === "Fixed" ? p.Fixed?.amount : p.Floor?.amount) ?? "0",
620
- ),
621
- },
622
- state:
623
- variant(st, "Enabled", "Disabled") === "Enabled" ? "enabled" : "disabled",
559
+ pricing: parsePricing(parsed.pricing),
560
+ state: state === "Enabled" ? "enabled" : "disabled",
624
561
  currencyType,
625
562
  };
626
563
  }
627
564
 
565
+ function parseRecord(
566
+ recordId: string,
567
+ content: Uint8Array,
568
+ type: string,
569
+ recordPackageId: string,
570
+ ): RecordView {
571
+ requireExactType(type, `${recordPackageId}::record::Record`, "Record");
572
+ const parsed = recordContract.Record.parse(content);
573
+ requireId("Record UID", parsed.id, recordId);
574
+ requireId(
575
+ "Record derived id",
576
+ recordId,
577
+ deriveRecordId(parsed.pressing_id, parsed.number, recordPackageId),
578
+ );
579
+ return {
580
+ id: recordId,
581
+ releaseId: parsed.release_id,
582
+ pressingId: parsed.pressing_id,
583
+ edition: parsed.edition,
584
+ number: parsed.number,
585
+ purchaseCurrency: normalizeStructTag(parsed.purchase_currency.name),
586
+ purchasePrice: parsed.purchase_price,
587
+ purchasedBy: parsed.purchased_by,
588
+ purchasedTimestampMs: parsed.purchased_timestamp_ms,
589
+ };
590
+ }
591
+
592
+ export async function getPressing(
593
+ client: ClientWithCoreApi,
594
+ pressingId: string,
595
+ recordPackageId: string,
596
+ ): Promise<PressingView | null> {
597
+ const got = await fetch(client, pressingId);
598
+ return got
599
+ ? parsePressing(pressingId, got.content, got.type, recordPackageId)
600
+ : null;
601
+ }
602
+
603
+ export async function getListing(
604
+ client: ClientWithCoreApi,
605
+ listingId: string,
606
+ recordShopPackageId: string,
607
+ ): Promise<ListingView | null> {
608
+ const got = await fetch(client, listingId);
609
+ return got
610
+ ? parseListing(listingId, got.content, got.type, recordShopPackageId)
611
+ : null;
612
+ }
613
+
614
+ export async function getRecord(
615
+ client: ClientWithCoreApi,
616
+ recordId: string,
617
+ recordPackageId: string,
618
+ ): Promise<RecordView | null> {
619
+ const got = await fetch(client, recordId);
620
+ return got
621
+ ? parseRecord(recordId, got.content, got.type, recordPackageId)
622
+ : null;
623
+ }
624
+
628
625
  export interface GetSaleParams {
629
- /** The release to resolve a sale for. */
630
626
  releaseId: string;
631
- /** The currency the buyer intends to pay in. */
627
+ edition: number;
632
628
  currencyType: string;
633
- misoPressingPackageId: string;
629
+ recordPackageId: string;
630
+ recordShopPackageId: string;
634
631
  }
635
632
 
636
- /**
637
- * Resolves a release's run AND one currency's offer in a single round trip, by pure
638
- * address math — no registry, no pointer, no event scan. `pressing: null` means the
639
- * release has never opened one; `listing: null` means it does not sell in that
640
- * currency (the other currencies, if any, are unaffected).
641
- */
642
633
  export async function getSale(
643
634
  client: ClientWithCoreApi,
644
635
  p: GetSaleParams,
645
636
  ): Promise<{ pressing: PressingView | null; listing: ListingView | null }> {
646
637
  const { pressingId, listingId } = deriveSaleIds(
647
638
  p.releaseId,
639
+ p.edition,
648
640
  p.currencyType,
649
- p.misoPressingPackageId,
641
+ p.recordPackageId,
642
+ p.recordShopPackageId,
650
643
  );
651
644
  const { objects } = await client.core.getObjects({
652
645
  objectIds: [pressingId, listingId],
653
646
  include: { content: true },
654
647
  });
655
- const pressingObject = objects[0];
656
- const listingObject = objects[1];
657
- if (pressingObject instanceof Error && !isNotFound(pressingObject)) {
648
+ const [pressingObject, listingObject] = objects;
649
+ if (pressingObject instanceof Error && !isNotFound(pressingObject))
658
650
  throw pressingObject;
659
- }
660
- if (listingObject instanceof Error && !isNotFound(listingObject)) {
651
+ if (listingObject instanceof Error && !isNotFound(listingObject))
661
652
  throw listingObject;
662
- }
663
- const parsedPressing =
653
+ const pressing =
664
654
  !pressingObject || pressingObject instanceof Error
665
655
  ? null
666
- : parseBatchPressing(pressingId, pressingObject.content, pressingObject.type, p.misoPressingPackageId);
667
- const parsedListing =
656
+ : parseBatchPressing(
657
+ pressingId,
658
+ pressingObject.content,
659
+ pressingObject.type,
660
+ p.recordPackageId,
661
+ );
662
+ const listing =
668
663
  !listingObject || listingObject instanceof Error
669
664
  ? null
670
- : parseBatchListing(listingId, listingObject.content, listingObject.type, p.misoPressingPackageId);
671
- if (parsedPressing) requireId("Sale pressing release", parsedPressing.releaseId, p.releaseId);
672
- if (parsedListing) {
673
- requireId("Sale listing release", parsedListing.releaseId, p.releaseId);
674
- requireId("Sale listing pressing", parsedListing.pressingId, pressingId);
675
- if (parsedListing.currencyType !== normalizeStructTag(p.currencyType)) {
676
- throw new Error(`Sale listing currency ${parsedListing.currencyType} does not match requested ${normalizeStructTag(p.currencyType)}`);
665
+ : parseBatchListing(
666
+ listingId,
667
+ listingObject.content,
668
+ listingObject.type,
669
+ p.recordShopPackageId,
670
+ );
671
+ if (pressing)
672
+ requireId("Sale pressing release", pressing.releaseId, p.releaseId);
673
+ if (listing) {
674
+ requireId("Sale listing release", listing.releaseId, p.releaseId);
675
+ requireId("Sale listing pressing", listing.pressingId, pressingId);
676
+ if (listing.currencyType !== normalizeStructTag(p.currencyType)) {
677
+ throw new Error(
678
+ `Sale listing currency ${listing.currencyType} does not match requested ${normalizeStructTag(p.currencyType)}`,
679
+ );
677
680
  }
678
681
  }
679
- return { pressing: parsedPressing, listing: parsedListing };
682
+ return { pressing, listing };
680
683
  }
681
684
 
682
685
  function parseBatchPressing(
683
- pressingId: string,
686
+ id: string,
684
687
  content: Uint8Array | undefined,
685
688
  type: string | undefined,
686
- misoPressingPackageId: string,
689
+ packageId: string,
687
690
  ): PressingView {
688
- if (!content || !type) throw new Error(`Pressing ${pressingId} has no BCS content or full type`);
689
- return parsePressing(pressingId, content, type, misoPressingPackageId);
691
+ if (!content || !type)
692
+ throw new Error(`Pressing ${id} has no BCS content or full type`);
693
+ return parsePressing(id, content, type, packageId);
690
694
  }
691
695
 
692
696
  function parseBatchListing(
693
- listingId: string,
697
+ id: string,
694
698
  content: Uint8Array | undefined,
695
699
  type: string | undefined,
696
- misoPressingPackageId: string,
700
+ packageId: string,
697
701
  ): ListingView {
698
- if (!content || !type) throw new Error(`Listing ${listingId} has no BCS content or full type`);
699
- return parseListing(listingId, content, type, misoPressingPackageId);
702
+ if (!content || !type)
703
+ throw new Error(`Listing ${id} has no BCS content or full type`);
704
+ return parseListing(id, content, type, packageId);
700
705
  }