@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
@@ -3,9 +3,9 @@
3
3
  //
4
4
  // What one record sale actually was, read back from its transaction.
5
5
  //
6
- // The transaction digest is the receipt's identity: everything shown is
6
+ // The transaction digest plus Record ID is the receipt's identity: everything shown is
7
7
  // re-derived from chain state, so a receipt link survives a refresh, a deep link,
8
- // or a share to another device. `listing::buy` emits ONE `RecordSoldEvent` carrying
8
+ // or a share to another device. `listing::purchase` emits `RecordSoldEvent` carrying
9
9
  // the copy that was minted, its number in the run, and what the buyer paid.
10
10
  //
11
11
  // Two reads, in order, because a receipt is opened at two very different ages:
@@ -14,13 +14,13 @@
14
14
  // indexer the old path — GraphQL keeps transactions the fullnode has pruned.
15
15
  // This is what makes a months-old receipt link still open.
16
16
  //
17
- // Where the money goes is PRESENTATIONAL, not a second on-chain fact: `buy`
17
+ // Where the money goes is PRESENTATIONAL, not a second on-chain fact: `purchase`
18
18
  // forwards the whole payment to the release's funds accumulator (there is no
19
19
  // platform fee), and the royalty layer splits it downstream by the same terms this
20
20
  // read walks. We show the buyer that arithmetic, in the same truncating integer
21
21
  // math the chain does.
22
22
 
