@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.
- package/README.md +153 -56
- package/dist/client.d.ts +175 -47
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +302 -47
- package/dist/client.js.map +1 -1
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts +11 -11
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.d.ts.map +1 -1
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js +9 -9
- package/dist/contracts/composition_royalty_pool/composition_royalty_pool.js.map +1 -1
- package/dist/contracts/miso_record/deps/std/type_name.d.ts.map +1 -0
- package/dist/contracts/miso_record/deps/std/type_name.js.map +1 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.d.ts +21 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.d.ts.map +1 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.js +20 -0
- package/dist/contracts/miso_record/deps/sui/vec_set.js.map +1 -0
- package/dist/contracts/miso_record/pressing.d.ts +327 -0
- package/dist/contracts/miso_record/pressing.d.ts.map +1 -0
- package/dist/contracts/miso_record/pressing.js +345 -0
- package/dist/contracts/miso_record/pressing.js.map +1 -0
- package/dist/contracts/miso_record/record.d.ts +190 -0
- package/dist/contracts/miso_record/record.d.ts.map +1 -0
- package/dist/contracts/miso_record/record.js +226 -0
- package/dist/contracts/miso_record/record.js.map +1 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts +18 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.d.ts.map +1 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.js +20 -0
- package/dist/contracts/miso_record_shop/deps/std/type_name.js.map +1 -0
- package/dist/contracts/miso_record_shop/listing.d.ts +387 -0
- package/dist/contracts/miso_record_shop/listing.d.ts.map +1 -0
- package/dist/contracts/miso_record_shop/listing.js +380 -0
- package/dist/contracts/miso_record_shop/listing.js.map +1 -0
- package/dist/contracts/miso_record_shop/witness.d.ts +7 -0
- package/dist/contracts/miso_record_shop/witness.d.ts.map +1 -0
- package/dist/contracts/miso_record_shop/witness.js +9 -0
- package/dist/contracts/miso_record_shop/witness.js.map +1 -0
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts +10 -12
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.d.ts.map +1 -1
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js +8 -10
- package/dist/contracts/recording_royalty_pool/recording_royalty_pool.js.map +1 -1
- package/dist/contracts/vault/vault.d.ts +149 -23
- package/dist/contracts/vault/vault.d.ts.map +1 -1
- package/dist/contracts/vault/vault.js +144 -18
- package/dist/contracts/vault/vault.js.map +1 -1
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +5 -4
- package/dist/contracts.js.map +1 -1
- package/dist/deployments.d.ts +47 -11
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +48 -9
- package/dist/deployments.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mix.d.ts +144 -0
- package/dist/mix.d.ts.map +1 -0
- package/dist/mix.js +656 -0
- package/dist/mix.js.map +1 -0
- package/dist/pressing.d.ts +72 -156
- package/dist/pressing.d.ts.map +1 -1
- package/dist/pressing.js +269 -261
- package/dist/pressing.js.map +1 -1
- package/dist/publication.d.ts +5 -2
- package/dist/publication.d.ts.map +1 -1
- package/dist/publication.js +63 -34
- package/dist/publication.js.map +1 -1
- package/dist/read/catalog.d.ts +8 -5
- package/dist/read/catalog.d.ts.map +1 -1
- package/dist/read/catalog.js +47 -35
- package/dist/read/catalog.js.map +1 -1
- package/dist/read/config.d.ts +6 -4
- package/dist/read/config.d.ts.map +1 -1
- package/dist/read/config.js +1 -2
- package/dist/read/config.js.map +1 -1
- package/dist/read/index.d.ts +1 -1
- package/dist/read/index.d.ts.map +1 -1
- package/dist/read/index.js +1 -1
- package/dist/read/index.js.map +1 -1
- package/dist/read/internal/scalars.d.ts.map +1 -1
- package/dist/read/internal/scalars.js +4 -3
- package/dist/read/internal/scalars.js.map +1 -1
- package/dist/read/internal/walrus.d.ts.map +1 -1
- package/dist/read/internal/walrus.js +2 -1
- package/dist/read/internal/walrus.js.map +1 -1
- package/dist/read/receipts.d.ts +21 -12
- package/dist/read/receipts.d.ts.map +1 -1
- package/dist/read/receipts.js +159 -56
- package/dist/read/receipts.js.map +1 -1
- package/dist/read/types.d.ts +40 -26
- package/dist/read/types.d.ts.map +1 -1
- package/dist/read/wallet.d.ts +3 -4
- package/dist/read/wallet.d.ts.map +1 -1
- package/dist/read/wallet.js +72 -53
- package/dist/read/wallet.js.map +1 -1
- package/dist/vault.d.ts +71 -15
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +135 -29
- package/dist/vault.js.map +1 -1
- package/package.json +10 -3
- package/src/client.ts +404 -69
- package/src/contracts/composition_royalty_pool/composition_royalty_pool.ts +15 -15
- package/src/contracts/miso_record/deps/sui/vec_set.ts +21 -0
- package/src/contracts/miso_record/pressing.ts +522 -0
- package/src/contracts/miso_record/record.ts +338 -0
- package/src/contracts/miso_record_shop/deps/std/type_name.ts +22 -0
- package/src/contracts/miso_record_shop/listing.ts +602 -0
- package/src/contracts/miso_record_shop/witness.ts +11 -0
- package/src/contracts/recording_royalty_pool/recording_royalty_pool.ts +14 -16
- package/src/contracts/vault/vault.ts +226 -21
- package/src/contracts.ts +5 -4
- package/src/deployments.ts +134 -38
- package/src/index.ts +2 -0
- package/src/mix.ts +910 -0
- package/src/pressing.ts +428 -423
- package/src/publication.ts +69 -36
- package/src/read/catalog.ts +49 -34
- package/src/read/config.ts +7 -7
- package/src/read/index.ts +9 -1
- package/src/read/internal/scalars.ts +4 -2
- package/src/read/internal/walrus.ts +2 -1
- package/src/read/receipts.ts +181 -66
- package/src/read/types.ts +41 -24
- package/src/read/wallet.ts +70 -57
- package/src/vault.ts +228 -41
- package/dist/contracts/miso_pressing/certificate.d.ts +0 -89
- package/dist/contracts/miso_pressing/certificate.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/certificate.js +0 -108
- package/dist/contracts/miso_pressing/certificate.js.map +0 -1
- package/dist/contracts/miso_pressing/deps/std/type_name.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/deps/std/type_name.js.map +0 -1
- package/dist/contracts/miso_pressing/listing.d.ts +0 -434
- package/dist/contracts/miso_pressing/listing.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/listing.js +0 -401
- package/dist/contracts/miso_pressing/listing.js.map +0 -1
- package/dist/contracts/miso_pressing/pressing.d.ts +0 -334
- package/dist/contracts/miso_pressing/pressing.d.ts.map +0 -1
- package/dist/contracts/miso_pressing/pressing.js +0 -298
- package/dist/contracts/miso_pressing/pressing.js.map +0 -1
- package/src/contracts/miso_pressing/certificate.ts +0 -164
- package/src/contracts/miso_pressing/listing.ts +0 -592
- package/src/contracts/miso_pressing/pressing.ts +0 -399
- /package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.d.ts +0 -0
- /package/dist/contracts/{miso_pressing → miso_record}/deps/std/type_name.js +0 -0
- /package/src/contracts/{miso_pressing → miso_record}/deps/std/type_name.ts +0 -0
package/src/read/receipts.ts
CHANGED
|
@@ -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::
|
|
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: `
|
|
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/
|
|
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
|
|
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
|
-
/** `
|
|
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?:
|
|
62
|
-
Floor?:
|
|
63
|
-
fixed?: { amount?: unknown };
|
|
64
|
-
floor?: { amount?: unknown };
|
|
66
|
+
Fixed?: unknown;
|
|
67
|
+
Floor?: unknown;
|
|
65
68
|
};
|
|
66
|
-
const fixed = variants.$kind === "Fixed" ||
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
84
|
-
const
|
|
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
|
|
87
|
-
|
|
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:
|
|
90
|
-
pressingId:
|
|
91
|
-
releaseId:
|
|
136
|
+
listingId: json.listing_id,
|
|
137
|
+
pressingId: json.pressing_id,
|
|
138
|
+
releaseId: json.release_id,
|
|
92
139
|
recordId,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
edition,
|
|
141
|
+
number,
|
|
142
|
+
purchaseCurrency: embeddedCurrency,
|
|
143
|
+
purchasePrice: purchasePrice.toString(),
|
|
144
|
+
pricing,
|
|
96
145
|
currencyType,
|
|
97
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
189
|
+
recordShopPackageId: string,
|
|
140
190
|
): boolean {
|
|
141
|
-
return recordSoldCurrencyType(eventType,
|
|
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
|
|
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
|
-
|
|
155
|
-
): RecordSale
|
|
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
|
-
|
|
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
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
edition: s.edition,
|
|
230
|
+
number: s.number,
|
|
231
|
+
purchaseCurrency: embeddedCurrency,
|
|
232
|
+
purchasePrice: s.purchase_price,
|
|
233
|
+
pricing,
|
|
175
234
|
currencyType,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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)
|
|
249
|
+
if (!fromJson) throw new MalformedRecordSoldEventError("malformed canonical RecordSoldEvent JSON");
|
|
250
|
+
sales.push(fromJson);
|
|
183
251
|
}
|
|
184
|
-
return
|
|
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
|
-
/**
|
|
188
|
-
async function
|
|
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
|
|
277
|
+
const sales = requireRecordSalesDeployment(client.config.recordSales);
|
|
278
|
+
const found = findRecordSales(
|
|
198
279
|
result.Transaction.events,
|
|
199
|
-
|
|
280
|
+
sales.recordShopPackageId,
|
|
200
281
|
);
|
|
201
|
-
if (
|
|
202
|
-
return
|
|
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
|
-
/**
|
|
224
|
-
async function
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
426
|
+
async function hydratePurchaseReceipt(
|
|
335
427
|
client: MisoClient,
|
|
336
|
-
|
|
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.
|
|
354
|
-
tracks: breakdown(detail.release.tracks, sale.
|
|
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: "
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
133
|
+
pricing: Price;
|
|
136
134
|
currency: Currency;
|
|
137
|
-
/** The per-currency switch
|
|
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
|
-
|
|
185
|
-
supply:
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
364
|
-
number
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
383
|
+
pricing: Price;
|
|
369
384
|
/** Currency type carried by the `RecordSoldEvent` generic argument. */
|
|
370
385
|
currencyType: string;
|
|
371
|
-
|
|
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. */
|