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