@misofm/platform 0.0.0 → 0.18.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/LICENSE +201 -0
- package/README.md +686 -3
- package/dist/auth.d.ts +73 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +193 -0
- package/dist/auth.js.map +1 -0
- package/dist/catalog.d.ts +48 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +92 -0
- package/dist/catalog.js.map +1 -0
- package/dist/client.d.ts +462 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +788 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts.d.ts +34 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +49 -0
- package/dist/contracts.js.map +1 -0
- package/dist/cover.d.ts +69 -0
- package/dist/cover.d.ts.map +1 -0
- package/dist/cover.js +132 -0
- package/dist/cover.js.map +1 -0
- package/dist/credits.d.ts +200 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +465 -0
- package/dist/credits.js.map +1 -0
- package/dist/deployments.d.ts +224 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.js +177 -0
- package/dist/deployments.js.map +1 -0
- package/dist/execute.d.ts +30 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +31 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +13 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +41 -0
- package/dist/internal.js.map +1 -0
- 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 +137 -0
- package/dist/pressing.d.ts.map +1 -0
- package/dist/pressing.js +390 -0
- package/dist/pressing.js.map +1 -0
- package/dist/publication.d.ts +216 -0
- package/dist/publication.d.ts.map +1 -0
- package/dist/publication.js +872 -0
- package/dist/publication.js.map +1 -0
- package/dist/queries.d.ts +2 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +8 -0
- package/dist/queries.js.map +1 -0
- package/dist/read/artist.d.ts +18 -0
- package/dist/read/artist.d.ts.map +1 -0
- package/dist/read/artist.js +86 -0
- package/dist/read/artist.js.map +1 -0
- package/dist/read/catalog.d.ts +93 -0
- package/dist/read/catalog.d.ts.map +1 -0
- package/dist/read/catalog.js +441 -0
- package/dist/read/catalog.js.map +1 -0
- package/dist/read/client.d.ts +37 -0
- package/dist/read/client.d.ts.map +1 -0
- package/dist/read/client.js +50 -0
- package/dist/read/client.js.map +1 -0
- package/dist/read/config.d.ts +68 -0
- package/dist/read/config.d.ts.map +1 -0
- package/dist/read/config.js +63 -0
- package/dist/read/config.js.map +1 -0
- package/dist/read/genres.d.ts +7 -0
- package/dist/read/genres.d.ts.map +1 -0
- package/dist/read/genres.js +38 -0
- package/dist/read/genres.js.map +1 -0
- package/dist/read/index.d.ts +14 -0
- package/dist/read/index.d.ts.map +1 -0
- package/dist/read/index.js +23 -0
- package/dist/read/index.js.map +1 -0
- package/dist/read/internal/scalars.d.ts +23 -0
- package/dist/read/internal/scalars.d.ts.map +1 -0
- package/dist/read/internal/scalars.js +57 -0
- package/dist/read/internal/scalars.js.map +1 -0
- package/dist/read/internal/walrus.d.ts +6 -0
- package/dist/read/internal/walrus.d.ts.map +1 -0
- package/dist/read/internal/walrus.js +29 -0
- package/dist/read/internal/walrus.js.map +1 -0
- package/dist/read/receipts.d.ts +41 -0
- package/dist/read/receipts.d.ts.map +1 -0
- package/dist/read/receipts.js +398 -0
- package/dist/read/receipts.js.map +1 -0
- package/dist/read/types.d.ts +328 -0
- package/dist/read/types.d.ts.map +1 -0
- package/dist/read/types.js +21 -0
- package/dist/read/types.js.map +1 -0
- package/dist/read/wallet.d.ts +74 -0
- package/dist/read/wallet.d.ts.map +1 -0
- package/dist/read/wallet.js +578 -0
- package/dist/read/wallet.js.map +1 -0
- package/dist/read/works.d.ts +29 -0
- package/dist/read/works.d.ts.map +1 -0
- package/dist/read/works.js +191 -0
- package/dist/read/works.js.map +1 -0
- package/dist/recording-extensions.d.ts +62 -0
- package/dist/recording-extensions.d.ts.map +1 -0
- package/dist/recording-extensions.js +159 -0
- package/dist/recording-extensions.js.map +1 -0
- package/dist/release-extensions.d.ts +93 -0
- package/dist/release-extensions.d.ts.map +1 -0
- package/dist/release-extensions.js +190 -0
- package/dist/release-extensions.js.map +1 -0
- package/dist/release-graph.d.ts +65 -0
- package/dist/release-graph.d.ts.map +1 -0
- package/dist/release-graph.js +136 -0
- package/dist/release-graph.js.map +1 -0
- package/dist/share-template.d.ts +3 -0
- package/dist/share-template.d.ts.map +1 -0
- package/dist/share-template.js +46 -0
- package/dist/share-template.js.map +1 -0
- package/dist/share.d.ts +67 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +157 -0
- package/dist/share.js.map +1 -0
- package/dist/transactions.d.ts +216 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/transactions.js +314 -0
- package/dist/transactions.js.map +1 -0
- package/dist/vault.d.ts +364 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +507 -0
- package/dist/vault.js.map +1 -0
- package/package.json +145 -5
- package/src/auth.ts +275 -0
- package/src/catalog.ts +189 -0
- package/src/client.ts +1320 -0
- package/src/contracts.ts +54 -0
- package/src/cover.ts +240 -0
- package/src/credits.ts +906 -0
- package/src/deployments.ts +357 -0
- package/src/execute.ts +52 -0
- package/src/index.ts +69 -0
- package/src/internal.ts +46 -0
- package/src/mix.ts +910 -0
- package/src/pressing.ts +705 -0
- package/src/publication.ts +1214 -0
- package/src/queries.ts +9 -0
- package/src/read/artist.ts +118 -0
- package/src/read/catalog.ts +668 -0
- package/src/read/client.ts +87 -0
- package/src/read/config.ts +138 -0
- package/src/read/genres.ts +37 -0
- package/src/read/index.ts +85 -0
- package/src/read/internal/scalars.ts +59 -0
- package/src/read/internal/walrus.ts +42 -0
- package/src/read/receipts.ts +476 -0
- package/src/read/types.ts +415 -0
- package/src/read/wallet.ts +687 -0
- package/src/read/works.ts +297 -0
- package/src/recording-extensions.ts +256 -0
- package/src/release-extensions.ts +311 -0
- package/src/release-graph.ts +219 -0
- package/src/share-template.ts +55 -0
- package/src/share.ts +261 -0
- package/src/transactions.ts +542 -0
- package/src/vault.ts +1009 -0
package/src/pressing.ts
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Primary Record sales. `miso_record` owns concrete Records and edition-scoped
|
|
5
|
+
// Pressings; immutable `miso_record_shop` owns per-currency Listings and payment.
|
|
6
|
+
|
|
7
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
8
|
+
import {
|
|
9
|
+
deriveObjectID,
|
|
10
|
+
normalizeStructTag,
|
|
11
|
+
normalizeSuiAddress,
|
|
12
|
+
parseStructTag,
|
|
13
|
+
} from "@mysten/sui/utils";
|
|
14
|
+
import type { TxThunk } from "./transactions.ts";
|
|
15
|
+
import { isNotFound } from "./queries.ts";
|
|
16
|
+
import { asU64, type U64Input } from "./vault.ts";
|
|
17
|
+
import * as pressingContract from "@misofm/protocol/contracts/miso_record/pressing";
|
|
18
|
+
import * as recordContract from "@misofm/protocol/contracts/miso_record/record";
|
|
19
|
+
import * as listingContract from "@misofm/protocol/contracts/miso_record_shop/listing";
|
|
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";
|
|
50
|
+
|
|
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
|
+
}
|
|
57
|
+
|
|
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
|
+
}
|
|
70
|
+
|
|
71
|
+
export function derivePressingId(
|
|
72
|
+
releaseId: string,
|
|
73
|
+
editionNumber: number,
|
|
74
|
+
recordPackageId: string,
|
|
75
|
+
): string {
|
|
76
|
+
const value = edition(editionNumber);
|
|
77
|
+
return deriveObjectID(
|
|
78
|
+
releaseId,
|
|
79
|
+
`${recordPackageId}::pressing::PressingKey`,
|
|
80
|
+
pressingContract.PressingKey.serialize([value]).toBytes(),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function derivePressingAdminCapId(
|
|
85
|
+
pressingId: string,
|
|
86
|
+
recordPackageId: string,
|
|
87
|
+
): string {
|
|
88
|
+
return deriveObjectID(
|
|
89
|
+
pressingId,
|
|
90
|
+
`${recordPackageId}::pressing::PressingAdminCapKey`,
|
|
91
|
+
UNIT_STRUCT_KEY_BYTES,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
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
|
+
|
|
110
|
+
export function deriveListingId(
|
|
111
|
+
pressingId: string,
|
|
112
|
+
currencyType: string,
|
|
113
|
+
recordShopPackageId: string,
|
|
114
|
+
): string {
|
|
115
|
+
return deriveObjectID(
|
|
116
|
+
pressingId,
|
|
117
|
+
`${recordShopPackageId}::listing::ListingKey<${normalizeStructTag(currencyType)}>`,
|
|
118
|
+
UNIT_STRUCT_KEY_BYTES,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function deriveSaleIds(
|
|
123
|
+
releaseId: string,
|
|
124
|
+
editionNumber: number,
|
|
125
|
+
currencyType: string,
|
|
126
|
+
recordPackageId: string,
|
|
127
|
+
recordShopPackageId: string,
|
|
128
|
+
): { pressingId: string; listingId: string } {
|
|
129
|
+
const pressingId = derivePressingId(
|
|
130
|
+
releaseId,
|
|
131
|
+
editionNumber,
|
|
132
|
+
recordPackageId,
|
|
133
|
+
);
|
|
134
|
+
return {
|
|
135
|
+
pressingId,
|
|
136
|
+
listingId: deriveListingId(pressingId, currencyType, recordShopPackageId),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type Tx = Parameters<TxThunk>[0];
|
|
141
|
+
|
|
142
|
+
function priceArg(tx: Tx, packageId: string, price: ListingPrice) {
|
|
143
|
+
const fn =
|
|
144
|
+
price.kind === "fixed" ? listingContract.fixed : listingContract.floor;
|
|
145
|
+
return tx.add(
|
|
146
|
+
fn({
|
|
147
|
+
package: packageId,
|
|
148
|
+
arguments: [asU64("listing price", price.amount)],
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
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 }));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function witnessType(recordShopPackageId: string): string {
|
|
160
|
+
return `${recordShopPackageId}::witness::Witness`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ListingTerms {
|
|
164
|
+
currencyType: string;
|
|
165
|
+
price: ListingPrice;
|
|
166
|
+
state?: ListingSwitch;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface OpenPressingParams {
|
|
170
|
+
releaseId: string;
|
|
171
|
+
releaseAdminCapId: string;
|
|
172
|
+
edition: number;
|
|
173
|
+
maxSupply?: number | null;
|
|
174
|
+
listings: readonly ListingTerms[];
|
|
175
|
+
adminCapRecipient: string;
|
|
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. */
|
|
182
|
+
export function openPressing(p: OpenPressingParams): TxThunk {
|
|
183
|
+
return (tx) => {
|
|
184
|
+
const [pressing, cap] = tx.add(
|
|
185
|
+
pressingContract._new({
|
|
186
|
+
package: p.recordPackageId,
|
|
187
|
+
arguments: [
|
|
188
|
+
p.releaseId,
|
|
189
|
+
p.releaseAdminCapId,
|
|
190
|
+
edition(p.edition),
|
|
191
|
+
maxSupply(p.maxSupply),
|
|
192
|
+
],
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
tx.add(
|
|
196
|
+
pressingContract.authorizeDistributor({
|
|
197
|
+
package: p.recordPackageId,
|
|
198
|
+
arguments: [pressing!, cap!],
|
|
199
|
+
typeArguments: [witnessType(p.recordShopPackageId)],
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
for (const terms of p.listings) {
|
|
203
|
+
const listing = tx.add(
|
|
204
|
+
listingContract._new({
|
|
205
|
+
package: p.recordShopPackageId,
|
|
206
|
+
arguments: [
|
|
207
|
+
pressing!,
|
|
208
|
+
cap!,
|
|
209
|
+
priceArg(tx, p.recordShopPackageId, terms.price),
|
|
210
|
+
],
|
|
211
|
+
typeArguments: [terms.currencyType],
|
|
212
|
+
}),
|
|
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
|
+
);
|
|
234
|
+
}
|
|
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
|
+
);
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface OpenListingParams {
|
|
277
|
+
pressingId: string;
|
|
278
|
+
pressingAdminCapId: string;
|
|
279
|
+
terms: ListingTerms;
|
|
280
|
+
recordShopPackageId: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Add one permanent currency Listing. Authorization is deliberately separate. */
|
|
284
|
+
export function openListing(p: OpenListingParams): TxThunk {
|
|
285
|
+
return (tx) => {
|
|
286
|
+
const listing = tx.add(
|
|
287
|
+
listingContract._new({
|
|
288
|
+
package: p.recordShopPackageId,
|
|
289
|
+
arguments: [
|
|
290
|
+
p.pressingId,
|
|
291
|
+
p.pressingAdminCapId,
|
|
292
|
+
priceArg(tx, p.recordShopPackageId, p.terms.price),
|
|
293
|
+
],
|
|
294
|
+
typeArguments: [p.terms.currencyType],
|
|
295
|
+
}),
|
|
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
|
+
);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface SetListingPriceParams {
|
|
321
|
+
pressingId: string;
|
|
322
|
+
currencyType: string;
|
|
323
|
+
pressingAdminCapId: string;
|
|
324
|
+
price: ListingPrice;
|
|
325
|
+
recordShopPackageId: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function setListingPrice(p: SetListingPriceParams): TxThunk {
|
|
329
|
+
return (tx) => {
|
|
330
|
+
tx.add(
|
|
331
|
+
listingContract.setPrice({
|
|
332
|
+
package: p.recordShopPackageId,
|
|
333
|
+
arguments: [
|
|
334
|
+
deriveListingId(p.pressingId, p.currencyType, p.recordShopPackageId),
|
|
335
|
+
p.pressingAdminCapId,
|
|
336
|
+
priceArg(tx, p.recordShopPackageId, p.price),
|
|
337
|
+
],
|
|
338
|
+
typeArguments: [p.currencyType],
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface SetListingStateParams {
|
|
345
|
+
pressingId: string;
|
|
346
|
+
currencyType: string;
|
|
347
|
+
pressingAdminCapId: string;
|
|
348
|
+
state: ListingSwitch;
|
|
349
|
+
recordShopPackageId: string;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function setListingState(p: SetListingStateParams): TxThunk {
|
|
353
|
+
return (tx) => {
|
|
354
|
+
tx.add(
|
|
355
|
+
listingContract.setState({
|
|
356
|
+
package: p.recordShopPackageId,
|
|
357
|
+
arguments: [
|
|
358
|
+
deriveListingId(p.pressingId, p.currencyType, p.recordShopPackageId),
|
|
359
|
+
p.pressingAdminCapId,
|
|
360
|
+
stateArg(tx, p.recordShopPackageId, p.state),
|
|
361
|
+
],
|
|
362
|
+
typeArguments: [p.currencyType],
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface PurchaseRecordParams {
|
|
369
|
+
releaseId: string;
|
|
370
|
+
edition: number;
|
|
371
|
+
paymentAmount: U64Input;
|
|
372
|
+
expectedPricing: ListingPrice;
|
|
373
|
+
currencyType: string;
|
|
374
|
+
recipient: string;
|
|
375
|
+
recordPackageId: string;
|
|
376
|
+
recordShopPackageId: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function purchaseRecord(p: PurchaseRecordParams): TxThunk {
|
|
380
|
+
return (tx) => {
|
|
381
|
+
const { pressingId, listingId } = deriveSaleIds(
|
|
382
|
+
p.releaseId,
|
|
383
|
+
p.edition,
|
|
384
|
+
p.currencyType,
|
|
385
|
+
p.recordPackageId,
|
|
386
|
+
p.recordShopPackageId,
|
|
387
|
+
);
|
|
388
|
+
const payment = tx.balance({
|
|
389
|
+
balance: asU64("purchase amount", p.paymentAmount),
|
|
390
|
+
type: p.currencyType,
|
|
391
|
+
useGasCoin: false,
|
|
392
|
+
});
|
|
393
|
+
const record = tx.add(
|
|
394
|
+
listingContract.purchase({
|
|
395
|
+
package: p.recordShopPackageId,
|
|
396
|
+
arguments: [
|
|
397
|
+
listingId,
|
|
398
|
+
pressingId,
|
|
399
|
+
payment,
|
|
400
|
+
priceArg(tx, p.recordShopPackageId, p.expectedPricing),
|
|
401
|
+
],
|
|
402
|
+
typeArguments: [p.currencyType],
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
tx.transferObjects([record], p.recipient);
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export interface PressingView {
|
|
410
|
+
id: string;
|
|
411
|
+
releaseId: string;
|
|
412
|
+
edition: number;
|
|
413
|
+
supply: number;
|
|
414
|
+
maxSupply: number | null;
|
|
415
|
+
distributors: string[];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface ListingView {
|
|
419
|
+
id: string;
|
|
420
|
+
releaseId: string;
|
|
421
|
+
pressingId: string;
|
|
422
|
+
pricing: { kind: "fixed" | "floor"; amount: string };
|
|
423
|
+
state: ListingSwitch;
|
|
424
|
+
currencyType: string;
|
|
425
|
+
}
|
|
426
|
+
|
|
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
|
+
|
|
439
|
+
async function fetch(
|
|
440
|
+
client: ClientWithCoreApi,
|
|
441
|
+
objectId: string,
|
|
442
|
+
): Promise<{ content: Uint8Array; type: string } | null> {
|
|
443
|
+
try {
|
|
444
|
+
const { object } = await client.core.getObject({
|
|
445
|
+
objectId,
|
|
446
|
+
include: { content: true },
|
|
447
|
+
});
|
|
448
|
+
if (!object) return null;
|
|
449
|
+
if (!object.content || !object.type)
|
|
450
|
+
throw new Error(`object ${objectId} has no BCS content or full type`);
|
|
451
|
+
return { content: object.content, type: object.type };
|
|
452
|
+
} catch (error) {
|
|
453
|
+
if (isNotFound(error)) return null;
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function sameId(a: string, b: string): boolean {
|
|
459
|
+
return normalizeSuiAddress(a) === normalizeSuiAddress(b);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function requireId(label: string, actual: string, expected: string): void {
|
|
463
|
+
if (!sameId(actual, expected))
|
|
464
|
+
throw new Error(`${label} ${actual} does not match expected ${expected}`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function requireExactType(type: string, expected: string, label: string): void {
|
|
468
|
+
const actual = normalizeStructTag(type);
|
|
469
|
+
const normalizedExpected = normalizeStructTag(expected);
|
|
470
|
+
if (actual !== normalizedExpected)
|
|
471
|
+
throw new Error(
|
|
472
|
+
`${label} has type ${actual}, expected ${normalizedExpected}`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function requireListingCurrency(
|
|
477
|
+
type: string,
|
|
478
|
+
recordShopPackageId: string,
|
|
479
|
+
): string {
|
|
480
|
+
const actual = normalizeStructTag(type);
|
|
481
|
+
const tag = parseStructTag(actual);
|
|
482
|
+
if (
|
|
483
|
+
tag.address !== normalizeSuiAddress(recordShopPackageId) ||
|
|
484
|
+
tag.module !== "listing" ||
|
|
485
|
+
tag.name !== "Listing" ||
|
|
486
|
+
tag.typeParams.length !== 1
|
|
487
|
+
)
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Listing has type ${actual}, not the configured Record Shop Listing<Currency>`,
|
|
490
|
+
);
|
|
491
|
+
return normalizeStructTag(tag.typeParams[0]!);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function enumKind(
|
|
495
|
+
value: { $kind?: string } & Record<string, unknown>,
|
|
496
|
+
names: readonly string[],
|
|
497
|
+
): string {
|
|
498
|
+
for (const name of names)
|
|
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 };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function parsePressing(
|
|
515
|
+
pressingId: string,
|
|
516
|
+
content: Uint8Array,
|
|
517
|
+
type: string,
|
|
518
|
+
recordPackageId: string,
|
|
519
|
+
): PressingView {
|
|
520
|
+
requireExactType(type, `${recordPackageId}::pressing::Pressing`, "Pressing");
|
|
521
|
+
const parsed = pressingContract.Pressing.parse(content);
|
|
522
|
+
requireId("Pressing UID", parsed.id, pressingId);
|
|
523
|
+
requireId(
|
|
524
|
+
"Pressing derived id",
|
|
525
|
+
pressingId,
|
|
526
|
+
derivePressingId(parsed.release_id, parsed.edition, recordPackageId),
|
|
527
|
+
);
|
|
528
|
+
return {
|
|
529
|
+
id: pressingId,
|
|
530
|
+
releaseId: parsed.release_id,
|
|
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
|
+
),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function parseListing(
|
|
541
|
+
listingId: string,
|
|
542
|
+
content: Uint8Array,
|
|
543
|
+
type: string,
|
|
544
|
+
recordShopPackageId: string,
|
|
545
|
+
): ListingView {
|
|
546
|
+
const currencyType = requireListingCurrency(type, recordShopPackageId);
|
|
547
|
+
const parsed = listingContract.Listing.parse(content);
|
|
548
|
+
requireId("Listing UID", parsed.id, listingId);
|
|
549
|
+
requireId(
|
|
550
|
+
"Listing derived id",
|
|
551
|
+
listingId,
|
|
552
|
+
deriveListingId(parsed.pressing_id, currencyType, recordShopPackageId),
|
|
553
|
+
);
|
|
554
|
+
const state = enumKind(parsed.state as never, ["Enabled", "Disabled"]);
|
|
555
|
+
return {
|
|
556
|
+
id: listingId,
|
|
557
|
+
releaseId: parsed.release_id,
|
|
558
|
+
pressingId: parsed.pressing_id,
|
|
559
|
+
pricing: parsePricing(parsed.pricing),
|
|
560
|
+
state: state === "Enabled" ? "enabled" : "disabled",
|
|
561
|
+
currencyType,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
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
|
+
|
|
625
|
+
export interface GetSaleParams {
|
|
626
|
+
releaseId: string;
|
|
627
|
+
edition: number;
|
|
628
|
+
currencyType: string;
|
|
629
|
+
recordPackageId: string;
|
|
630
|
+
recordShopPackageId: string;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export async function getSale(
|
|
634
|
+
client: ClientWithCoreApi,
|
|
635
|
+
p: GetSaleParams,
|
|
636
|
+
): Promise<{ pressing: PressingView | null; listing: ListingView | null }> {
|
|
637
|
+
const { pressingId, listingId } = deriveSaleIds(
|
|
638
|
+
p.releaseId,
|
|
639
|
+
p.edition,
|
|
640
|
+
p.currencyType,
|
|
641
|
+
p.recordPackageId,
|
|
642
|
+
p.recordShopPackageId,
|
|
643
|
+
);
|
|
644
|
+
const { objects } = await client.core.getObjects({
|
|
645
|
+
objectIds: [pressingId, listingId],
|
|
646
|
+
include: { content: true },
|
|
647
|
+
});
|
|
648
|
+
const [pressingObject, listingObject] = objects;
|
|
649
|
+
if (pressingObject instanceof Error && !isNotFound(pressingObject))
|
|
650
|
+
throw pressingObject;
|
|
651
|
+
if (listingObject instanceof Error && !isNotFound(listingObject))
|
|
652
|
+
throw listingObject;
|
|
653
|
+
const pressing =
|
|
654
|
+
!pressingObject || pressingObject instanceof Error
|
|
655
|
+
? null
|
|
656
|
+
: parseBatchPressing(
|
|
657
|
+
pressingId,
|
|
658
|
+
pressingObject.content,
|
|
659
|
+
pressingObject.type,
|
|
660
|
+
p.recordPackageId,
|
|
661
|
+
);
|
|
662
|
+
const listing =
|
|
663
|
+
!listingObject || listingObject instanceof Error
|
|
664
|
+
? null
|
|
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
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return { pressing, listing };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function parseBatchPressing(
|
|
686
|
+
id: string,
|
|
687
|
+
content: Uint8Array | undefined,
|
|
688
|
+
type: string | undefined,
|
|
689
|
+
packageId: string,
|
|
690
|
+
): PressingView {
|
|
691
|
+
if (!content || !type)
|
|
692
|
+
throw new Error(`Pressing ${id} has no BCS content or full type`);
|
|
693
|
+
return parsePressing(id, content, type, packageId);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function parseBatchListing(
|
|
697
|
+
id: string,
|
|
698
|
+
content: Uint8Array | undefined,
|
|
699
|
+
type: string | undefined,
|
|
700
|
+
packageId: string,
|
|
701
|
+
): ListingView {
|
|
702
|
+
if (!content || !type)
|
|
703
|
+
throw new Error(`Listing ${id} has no BCS content or full type`);
|
|
704
|
+
return parseListing(id, content, type, packageId);
|
|
705
|
+
}
|