23
- import * as listingContract from "../contracts/miso_pressing/listing.ts";
23
+ import * as listingContract from "../contracts/miso_record_shop/listing.ts";
24
24
  import {
25
25
  extractTypeParams2,
26
26
  getCompositionsByIds,
@@ -28,13 +28,14 @@ import {
28
28
  import { normalizeStructTag, normalizeSuiAddress } from "@mysten/sui/utils";
29
29
  import { getPressingDetail } from "./catalog.ts";
30
30
  import type { MisoClient } from "./client.ts";
31
- import { int, u64 } from "./internal/scalars.ts";
31
+ import { int } from "./internal/scalars.ts";
32
+ import { requireRecordSalesDeployment } from "../deployments.ts";
32
33
  import type { Price, PressingDetail, PurchaseReceipt, RecordSale, TrackRoyalty } from "./types.ts";
33
34
  import { getWorkAddressesByShareTypes } from "./works.ts";
34
35
 
35
36
  const BPS = 10_000n;
36
37
 
37
- /** `miso_pressing::listing::RecordSoldEvent<Currency>` — emitted by `listing::buy`. */
38
+ /** `miso_record_shop::listing::RecordSoldEvent<Currency>`. */
38
39
  const RECORD_SOLD_EVENT_NAME = "listing::RecordSoldEvent";
39
40
 
40
41
  /**
@@ -44,6 +45,10 @@ const RECORD_SOLD_EVENT_NAME = "listing::RecordSoldEvent";
44
45
  */
45
46
  const FULLNODE_WAIT_MS = 5_000;
46
47
 
48
+ export class MalformedRecordSoldEventError extends Error {
49
+ override readonly name = "MalformedRecordSoldEventError";
50
+ }
51
+
47
52
  /** Composition royalty rate (bps) per recording id. Sparse — an unresolved parent has no entry. */
48
53
  type CompositionRates = Record<string, number | undefined>;
49
54
 
@@ -58,18 +63,45 @@ function priceFromUnknown(value: unknown): Price | null {
58
63
  if (!value || typeof value !== "object") return null;
59
64
  const variants = value as {
60
65
  $kind?: string;
61
- Fixed?: { amount?: unknown };
62
- Floor?: { amount?: unknown };
63
- fixed?: { amount?: unknown };
64
- floor?: { amount?: unknown };
66
+ Fixed?: unknown;
67
+ Floor?: unknown;
65
68
  };
66
- const fixed = variants.$kind === "Fixed" || variants.Fixed != null || variants.fixed != null;
67
- const amount = coerceBigInt(
68
- fixed
69
- ? (variants.Fixed ?? variants.fixed)?.amount
70
- : (variants.Floor ?? variants.floor)?.amount,
71
- );
72
- return amount == null ? null : { kind: fixed ? "fixed" : "floor", amount: amount.toString() };
69
+ const fixed = variants.$kind === "Fixed" || Object.hasOwn(variants, "Fixed");
70
+ const floor = variants.$kind === "Floor" || Object.hasOwn(variants, "Floor");
71
+ if (fixed === floor) return null;
72
+ const amount = coerceBigInt(fixed ? variants.Fixed : variants.Floor);
73
+ return amount == null || amount <= 0n
74
+ ? null
75
+ : { kind: fixed ? "fixed" : "floor", amount: amount.toString() };
76
+ }
77
+
78
+ function typeName(value: unknown): string | null {
79
+ const raw = typeof value === "string"
80
+ ? value
81
+ : value && typeof value === "object"
82
+ ? (value as Record<string, unknown>).name
83
+ : null;
84
+ if (typeof raw !== "string" || !raw) return null;
85
+ try { return normalizeStructTag(raw); } catch { return null; }
86
+ }
87
+
88
+ function positiveU32(value: unknown): number | null {
89
+ const bigint = coerceBigInt(value);
90
+ return bigint == null || bigint <= 0n || bigint > 0xffff_ffffn
91
+ ? null
92
+ : Number(bigint);
93
+ }
94
+
95
+ function positiveU16(value: unknown): number | null {
96
+ const number = positiveU32(value);
97
+ return number == null || number > 0xffff ? null : number;
98
+ }
99
+
100
+ function validPricingRelationship(purchasePrice: bigint, pricing: Price): boolean {
101
+ const configured = BigInt(pricing.amount);
102
+ return pricing.kind === "fixed"
103
+ ? purchasePrice === configured
104
+ : purchasePrice >= configured;
73
105
  }
74
106
 
75
107
  /**
@@ -80,21 +112,39 @@ function saleFromJson(
80
112
  json: Record<string, unknown>,
81
113
  currencyType: string,
82
114
  ): RecordSale | null {
83
- const number = coerceBigInt(json.number);
84
- const paid = coerceBigInt(json.paid);
115
+ const edition = positiveU16(json.edition);
116
+ const number = positiveU32(json.number);
117
+ const purchasePrice = coerceBigInt(json.purchase_price);
118
+ const purchasedTimestampMs = coerceBigInt(json.purchased_timestamp_ms);
85
119
  const recordId = json.record_id;
86
- const price = priceFromUnknown(json.price);
87
- if (number == null || paid == null || !price || typeof recordId !== "string") return null;
120
+ const pricing = priceFromUnknown(json.pricing);
121
+ const embeddedCurrency = typeName(json.purchase_currency);
122
+ if (
123
+ edition == null ||
124
+ number == null ||
125
+ purchasePrice == null || purchasePrice <= 0n ||
126
+ purchasedTimestampMs == null ||
127
+ !pricing || !validPricingRelationship(purchasePrice, pricing) ||
128
+ embeddedCurrency !== currencyType ||
129
+ typeof recordId !== "string" || !recordId ||
130
+ typeof json.listing_id !== "string" || !json.listing_id ||
131
+ typeof json.pressing_id !== "string" || !json.pressing_id ||
132
+ typeof json.release_id !== "string" || !json.release_id ||
133
+ typeof json.purchased_by !== "string" || !json.purchased_by
134
+ ) return null;
88
135
  return {
89
- listingId: typeof json.listing_id === "string" ? json.listing_id : "",
90
- pressingId: typeof json.pressing_id === "string" ? json.pressing_id : "",
91
- releaseId: typeof json.release_id === "string" ? json.release_id : "",
136
+ listingId: json.listing_id,
137
+ pressingId: json.pressing_id,
138
+ releaseId: json.release_id,
92
139
  recordId,
93
- number: number.toString(),
94
- paid: paid.toString(),
95
- price,
140
+ edition,
141
+ number,
142
+ purchaseCurrency: embeddedCurrency,
143
+ purchasePrice: purchasePrice.toString(),
144
+ pricing,
96
145
  currencyType,
97
- buyer: typeof json.buyer === "string" ? json.buyer : "",
146
+ purchasedBy: json.purchased_by,
147
+ purchasedTimestampMs: purchasedTimestampMs.toString(),
98
148
  };
99
149
  }
100
150
 
@@ -105,9 +155,9 @@ function saleFromJson(
105
155
  * be decoded as a Miso sale. Normalize both package/type tags and require exactly
106
156
  * one (possibly nested) currency type argument.
107
157
  */
108
- function recordSoldCurrencyType(
158
+ export function recordSoldCurrencyType(
109
159
  eventType: string,
110
- misoPressingPackageId: string,
160
+ recordShopPackageId: string,
111
161
  ): string | null {
112
162
  let tag: string;
113
163
  try {
@@ -116,7 +166,7 @@ function recordSoldCurrencyType(
116
166
  return null;
117
167
  }
118
168
 
119
- const prefix = `${normalizeSuiAddress(misoPressingPackageId)}::${RECORD_SOLD_EVENT_NAME}`;
169
+ const prefix = `${normalizeSuiAddress(recordShopPackageId)}::${RECORD_SOLD_EVENT_NAME}`;
120
170
  if (!tag.startsWith(`${prefix}<`) || !tag.endsWith(">")) return null;
121
171
 
122
172
  const typeArgument = tag.slice(prefix.length + 1, -1);
@@ -136,56 +186,86 @@ function recordSoldCurrencyType(
136
186
 
137
187
  export function isRecordSoldEventType(
138
188
  eventType: string,
139
- misoPressingPackageId: string,
189
+ recordShopPackageId: string,
140
190
  ): boolean {
141
- return recordSoldCurrencyType(eventType, misoPressingPackageId) !== null;
191
+ return recordSoldCurrencyType(eventType, recordShopPackageId) !== null;
142
192
  }
143
193
 
144
194
  /**
145
195
  * The sale a transaction recorded, or null if it emitted none. BCS is the stable
146
196
  * shape (a JSON projection's field names vary by transport), so it is tried first.
147
197
  */
148
- export function findRecordSale(
198
+ export function findRecordSales(
149
199
  events: {
150
200
  eventType: string;
151
201
  bcs: Uint8Array;
152
202
  json: Record<string, unknown> | null;
153
203
  }[],
154
- misoPressingPackageId: string,
155
- ): RecordSale | null {
204
+ recordShopPackageId: string,
205
+ ): RecordSale[] {
206
+ const sales: RecordSale[] = [];
156
207
  for (const e of events) {
157
208
  const currencyType = recordSoldCurrencyType(
158
209
  e.eventType,
159
- misoPressingPackageId,
210
+ recordShopPackageId,
160
211
  );
161
212
  if (!currencyType) continue;
162
213
  try {
163
214
  // The generated struct decodes addresses to 0x-hex and u64s to decimal strings.
164
215
  const s = listingContract.RecordSoldEvent.parse(e.bcs);
165
- const price = priceFromUnknown(s.price);
166
- if (!price) continue;
167
- return {
216
+ const pricing = priceFromUnknown(s.pricing);
217
+ const embeddedCurrency = typeName(s.purchase_currency);
218
+ const purchasePrice = BigInt(s.purchase_price);
219
+ if (
220
+ !pricing || embeddedCurrency !== currencyType ||
221
+ !validPricingRelationship(purchasePrice, pricing) ||
222
+ s.edition <= 0 || s.number <= 0
223
+ ) throw new MalformedRecordSoldEventError("malformed canonical RecordSoldEvent");
224
+ sales.push({
168
225
  listingId: s.listing_id,
169
226
  pressingId: s.pressing_id,
170
227
  releaseId: s.release_id,
171
228
  recordId: s.record_id,
172
- number: u64(s.number),
173
- paid: u64(s.paid),
174
- price,
229
+ edition: s.edition,
230
+ number: s.number,
231
+ purchaseCurrency: embeddedCurrency,
232
+ purchasePrice: s.purchase_price,
233
+ pricing,
175
234
  currencyType,
176
- buyer: s.buyer,
177
- };
178
- } catch {
179
- // Unparseable BCS (or none surfaced) — fall through to the JSON view.
235
+ purchasedBy: s.purchased_by,
236
+ purchasedTimestampMs: s.purchased_timestamp_ms,
237
+ });
238
+ continue;
239
+ } catch (error) {
240
+ // Never reinterpret malformed canonical BCS through a looser JSON view.
241
+ if (e.bcs.length > 0) {
242
+ if (error instanceof MalformedRecordSoldEventError) throw error;
243
+ throw new MalformedRecordSoldEventError(
244
+ `malformed canonical RecordSoldEvent BCS: ${error instanceof Error ? error.message : String(error)}`,
245
+ );
246
+ }
180
247
  }
181
248
  const fromJson = e.json && saleFromJson(e.json, currencyType);
182
- if (fromJson) return fromJson;
249
+ if (!fromJson) throw new MalformedRecordSoldEventError("malformed canonical RecordSoldEvent JSON");
250
+ sales.push(fromJson);
183
251
  }
184
- return null;
252
+ return sales;
253
+ }
254
+
255
+ /** Select one canonical sale by its Record ID. Selection is always explicit. */
256
+ export function findRecordSale(
257
+ events: Parameters<typeof findRecordSales>[0],
258
+ recordShopPackageId: string,
259
+ recordId: string,
260
+ ): RecordSale | null {
261
+ const sales = findRecordSales(events, recordShopPackageId);
262
+ return sales.find(
263
+ (sale) => normalizeSuiAddress(sale.recordId) === normalizeSuiAddress(recordId),
264
+ ) ?? null;
185
265
  }
186
266
 
187
- /** The sale, read from the fullnode. */
188
- async function saleFromFullnode(client: MisoClient, txDigest: string): Promise<RecordSale> {
267
+ /** Every canonical sale, read from the fullnode in event order. */
268
+ async function salesFromFullnode(client: MisoClient, txDigest: string): Promise<RecordSale[]> {
189
269
  const result = await client.sui.waitForTransaction({
190
270
  digest: txDigest,
191
271
  include: { events: true },
@@ -194,12 +274,13 @@ async function saleFromFullnode(client: MisoClient, txDigest: string): Promise<R
194
274
  if (result.$kind !== "Transaction" || !result.Transaction.status.success) {
195
275
  throw new Error(`Transaction did not succeed: ${txDigest}`);
196
276
  }
197
- const sale = findRecordSale(
277
+ const sales = requireRecordSalesDeployment(client.config.recordSales);
278
+ const found = findRecordSales(
198
279
  result.Transaction.events,
199
- client.config.protocol.pressing,
280
+ sales.recordShopPackageId,
200
281
  );
201
- if (!sale) throw new Error(`No record purchase in transaction ${txDigest}`);
202
- return sale;
282
+ if (found.length === 0) throw new Error(`No record purchase in transaction ${txDigest}`);
283
+ return found;
203
284
  }
204
285
 
205
286
  const TX_EVENTS_QUERY = `query TransactionEvents($digest: String!) {
@@ -220,8 +301,8 @@ interface TxEventsResult {
220
301
  } | null;
221
302
  }
222
303
 
223
- /** The sale, read from the GraphQL indexer the path for a pruned transaction. */
224
- async function saleFromIndexer(client: MisoClient, txDigest: string): Promise<RecordSale> {
304
+ /** Every canonical sale, read from the GraphQL indexer in event order. */
305
+ async function salesFromIndexer(client: MisoClient, txDigest: string): Promise<RecordSale[]> {
225
306
  const { data, errors } = await client.graphqlRaw.query<TxEventsResult, { digest: string }>({
226
307
  query: TX_EVENTS_QUERY,
227
308
  variables: { digest: txDigest },
@@ -232,23 +313,34 @@ async function saleFromIndexer(client: MisoClient, txDigest: string): Promise<Re
232
313
  if (!effects) throw new Error(`Transaction not found: ${txDigest}`);
233
314
  if (effects.status !== "SUCCESS") throw new Error(`Transaction did not succeed: ${txDigest}`);
234
315
 
316
+ const salesDeployment = requireRecordSalesDeployment(client.config.recordSales);
317
+ const sales: RecordSale[] = [];
235
318
  for (const node of effects.events?.nodes ?? []) {
236
319
  const contents = node.contents;
237
320
  if (!contents) continue;
238
321
  const currencyType = recordSoldCurrencyType(
239
322
  contents.type.repr,
240
- client.config.protocol.pressing,
323
+ salesDeployment.recordShopPackageId,
241
324
  );
242
325
  if (!currencyType) continue;
243
326
  const sale =
244
327
  typeof contents.json === "object" && contents.json !== null
245
328
  ? saleFromJson(contents.json as Record<string, unknown>, currencyType)
246
329
  : null;
247
- if (sale) return sale;
330
+ if (!sale) throw new MalformedRecordSoldEventError("malformed canonical RecordSoldEvent JSON");
331
+ sales.push(sale);
248
332
  }
333
+ if (sales.length > 0) return sales;
249
334
  throw new Error(`No record purchase in transaction ${txDigest}`);
250
335
  }
251
336
 
337
+ async function salesFromChain(client: MisoClient, txDigest: string): Promise<RecordSale[]> {
338
+ return salesFromFullnode(client, txDigest).catch((error) => {
339
+ if (error instanceof MalformedRecordSoldEventError) throw error;
340
+ return salesFromIndexer(client, txDigest);
341
+ });
342
+ }
343
+
252
344
  /**
253
345
  * Each recording's parent composition royalty rate, in basis points, keyed by
254
346
  * recording id.
@@ -331,13 +423,10 @@ export function breakdown(
331
423
  * The sale identifies the Pressing before its detail is fetched. `null` only when
332
424
  * that immutable Pressing cannot be read; it is never superseded or destroyed.
333
425
  */
334
- export async function getPurchaseReceipt(
426
+ async function hydratePurchaseReceipt(
335
427
  client: MisoClient,
336
- txDigest: string,
428
+ sale: RecordSale,
337
429
  ): Promise<PurchaseReceipt | null> {
338
- const sale = await saleFromFullnode(client, txDigest).catch(() =>
339
- saleFromIndexer(client, txDigest),
340
- );
341
430
  const detail = await getPressingDetail(client, sale.pressingId);
342
431
  if (!detail) return null;
343
432
 
@@ -350,7 +439,33 @@ export async function getPurchaseReceipt(
350
439
  return {
351
440
  sale,
352
441
  detail,
353
- price: sale.price.amount,
354
- tracks: breakdown(detail.release.tracks, sale.paid, rates),
442
+ price: sale.pricing.amount,
443
+ tracks: breakdown(detail.release.tracks, sale.purchasePrice, rates),
355
444
  };
356
445
  }
446
+
447
+ /** Read and hydrate every canonical Record sale in transaction event order. */
448
+ export async function getPurchaseReceipts(
449
+ client: MisoClient,
450
+ txDigest: string,
451
+ ): Promise<PurchaseReceipt[]> {
452
+ const sales = await salesFromChain(client, txDigest);
453
+ const hydrated = await Promise.all(sales.map((sale) => hydratePurchaseReceipt(client, sale)));
454
+ if (hydrated.some((receipt) => receipt === null)) {
455
+ throw new Error(`A canonical Record sale in transaction ${txDigest} references an unreadable Pressing`);
456
+ }
457
+ return hydrated as PurchaseReceipt[];
458
+ }
459
+
460
+ /** Read one canonical receipt selected by its Record ID. Selection is mandatory:
461
+ * one transaction may purchase multiple Records from the same Pressing. */
462
+ export async function getPurchaseReceipt(
463
+ client: MisoClient,
464
+ txDigest: string,
465
+ recordId: string,
466
+ ): Promise<PurchaseReceipt | null> {
467
+ const sales = await salesFromChain(client, txDigest);
468
+ const wanted = normalizeSuiAddress(recordId);
469
+ const sale = sales.find((candidate) => normalizeSuiAddress(candidate.recordId) === wanted);
470
+ return sale ? hydratePurchaseReceipt(client, sale) : null;
471
+ }
package/src/read/types.ts CHANGED
@@ -22,8 +22,7 @@
22
22
 
23
23
  /** Lifecycle state shared by compositions, recordings, and releases. */
24
24
  export type WorkState =
25
- | { type: "Initialized" }
26
- | { type: "Published"; timestampMs: number };
25
+ { type: "Initialized" } | { type: "Published"; timestampMs: number };
27
26
 
28
27
  /** A cover image's location on Walrus, plus the URL to fetch it from. */
29
28
  export interface CoverImage {
@@ -77,6 +76,8 @@ export interface TrackView {
77
76
  disc: number;
78
77
  /** Base64url Walrus blob id for the master stream, when attached on-chain. */
79
78
  masterBlobId?: string;
79
+ /** Base64url Walrus blob id for the public mix-delivery descriptor. */
80
+ mixBlobId?: string;
80
81
  }
81
82
 
82
83
  /** A release with everything a page renders: metadata, cover, credits, tracklist. */
@@ -118,13 +119,10 @@ export interface Currency {
118
119
  export interface PressingView {
119
120
  id: string;
120
121
  releaseId: string;
121
- /** The one run-wide sale switch. Scheduled carries its opening time. */
122
- state:
123
- | { kind: "scheduled"; startTimestampMs: number }
124
- | { kind: "active" }
125
- | { kind: "paused" };
126
- /** Records pressed so far, decimal string. The run is intentionally uncapped. */
127
- supply: string;
122
+ edition: number;
123
+ supply: number;
124
+ maxSupply: number | null;
125
+ distributors: string[];
128
126
  }
129
127
 
130
128
  /** One permanent currency offer derived from a Pressing. */
@@ -132,9 +130,9 @@ export interface ListingView {
132
130
  id: string;
133
131
  pressingId: string;
134
132
  releaseId: string;
135
- price: Price;
133
+ pricing: Price;
136
134
  currency: Currency;
137
- /** The per-currency switch; the Pressing state still governs every purchase. */
135
+ /** The per-currency purchase switch. */
138
136
  state: "enabled" | "disabled";
139
137
  }
140
138
 
@@ -181,8 +179,9 @@ export interface PressingPreview {
181
179
  title: string;
182
180
  subtitle: string | null;
183
181
  coverUrl: string | null;
184
- state: PressingView["state"];
185
- supply: string;
182
+ edition: number;
183
+ supply: number;
184
+ maxSupply: number | null;
186
185
  trackCount: number;
187
186
  }
188
187
 
@@ -284,9 +283,18 @@ export interface OwnedRecord {
284
283
  id: string;
285
284
  /** Full on-chain type, e.g. `<miso_record>::record::Record`. */
286
285
  type: string;
287
- releaseId: string | null;
288
- /** Copy / edition number, when the struct exposes one. */
289
- number: number | null;
286
+ releaseId: string;
287
+ pressingId: string;
288
+ edition: number;
289
+ number: number;
290
+ /** Defining Move type name of the currency used for the purchase. */
291
+ purchaseCurrency: string;
292
+ /** Lossless u64 base-unit amount actually paid. */
293
+ purchasePrice: string;
294
+ /** Transaction sender that purchased the Record. */
295
+ purchasedBy: string;
296
+ /** Lossless u64 Unix-millisecond purchase timestamp. */
297
+ purchasedTimestampMs: string;
290
298
  }
291
299
 
292
300
  /** A party the wallet administers (holds the `PartyAdminCap` for). */
@@ -332,12 +340,16 @@ export interface WorkDetail extends OwnedWork {
332
340
  trackCount?: number;
333
341
  }
334
342
 
335
- /** A wallet's spendable balance in one currency. */
343
+ /** A wallet's balance in one currency, split by Sui storage model. */
336
344
  export interface Balance {
337
345
  address: string;
338
346
  coinType: string;
339
- /** Base units, decimal string. Totals coin objects AND the address balance. */
347
+ /** Base units, decimal string. `coinBalance + addressBalance`. */
340
348
  balance: string;
349
+ /** Base units held in address-owned `Coin<T>` objects. */
350
+ coinBalance: string;
351
+ /** Base units held in the address balance and available to `FundsWithdrawal`. */
352
+ addressBalance: string;
341
353
  decimals: number;
342
354
  }
343
355
 
@@ -360,15 +372,20 @@ export interface RecordSale {
360
372
  releaseId: string;
361
373
  /** The `Record` object minted to the buyer. */
362
374
  recordId: string;
363
- /** This copy's number in the Pressing run (1-based), decimal string. */
364
- number: string;
365
- /** What the buyer actually paid, in the currency's base units. */
366
- paid: string;
375
+ edition: number;
376
+ /** This copy's number in the edition. */
377
+ number: number;
378
+ /** Embedded defining TypeName; validated against the event generic. */
379
+ purchaseCurrency: string;
380
+ /** What the buyer actually paid, in base units. */
381
+ purchasePrice: string;
367
382
  /** The fixed or floor price that the Listing accepted at this sale. */
368
- price: Price;
383
+ pricing: Price;
369
384
  /** Currency type carried by the `RecordSoldEvent` generic argument. */
370
385
  currencyType: string;
371
- buyer: string;
386
+ purchasedBy: string;
387
+ /** Lossless chain-clock timestamp captured by `listing::purchase`. */
388
+ purchasedTimestampMs: string;
372
389
  }
373
390
 
374
391
  /** One track's share of the paid amount, and how that share splits again. */