@misofm/sdk 0.1.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 +248 -0
- package/dist/catalog.d.ts +31 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +45 -0
- package/dist/catalog.js.map +1 -0
- package/dist/client.d.ts +134 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +113 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts/miso_pressing/certificate.d.ts +78 -0
- package/dist/contracts/miso_pressing/certificate.d.ts.map +1 -0
- package/dist/contracts/miso_pressing/certificate.js +91 -0
- package/dist/contracts/miso_pressing/certificate.js.map +1 -0
- package/dist/contracts/miso_pressing/deps/std/type_name.d.ts +18 -0
- package/dist/contracts/miso_pressing/deps/std/type_name.d.ts.map +1 -0
- package/dist/contracts/miso_pressing/deps/std/type_name.js +20 -0
- package/dist/contracts/miso_pressing/deps/std/type_name.js.map +1 -0
- package/dist/contracts/miso_pressing/listing.d.ts +449 -0
- package/dist/contracts/miso_pressing/listing.d.ts.map +1 -0
- package/dist/contracts/miso_pressing/listing.js +416 -0
- package/dist/contracts/miso_pressing/listing.js.map +1 -0
- package/dist/contracts/miso_pressing/pressing.d.ts +365 -0
- package/dist/contracts/miso_pressing/pressing.d.ts.map +1 -0
- package/dist/contracts/miso_pressing/pressing.js +335 -0
- package/dist/contracts/miso_pressing/pressing.js.map +1 -0
- package/dist/contracts/utils/index.d.ts +31 -0
- package/dist/contracts/utils/index.d.ts.map +1 -0
- package/dist/contracts/utils/index.js +162 -0
- package/dist/contracts/utils/index.js.map +1 -0
- package/dist/execute.d.ts +17 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +21 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/pressing.d.ts +223 -0
- package/dist/pressing.d.ts.map +1 -0
- package/dist/pressing.js +280 -0
- package/dist/pressing.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/release-graph.d.ts +93 -0
- package/dist/release-graph.d.ts.map +1 -0
- package/dist/release-graph.js +143 -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 +66 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +156 -0
- package/dist/share.js.map +1 -0
- package/dist/transactions.d.ts +177 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/transactions.js +237 -0
- package/dist/transactions.js.map +1 -0
- package/package.json +99 -0
- package/src/catalog.ts +77 -0
- package/src/client.ts +249 -0
- package/src/contracts/miso_pressing/certificate.ts +129 -0
- package/src/contracts/miso_pressing/deps/std/type_name.ts +22 -0
- package/src/contracts/miso_pressing/listing.ts +621 -0
- package/src/contracts/miso_pressing/pressing.ts +454 -0
- package/src/contracts/utils/index.ts +229 -0
- package/src/execute.ts +34 -0
- package/src/index.ts +29 -0
- package/src/pressing.ts +497 -0
- package/src/queries.ts +9 -0
- package/src/release-graph.ts +279 -0
- package/src/share-template.ts +55 -0
- package/src/share.ts +258 -0
- package/src/transactions.ts +440 -0
package/src/execute.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// `executeViaExecutor` — the one piece of transaction execution that's specific
|
|
5
|
+
// to this package's opinionated publish flows (batched, non-idempotent share-
|
|
6
|
+
// currency publishing in `share.ts`). Everything else — `buildTx`,
|
|
7
|
+
// `signAndExecute`, `toExecResult`, the object-change extractors — is generic
|
|
8
|
+
// PTB execution plumbing that stays in `@misonetwork/sdk`'s `execute.ts` and is
|
|
9
|
+
// re-exported from there; this module builds on top of it.
|
|
10
|
+
|
|
11
|
+
import type { ParallelTransactionExecutor } from "@mysten/sui/transactions";
|
|
12
|
+
import type { SuiClientTypes } from "@mysten/sui/client";
|
|
13
|
+
import { buildTx, toExecResult, FULL_INCLUDE, type ExecResult } from "@misonetwork/sdk";
|
|
14
|
+
import type { TxThunk } from "./transactions.ts";
|
|
15
|
+
|
|
16
|
+
type FullInclude = typeof FULL_INCLUDE;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Builds a transaction from thunks and executes it through the parallel executor,
|
|
20
|
+
* exactly ONCE.
|
|
21
|
+
*
|
|
22
|
+
* These PTBs are typically non-idempotent (publish a package, initialize a
|
|
23
|
+
* currency, consume a TreasuryCap). A thrown transport error is ambiguous — the tx
|
|
24
|
+
* may already have committed — so blindly rebuilding and re-submitting would risk
|
|
25
|
+
* double-execution or a guaranteed abort against already-consumed inputs. We
|
|
26
|
+
* therefore do NOT auto-retry: any error propagates and recovery is the caller's
|
|
27
|
+
* job (e.g. a resumable checkpoint that reconciles against on-chain state). A Move
|
|
28
|
+
* abort RESOLVES as a `FailedTransaction`, so `toExecResult` surfaces it too.
|
|
29
|
+
*/
|
|
30
|
+
export async function executeViaExecutor(executor: ParallelTransactionExecutor, ...thunks: TxThunk[]): Promise<ExecResult> {
|
|
31
|
+
const tx = await buildTx(...thunks);
|
|
32
|
+
const res: SuiClientTypes.TransactionResult<FullInclude> = await executor.executeTransaction(tx, FULL_INCLUDE);
|
|
33
|
+
return toExecResult(res);
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// `@misofm/sdk` — the PLATFORM layer.
|
|
5
|
+
//
|
|
6
|
+
// Miso's own product sits on top of the permissionless protocol, and the two
|
|
7
|
+
// have different names on npm because they have different promises:
|
|
8
|
+
//
|
|
9
|
+
// @misonetwork/* PROTOCOL — Composition, Recording, Release. The open layer
|
|
10
|
+
// anyone can build on, and nobody needs Miso's permission to use.
|
|
11
|
+
// @misofm/* PLATFORM — Pressing, Listing, Record. How MISO sells copies of
|
|
12
|
+
// a release: the production line, its currencies, and the record
|
|
13
|
+
// a buyer walks away with.
|
|
14
|
+
//
|
|
15
|
+
// A release is protocol. Pressing a record off it and selling that record is
|
|
16
|
+
// platform. Keeping the split at the package boundary is what stops the protocol
|
|
17
|
+
// from quietly growing a storefront.
|
|
18
|
+
|
|
19
|
+
// The recommended entry point is the client extension (see ./client.ts); the bare
|
|
20
|
+
// builders and readers stay exported for callers that hold ids themselves.
|
|
21
|
+
export { misoPlatform, MisoPlatformClient } from "./client.ts";
|
|
22
|
+
export type { MisoPlatformConfig } from "./client.ts";
|
|
23
|
+
export * from "./pressing.ts";
|
|
24
|
+
export * from "./transactions.ts";
|
|
25
|
+
export * from "./execute.ts";
|
|
26
|
+
export * from "./share.ts";
|
|
27
|
+
export * from "./share-template.ts";
|
|
28
|
+
export * from "./release-graph.ts";
|
|
29
|
+
export * from "./catalog.ts";
|
package/src/pressing.ts
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Pressing — the record production line. A release has exactly ONE `Pressing`: a
|
|
5
|
+
// single uncapped run whose counter numbers every copy that release will ever sell,
|
|
6
|
+
// forever. There are no editions, no supply caps, no sold-out state, and no expiry —
|
|
7
|
+
// the run is `Scheduled → Active → Paused → Active` and nothing else. Selling in a
|
|
8
|
+
// currency is a `Listing<Currency>` derived off the pressing: one per currency, ever,
|
|
9
|
+
// permanent, edited in place rather than replaced. A sale needs both switches open.
|
|
10
|
+
//
|
|
11
|
+
// EVERYTHING IS ADDRESS MATH. The pressing's UID derives off its release's, each
|
|
12
|
+
// listing's off the pressing's. So there is no registry and no pointer to follow:
|
|
13
|
+
// `derivePressingId`/`deriveListingId` answer "where is it" offline, which is why the
|
|
14
|
+
// builders here take a RELEASE id and compute the rest. A caller cannot pass a
|
|
15
|
+
// listing that belongs to some other pressing, because it never picks one.
|
|
16
|
+
|
|
17
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
18
|
+
import { deriveObjectID, normalizeStructTag } from "@mysten/sui/utils";
|
|
19
|
+
import type { TxThunk } from "./transactions.ts";
|
|
20
|
+
import { isNotFound } from "./queries.ts";
|
|
21
|
+
import * as listing from "./contracts/miso_pressing/listing.ts";
|
|
22
|
+
import * as pressing from "./contracts/miso_pressing/pressing.ts";
|
|
23
|
+
|
|
24
|
+
/** Pricing policy: pay exactly `amount` (fixed) or at least `amount` (floor). */
|
|
25
|
+
export type ListingPrice = { kind: "fixed" | "floor"; amount: bigint | number | string };
|
|
26
|
+
|
|
27
|
+
/** Whether a currency's listing takes payment. Below the pressing's own run state. */
|
|
28
|
+
export type ListingSwitch = "enabled" | "disabled";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The run's state, across every currency at once.
|
|
32
|
+
*
|
|
33
|
+
* `scheduled` is transitional: the first sale past `startTimestampMs` rewrites it to
|
|
34
|
+
* `active` inside `mint_next`, and the start time leaves the object with it. "When was
|
|
35
|
+
* this run scheduled for" is an event-log question once a run has sold anything.
|
|
36
|
+
*/
|
|
37
|
+
export type PressingRunState =
|
|
38
|
+
| { kind: "scheduled"; startTimestampMs: bigint | number }
|
|
39
|
+
| { kind: "active" }
|
|
40
|
+
| { kind: "paused" };
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Address math
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/** Key bytes for Move unit structs (single `0x00` for `dummy_field: bool = false`). */
|
|
47
|
+
const UNIT_STRUCT_KEY_BYTES = new Uint8Array([0x00]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Where `releaseId`'s pressing lives — computable before it has been opened, and
|
|
51
|
+
* stable forever after, because a release can only ever claim one.
|
|
52
|
+
*
|
|
53
|
+
* `misoPressingPackageId` must be the package's ORIGINAL (first-published) id, since
|
|
54
|
+
* that is the address a Move type tag canonicalises to. `miso_pressing` is published
|
|
55
|
+
* immutable, so its original and current ids are the same.
|
|
56
|
+
*/
|
|
57
|
+
export function derivePressingId(releaseId: string, misoPressingPackageId: string): string {
|
|
58
|
+
return deriveObjectID(releaseId, `${misoPressingPackageId}::pressing::PressingKey`, UNIT_STRUCT_KEY_BYTES);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Where `pressingId`'s admin cap was minted. The cap is transferable, so this is not
|
|
63
|
+
* where it lives — only where it came from.
|
|
64
|
+
*/
|
|
65
|
+
export function derivePressingAdminCapId(pressingId: string, misoPressingPackageId: string): string {
|
|
66
|
+
return deriveObjectID(
|
|
67
|
+
pressingId,
|
|
68
|
+
`${misoPressingPackageId}::pressing::PressingAdminCapKey`,
|
|
69
|
+
UNIT_STRUCT_KEY_BYTES,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Where `currencyType`'s listing on `pressingId` lives. The key is phantom-typed, so
|
|
75
|
+
* the currency rides in the key's TYPE — one listing per (pressing, currency), ever.
|
|
76
|
+
*/
|
|
77
|
+
export function deriveListingId(
|
|
78
|
+
pressingId: string,
|
|
79
|
+
currencyType: string,
|
|
80
|
+
misoPressingPackageId: string,
|
|
81
|
+
): string {
|
|
82
|
+
return deriveObjectID(
|
|
83
|
+
pressingId,
|
|
84
|
+
`${misoPressingPackageId}::listing::ListingKey<${normalizeStructTag(currencyType)}>`,
|
|
85
|
+
UNIT_STRUCT_KEY_BYTES,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Both derived ids a sale needs, from the release id alone. */
|
|
90
|
+
export function deriveSaleIds(
|
|
91
|
+
releaseId: string,
|
|
92
|
+
currencyType: string,
|
|
93
|
+
misoPressingPackageId: string,
|
|
94
|
+
): { pressingId: string; listingId: string } {
|
|
95
|
+
const pressingId = derivePressingId(releaseId, misoPressingPackageId);
|
|
96
|
+
return { pressingId, listingId: deriveListingId(pressingId, currencyType, misoPressingPackageId) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Term constructors (shared by the builders below)
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
type Tx = Parameters<TxThunk>[0];
|
|
104
|
+
|
|
105
|
+
function priceArg(tx: Tx, misoPressingPackageId: string, price: ListingPrice) {
|
|
106
|
+
const make = price.kind === "fixed" ? listing.newFixedPrice : listing.newFloorPrice;
|
|
107
|
+
return tx.add(make({ package: misoPressingPackageId, arguments: [BigInt(price.amount)] }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function switchArg(tx: Tx, misoPressingPackageId: string, state: ListingSwitch) {
|
|
111
|
+
const make = state === "enabled" ? listing.newEnabledState : listing.newDisabledState;
|
|
112
|
+
return tx.add(make({ package: misoPressingPackageId }));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function runStateArg(tx: Tx, misoPressingPackageId: string, state: PressingRunState) {
|
|
116
|
+
const pkg = { package: misoPressingPackageId };
|
|
117
|
+
switch (state.kind) {
|
|
118
|
+
case "scheduled":
|
|
119
|
+
return tx.add(pressing.newScheduledState({ ...pkg, arguments: [BigInt(state.startTimestampMs)] }));
|
|
120
|
+
case "active":
|
|
121
|
+
return tx.add(pressing.newActiveState(pkg));
|
|
122
|
+
case "paused":
|
|
123
|
+
return tx.add(pressing.newPausedState(pkg));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Opening the run
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
/** One currency's offer, as `openPressing` takes it. */
|
|
132
|
+
export interface ListingTerms {
|
|
133
|
+
/** The `Currency` type argument, e.g. `0x2::sui::SUI`. */
|
|
134
|
+
currencyType: string;
|
|
135
|
+
price: ListingPrice;
|
|
136
|
+
/** Defaults to `"enabled"` — a listing opened disabled takes no payment yet. */
|
|
137
|
+
state?: ListingSwitch;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface OpenPressingParams {
|
|
141
|
+
/** The `Release` this run presses copies of — also the pressing's derivation parent. */
|
|
142
|
+
releaseId: string;
|
|
143
|
+
/** The release's `ReleaseAdminCap`. Needed ONLY to open the run; every later change
|
|
144
|
+
* authorizes against the `PressingAdminCap` this mints. */
|
|
145
|
+
releaseAdminCapId: string;
|
|
146
|
+
/** Currencies to open the run in. May be empty — listings can be added later with
|
|
147
|
+
* {@link openListing} — but a run with no listings sells nothing. */
|
|
148
|
+
listings: ListingTerms[];
|
|
149
|
+
/** Run state at open. Defaults to `active` (sell immediately); `scheduled` is the
|
|
150
|
+
* trustless drop moment, and needs no later transaction to open. */
|
|
151
|
+
state?: PressingRunState;
|
|
152
|
+
/** Where to send the minted `PressingAdminCap` (usually the artist or their vault). */
|
|
153
|
+
adminCapRecipient: string;
|
|
154
|
+
misoPressingPackageId: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Opens a release's pressing and lists it, in ONE transaction.
|
|
159
|
+
*
|
|
160
|
+
* `pressing::new` hands the `Pressing` back UNSHARED precisely so the listings can be
|
|
161
|
+
* claimed off it before `share` puts it on chain — that ordering is the whole reason
|
|
162
|
+
* this is a single builder and not three. A release can only ever open one pressing,
|
|
163
|
+
* so this call is once-per-release forever; changing price or currency afterwards is
|
|
164
|
+
* an edit ({@link setListingPrice}, {@link openListing}), never a re-open.
|
|
165
|
+
*/
|
|
166
|
+
export function openPressing(p: OpenPressingParams): TxThunk {
|
|
167
|
+
return (tx) => {
|
|
168
|
+
const pkg = p.misoPressingPackageId;
|
|
169
|
+
const [run, adminCap] = tx.add(
|
|
170
|
+
pressing._new({
|
|
171
|
+
package: pkg,
|
|
172
|
+
arguments: [p.releaseId, p.releaseAdminCapId, runStateArg(tx, pkg, p.state ?? { kind: "active" })],
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
for (const terms of p.listings) {
|
|
176
|
+
tx.add(
|
|
177
|
+
listing._new({
|
|
178
|
+
package: pkg,
|
|
179
|
+
arguments: [
|
|
180
|
+
run!,
|
|
181
|
+
adminCap!,
|
|
182
|
+
priceArg(tx, pkg, terms.price),
|
|
183
|
+
switchArg(tx, pkg, terms.state ?? "enabled"),
|
|
184
|
+
],
|
|
185
|
+
typeArguments: [terms.currencyType],
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
tx.add(pressing.share({ package: pkg, arguments: [run!] }));
|
|
190
|
+
tx.transferObjects([adminCap!], p.adminCapRecipient);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface OpenListingParams {
|
|
195
|
+
/** The release whose pressing gains a currency. The pressing id derives from it. */
|
|
196
|
+
releaseId: string;
|
|
197
|
+
pressingAdminCapId: string;
|
|
198
|
+
terms: ListingTerms;
|
|
199
|
+
misoPressingPackageId: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Adds a currency to a live pressing. Once per currency, ever — the slot is a derived
|
|
204
|
+
* claim, so a second call for the same currency aborts. Everything after is an edit.
|
|
205
|
+
*/
|
|
206
|
+
export function openListing(p: OpenListingParams): TxThunk {
|
|
207
|
+
return (tx) => {
|
|
208
|
+
const pkg = p.misoPressingPackageId;
|
|
209
|
+
tx.add(
|
|
210
|
+
listing._new({
|
|
211
|
+
package: pkg,
|
|
212
|
+
arguments: [
|
|
213
|
+
derivePressingId(p.releaseId, pkg),
|
|
214
|
+
p.pressingAdminCapId,
|
|
215
|
+
priceArg(tx, pkg, p.terms.price),
|
|
216
|
+
switchArg(tx, pkg, p.terms.state ?? "enabled"),
|
|
217
|
+
],
|
|
218
|
+
typeArguments: [p.terms.currencyType],
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// Editing the offer
|
|
226
|
+
// ============================================================================
|
|
227
|
+
|
|
228
|
+
export interface SetListingPriceParams {
|
|
229
|
+
releaseId: string;
|
|
230
|
+
currencyType: string;
|
|
231
|
+
pressingAdminCapId: string;
|
|
232
|
+
price: ListingPrice;
|
|
233
|
+
misoPressingPackageId: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Reprices one currency in place, keeping the listing's address and identity. Safe
|
|
238
|
+
* against in-flight buys by construction: a `fixed` buyer pays exactly, so a stale
|
|
239
|
+
* payment aborts against the new price, and a `floor` buyer never pays more than the
|
|
240
|
+
* balance they sent.
|
|
241
|
+
*/
|
|
242
|
+
export function setListingPrice(p: SetListingPriceParams): TxThunk {
|
|
243
|
+
return (tx) => {
|
|
244
|
+
const pkg = p.misoPressingPackageId;
|
|
245
|
+
const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
|
|
246
|
+
tx.add(
|
|
247
|
+
listing.setPrice({
|
|
248
|
+
package: pkg,
|
|
249
|
+
arguments: [listingId, p.pressingAdminCapId, priceArg(tx, pkg, p.price)],
|
|
250
|
+
typeArguments: [p.currencyType],
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface SetListingStateParams {
|
|
257
|
+
releaseId: string;
|
|
258
|
+
currencyType: string;
|
|
259
|
+
pressingAdminCapId: string;
|
|
260
|
+
state: ListingSwitch;
|
|
261
|
+
misoPressingPackageId: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Stops or resumes ONE currency. The run's other currencies carry on. */
|
|
265
|
+
export function setListingState(p: SetListingStateParams): TxThunk {
|
|
266
|
+
return (tx) => {
|
|
267
|
+
const pkg = p.misoPressingPackageId;
|
|
268
|
+
const { listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
|
|
269
|
+
tx.add(
|
|
270
|
+
listing.setState({
|
|
271
|
+
package: pkg,
|
|
272
|
+
arguments: [listingId, p.pressingAdminCapId, switchArg(tx, pkg, p.state)],
|
|
273
|
+
typeArguments: [p.currencyType],
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface SetPressingStateParams {
|
|
280
|
+
releaseId: string;
|
|
281
|
+
pressingAdminCapId: string;
|
|
282
|
+
state: PressingRunState;
|
|
283
|
+
misoPressingPackageId: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Moves the whole run between its three modes, over every currency at once. There is
|
|
288
|
+
* no "end" — ending a campaign is `{ kind: "paused" }`.
|
|
289
|
+
*/
|
|
290
|
+
export function setPressingState(p: SetPressingStateParams): TxThunk {
|
|
291
|
+
return (tx) => {
|
|
292
|
+
const pkg = p.misoPressingPackageId;
|
|
293
|
+
tx.add(
|
|
294
|
+
pressing.setState({
|
|
295
|
+
package: pkg,
|
|
296
|
+
arguments: [
|
|
297
|
+
derivePressingId(p.releaseId, pkg),
|
|
298
|
+
p.pressingAdminCapId,
|
|
299
|
+
runStateArg(tx, pkg, p.state),
|
|
300
|
+
],
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// Buying
|
|
308
|
+
// ============================================================================
|
|
309
|
+
|
|
310
|
+
export interface BuyRecordParams {
|
|
311
|
+
/**
|
|
312
|
+
* The `Release` being bought a copy of. The pressing and this currency's listing
|
|
313
|
+
* BOTH derive from it (see {@link deriveSaleIds}), so there is nothing to look up
|
|
314
|
+
* and no way to pair a listing with the wrong pressing.
|
|
315
|
+
*/
|
|
316
|
+
releaseId: string;
|
|
317
|
+
/** Amount to pay in the currency's smallest unit (exact for `fixed`, ≥ for `floor`). */
|
|
318
|
+
amount: bigint | number | string;
|
|
319
|
+
/** The listing's `Currency` type, e.g. `0x2::sui::SUI`. */
|
|
320
|
+
currencyType: string;
|
|
321
|
+
/** The `miso_record` `Settings` shared object — it must authorize `miso_pressing`'s
|
|
322
|
+
* `MintWitness`, or the mint aborts. */
|
|
323
|
+
settingsId: string;
|
|
324
|
+
/** Where to send the pressed `Record` (usually the buyer). */
|
|
325
|
+
recipient: string;
|
|
326
|
+
misoPressingPackageId: string;
|
|
327
|
+
/**
|
|
328
|
+
* Set only when the buyer pays their OWN gas. Miso sponsors every purchase — the gas
|
|
329
|
+
* coin is the sponsor's, so drawing a payment out of it would spend the wrong
|
|
330
|
+
* wallet's money. Defaults to sponsored; bites only on SUI-priced listings.
|
|
331
|
+
*/
|
|
332
|
+
useGasCoin?: boolean;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Buys one record: sources the payment, calls `listing::buy`, and sends the pressed
|
|
337
|
+
* `Record` to `recipient`. `buy` RETURNS the record rather than transferring it, so
|
|
338
|
+
* the transfer here is not optional. The `Clock` is auto-supplied.
|
|
339
|
+
*
|
|
340
|
+
* ONE payment path, no coin picking. `tx.balance()` resolves the money at build time,
|
|
341
|
+
* preferring the buyer's ADDRESS BALANCE — where value is assumed to live — and falling
|
|
342
|
+
* back to coin objects inside the SDK if it must. Callers never enumerate coins: that
|
|
343
|
+
* road showed a buyer their $1000 and then refused to spend a cent of it, because the
|
|
344
|
+
* listing only saw `Coin<T>` objects and the money was in the address balance.
|
|
345
|
+
*
|
|
346
|
+
* `listing::buy` takes a bare `Balance<Currency>`, which is what makes that resolution
|
|
347
|
+
* cheap: when the address balance covers the price, `tx.balance()` emits a single
|
|
348
|
+
* `balance::redeem_funds` straight off the accumulator and no coin object is minted,
|
|
349
|
+
* touched, or destroyed — leaving the sale free of any owned-object contention. Only
|
|
350
|
+
* when it must fall back to coins does it merge, split, and `coin::into_balance`.
|
|
351
|
+
*/
|
|
352
|
+
export function buyRecord(p: BuyRecordParams): TxThunk {
|
|
353
|
+
return (tx) => {
|
|
354
|
+
const pkg = p.misoPressingPackageId;
|
|
355
|
+
const { pressingId, listingId } = deriveSaleIds(p.releaseId, p.currencyType, pkg);
|
|
356
|
+
const payment = tx.balance({
|
|
357
|
+
type: p.currencyType,
|
|
358
|
+
balance: BigInt(p.amount),
|
|
359
|
+
useGasCoin: p.useGasCoin ?? false,
|
|
360
|
+
});
|
|
361
|
+
const record = tx.add(
|
|
362
|
+
listing.buy({
|
|
363
|
+
package: pkg,
|
|
364
|
+
arguments: [listingId, pressingId, payment, p.settingsId],
|
|
365
|
+
typeArguments: [p.currencyType],
|
|
366
|
+
}),
|
|
367
|
+
);
|
|
368
|
+
tx.transferObjects([record], p.recipient);
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ============================================================================
|
|
373
|
+
// Reads
|
|
374
|
+
// ============================================================================
|
|
375
|
+
|
|
376
|
+
/** A parsed `Pressing` — the run itself. */
|
|
377
|
+
export interface PressingView {
|
|
378
|
+
id: string;
|
|
379
|
+
releaseId: string;
|
|
380
|
+
state: PressingRunState & { startTimestampMs?: bigint };
|
|
381
|
+
/** Records pressed so far; also the most recent number. Not a cap — nothing counts
|
|
382
|
+
* against it, and there is no sold-out state to reach. */
|
|
383
|
+
supply: bigint;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** A parsed `Listing<Currency>` — one currency's offer on the run. */
|
|
387
|
+
export interface ListingView {
|
|
388
|
+
id: string;
|
|
389
|
+
releaseId: string;
|
|
390
|
+
pressingId: string;
|
|
391
|
+
price: { kind: "fixed" | "floor"; amount: bigint };
|
|
392
|
+
state: ListingSwitch;
|
|
393
|
+
/** The `Currency` type, parsed from the object's type tag (`null` if unavailable). */
|
|
394
|
+
currencyType: string | null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Extracts `CURRENCY` from a `…::listing::Listing<CURRENCY>` type tag. */
|
|
398
|
+
function currencyFromType(type: string | null | undefined): string | null {
|
|
399
|
+
if (!type) return null;
|
|
400
|
+
const lt = type.indexOf("<");
|
|
401
|
+
return lt >= 0 && type.endsWith(">") ? type.slice(lt + 1, -1) : null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Reads one object's BCS content + type, or `null` when it does not exist. */
|
|
405
|
+
async function fetch(
|
|
406
|
+
client: ClientWithCoreApi,
|
|
407
|
+
objectId: string,
|
|
408
|
+
): Promise<{ content: Uint8Array; type?: string } | null> {
|
|
409
|
+
try {
|
|
410
|
+
const { object } = await client.core.getObject({ objectId, include: { content: true } });
|
|
411
|
+
return object?.content ? { content: object.content, type: object.type } : null;
|
|
412
|
+
} catch (e) {
|
|
413
|
+
if (isNotFound(e)) return null;
|
|
414
|
+
throw e;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// The generated `MoveEnum` parsers emit `{ $kind, <Variant>: … }`; older ones omitted
|
|
419
|
+
// `$kind`. Both shapes are accepted so a codegen bump cannot silently mis-read a price.
|
|
420
|
+
function variant<T extends string>(v: { $kind?: string } & Record<string, unknown>, ...names: T[]): T {
|
|
421
|
+
for (const name of names) if (v.$kind === name || v[name] != null) return name;
|
|
422
|
+
return names[0]!;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Fetches and parses a `Pressing`, or `null` if the release has never opened one.
|
|
427
|
+
*
|
|
428
|
+
* NOTE: like the old drop reader, this deliberately deviates from the core-getter
|
|
429
|
+
* convention in `./queries.ts` (throw on missing object). A pressing's address is
|
|
430
|
+
* computable for ANY release, so probing one that was never opened is a normal state,
|
|
431
|
+
* not a broken reference — absence is `null` and only transport failures throw.
|
|
432
|
+
*/
|
|
433
|
+
export async function getPressing(client: ClientWithCoreApi, pressingId: string): Promise<PressingView | null> {
|
|
434
|
+
const got = await fetch(client, pressingId);
|
|
435
|
+
if (!got) return null;
|
|
436
|
+
const parsed = pressing.Pressing.parse(got.content);
|
|
437
|
+
const s = parsed.state as { $kind?: string; Scheduled?: { start_timestamp_ms: string } };
|
|
438
|
+
const kind = variant(s, "Scheduled", "Active", "Paused");
|
|
439
|
+
return {
|
|
440
|
+
id: pressingId,
|
|
441
|
+
releaseId: parsed.release_id,
|
|
442
|
+
state:
|
|
443
|
+
kind === "Scheduled"
|
|
444
|
+
? { kind: "scheduled", startTimestampMs: BigInt(s.Scheduled?.start_timestamp_ms ?? "0") }
|
|
445
|
+
: kind === "Paused"
|
|
446
|
+
? { kind: "paused" }
|
|
447
|
+
: { kind: "active" },
|
|
448
|
+
supply: BigInt(parsed.supply),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Fetches and parses a `Listing<Currency>`, or `null` if that currency is not listed. */
|
|
453
|
+
export async function getListing(client: ClientWithCoreApi, listingId: string): Promise<ListingView | null> {
|
|
454
|
+
const got = await fetch(client, listingId);
|
|
455
|
+
if (!got) return null;
|
|
456
|
+
const parsed = listing.Listing.parse(got.content);
|
|
457
|
+
const p = parsed.price as { $kind?: string; Fixed?: { amount: string }; Floor?: { amount: string } };
|
|
458
|
+
const priceKind = variant(p, "Fixed", "Floor");
|
|
459
|
+
const st = parsed.state as { $kind?: string; Enabled?: unknown; Disabled?: unknown };
|
|
460
|
+
return {
|
|
461
|
+
id: listingId,
|
|
462
|
+
releaseId: parsed.release_id,
|
|
463
|
+
pressingId: parsed.pressing_id,
|
|
464
|
+
price: {
|
|
465
|
+
kind: priceKind === "Fixed" ? "fixed" : "floor",
|
|
466
|
+
amount: BigInt((priceKind === "Fixed" ? p.Fixed?.amount : p.Floor?.amount) ?? "0"),
|
|
467
|
+
},
|
|
468
|
+
state: variant(st, "Enabled", "Disabled") === "Enabled" ? "enabled" : "disabled",
|
|
469
|
+
currencyType: currencyFromType(got.type),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export interface GetSaleParams {
|
|
474
|
+
/** The release to resolve a sale for. */
|
|
475
|
+
releaseId: string;
|
|
476
|
+
/** The currency the buyer intends to pay in. */
|
|
477
|
+
currencyType: string;
|
|
478
|
+
misoPressingPackageId: string;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Resolves a release's run AND one currency's offer in a single round trip, by pure
|
|
483
|
+
* address math — no registry, no pointer, no event scan. `pressing: null` means the
|
|
484
|
+
* release has never opened one; `listing: null` means it does not sell in that
|
|
485
|
+
* currency (the other currencies, if any, are unaffected).
|
|
486
|
+
*/
|
|
487
|
+
export async function getSale(
|
|
488
|
+
client: ClientWithCoreApi,
|
|
489
|
+
p: GetSaleParams,
|
|
490
|
+
): Promise<{ pressing: PressingView | null; listing: ListingView | null }> {
|
|
491
|
+
const { pressingId, listingId } = deriveSaleIds(p.releaseId, p.currencyType, p.misoPressingPackageId);
|
|
492
|
+
const [pressingView, listingView] = await Promise.all([
|
|
493
|
+
getPressing(client, pressingId),
|
|
494
|
+
getListing(client, listingId),
|
|
495
|
+
]);
|
|
496
|
+
return { pressing: pressingView, listing: listingView };
|
|
497
|
+
}
|
package/src/queries.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) Miso Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Shared read plumbing for the platform reads.
|
|
5
|
+
|
|
6
|
+
// `isNotFound` is the protocol SDK's — the not-found taxonomy is a property of
|
|
7
|
+
// the Sui transports, not of any one package's objects, so it is defined once in
|
|
8
|
+
// `@misonetwork/sdk` and re-exported here for the platform readers.
|
|
9
|
+
export { isNotFound } from "@misonetwork/sdk";
|