@cypher-zk/sdk 0.4.0 → 0.6.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 +336 -72
- package/dist/{client-hOLBWshA.d.ts → client-B0EueahJ.d.ts} +587 -21
- package/dist/{cypher-WAYH63ZA.json → cypher-M5PH6UM5.json} +440 -30
- package/dist/index.d.ts +23 -4
- package/dist/index.js +191 -12
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +10 -3
- package/dist/react/index.js +36 -2
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/src/idl/cypher.json +440 -30
- package/src/idl/cypher.ts +440 -30
package/README.md
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
> **TypeScript SDK for the [Cypher](https://cyphers.live) privacy-preserving prediction market on Solana**, powered by [Arcium](https://arcium.com) MPC.
|
|
4
4
|
|
|
5
|
-
[]()
|
|
6
6
|
[]()
|
|
7
7
|
[]()
|
|
8
|
+
[]()
|
|
8
9
|
[](./LICENSE)
|
|
9
10
|
|
|
10
11
|
A framework-agnostic core (Node, Bun, browser) with an optional React
|
|
11
12
|
hooks subpath and end-to-end progress callbacks so frontends can render
|
|
12
13
|
fine-grained loading state across every multi-step on-chain flow.
|
|
13
14
|
|
|
15
|
+
> **v0.2 — dispute window.** Reveal callbacks now land the market in
|
|
16
|
+
> `PendingResolution`, not `Resolved`. A configurable 24h–48h challenge
|
|
17
|
+
> window lets anyone flag a wrong outcome before claims open. See
|
|
18
|
+
> [§ Dispute window (v0.2)](#dispute-window-v02) for the full flow and
|
|
19
|
+
> three new instructions / actions / hooks.
|
|
20
|
+
|
|
14
21
|
---
|
|
15
22
|
|
|
16
23
|
## Why this SDK exists
|
|
@@ -35,8 +42,8 @@ const result = await client.actions.placeBet({
|
|
|
35
42
|
payer: wallet.publicKey,
|
|
36
43
|
user: wallet.publicKey,
|
|
37
44
|
marketId: 7n,
|
|
38
|
-
side: 1,
|
|
39
|
-
amountUsdc: 5_000_000n,
|
|
45
|
+
side: 1, // 1 = YES
|
|
46
|
+
amountUsdc: 5_000_000n, // $5 (6 decimals)
|
|
40
47
|
onProgress: (e) => updateLoaderUI(e.stage, e.message),
|
|
41
48
|
});
|
|
42
49
|
// Persist result.userKeypair.privateKey under the wallet's key — that's
|
|
@@ -45,34 +52,45 @@ const result = await client.actions.placeBet({
|
|
|
45
52
|
|
|
46
53
|
## What's in the box
|
|
47
54
|
|
|
48
|
-
- **All
|
|
49
|
-
`TransactionInstruction`s (compose, simulate, bundle freely)
|
|
50
|
-
|
|
55
|
+
- **All 29 program instructions** with typed builders that return raw
|
|
56
|
+
`TransactionInstruction`s (compose, simulate, bundle freely) — 3 admin,
|
|
57
|
+
8 init comp def, 4 market lifecycle, 2 bet, 2 resolve, 4 claim, plus
|
|
58
|
+
**3 dispute-window** instructions added in v0.2.
|
|
59
|
+
- **Ten high-level action helpers** (`createMarket`,
|
|
51
60
|
`createMarketMulti`, `placeBet`, `resolveMarket`, `claimPayout`,
|
|
52
|
-
`claimRefund`, `cancelMarket`, `withdrawCreatorFunds
|
|
53
|
-
|
|
61
|
+
`claimRefund`, `cancelMarket`, `withdrawCreatorFunds`, plus
|
|
62
|
+
**`flagResolution`, `finalizeResolution`, `adminOverrideResolution`**)
|
|
63
|
+
that hide the "encrypt → send → await MPC callback → refetch"
|
|
64
|
+
choreography.
|
|
54
65
|
- **Async progress events** on every multi-step action, so frontends can
|
|
55
66
|
render `Encrypting…` → `Submitting…` → `Awaiting MPC nodes…` instead
|
|
56
67
|
of one generic spinner.
|
|
57
|
-
- **Typed event surface** —
|
|
58
|
-
`parseLogs`, `parseLogsFor`,
|
|
59
|
-
|
|
60
|
-
`bigint`s, matching the typed
|
|
68
|
+
- **Typed event surface** — **10 discriminated-union events** (7 core +
|
|
69
|
+
3 dispute-window in v0.2) with `parseLogs`, `parseLogsFor`,
|
|
70
|
+
`subscribeAll`, `onXxx` helpers, and a WebSocket-less `pollEvents`
|
|
71
|
+
fallback. Decoded fields are camelCase `bigint`s, matching the typed
|
|
72
|
+
interfaces 1:1.
|
|
61
73
|
- **Account fetch + memcmp filters** for every program account, with
|
|
62
74
|
byte offsets drift-tested against the IDL.
|
|
63
75
|
- **React hooks** (`@cypher-zk/sdk/react`): `CypherProvider`,
|
|
64
76
|
`useGlobalState`, `useMarket`, `useMarkets`, `useUserPositions`,
|
|
65
77
|
`usePlaceBet`, `useResolveMarket`, `useClaimPayout`, `useClaimRefund`,
|
|
66
|
-
`useCreateMarket`, `useCancelMarket`,
|
|
67
|
-
|
|
78
|
+
`useCreateMarket`, `useCancelMarket`, **`useFlagResolution`,
|
|
79
|
+
`useFinalizeResolution`, `useAdminOverrideResolution`** (v0.2),
|
|
80
|
+
`useMarketEvents` — all built on TanStack Query with sensible
|
|
81
|
+
cache-invalidation defaults.
|
|
82
|
+
- **Phase-aware UI gating** — `marketPhase(market)` returns one of nine
|
|
83
|
+
literal values including `pendingResolution`, `awaitingFinalize`, and
|
|
84
|
+
`disputed` so buttons only render when the corresponding ix is
|
|
85
|
+
actually clickable.
|
|
68
86
|
- **Cluster-agnostic at runtime** — reads `GlobalState.accepted_mint`
|
|
69
87
|
on-chain, so the same build works against any deployment (devnet CSDC,
|
|
70
88
|
mainnet USDC, localnet test mint).
|
|
71
|
-
- **
|
|
72
|
-
phases, IDL drift, Arcium offsets, encryption
|
|
73
|
-
parser round-trip with all
|
|
74
|
-
|
|
75
|
-
smoke suites.
|
|
89
|
+
- **150 unit tests** (712 assertions) covering PDA derivations, fee
|
|
90
|
+
math, deadline phases, IDL drift, Arcium offsets, encryption
|
|
91
|
+
round-trip, event parser round-trip with all 10 event types, action
|
|
92
|
+
input validation (including dispute-window phase gating), and React
|
|
93
|
+
hook wiring. Plus opt-in localnet integration and devnet smoke suites.
|
|
76
94
|
|
|
77
95
|
## Install
|
|
78
96
|
|
|
@@ -84,9 +102,9 @@ npm install @cypher-zk/sdk
|
|
|
84
102
|
|
|
85
103
|
### Peer dependencies
|
|
86
104
|
|
|
87
|
-
| Package
|
|
88
|
-
|
|
|
89
|
-
| `react` ^18 \|\| ^19
|
|
105
|
+
| Package | Required for |
|
|
106
|
+
| -------------------------- | ----------------------------------- |
|
|
107
|
+
| `react` ^18 \|\| ^19 | `@cypher-zk/sdk/react` subpath only |
|
|
90
108
|
| `@tanstack/react-query` ^5 | `@cypher-zk/sdk/react` subpath only |
|
|
91
109
|
|
|
92
110
|
Core SDK works in any TypeScript environment with no peer requirements.
|
|
@@ -116,8 +134,8 @@ const gs = await client.globalState.fetch();
|
|
|
116
134
|
console.log("Protocol fee:", gs.protocolFeeRate, "bps");
|
|
117
135
|
|
|
118
136
|
const market = await client.markets.fetch(0n);
|
|
119
|
-
const active = await client.markets.byState(0);
|
|
120
|
-
const mine
|
|
137
|
+
const active = await client.markets.byState(0); // MarketState.Active
|
|
138
|
+
const mine = await client.markets.byCreator(wallet.publicKey);
|
|
121
139
|
const myBets = await client.positions.byUser(wallet.publicKey);
|
|
122
140
|
```
|
|
123
141
|
|
|
@@ -131,15 +149,20 @@ const preview = computeFees(5_000_000n, {
|
|
|
131
149
|
protocolFeeRateBps: gs.protocolFeeRate,
|
|
132
150
|
lpFeeRateBps: gs.lpFeeRate,
|
|
133
151
|
});
|
|
134
|
-
console.log(
|
|
152
|
+
console.log(
|
|
153
|
+
"Net stake:",
|
|
154
|
+
preview.netAmount,
|
|
155
|
+
"after fees:",
|
|
156
|
+
preview.protocolFee + preview.lpFee,
|
|
157
|
+
);
|
|
135
158
|
|
|
136
159
|
// Fire the end-to-end flow:
|
|
137
160
|
const { signature, position, userKeypair } = await client.actions.placeBet({
|
|
138
161
|
payer: wallet.publicKey,
|
|
139
162
|
user: wallet.publicKey,
|
|
140
163
|
marketId: 0n,
|
|
141
|
-
side: 1,
|
|
142
|
-
amountUsdc: 5_000_000n,
|
|
164
|
+
side: 1, // 0 = NO, 1 = YES
|
|
165
|
+
amountUsdc: 5_000_000n, // $5 (USDC has 6 decimals)
|
|
143
166
|
onProgress: ({ stage, message, signature }) => {
|
|
144
167
|
// stage ∈ "validating" | "fetching-state" | "encrypting" | "submitting"
|
|
145
168
|
// | "awaiting-callback" | "refetching" | "done"
|
|
@@ -172,7 +195,11 @@ const { marketId, marketPda, signature } = await client.actions.createMarket({
|
|
|
172
195
|
creator: wallet.publicKey,
|
|
173
196
|
question: "Will ETH hit $10k by end of 2026?",
|
|
174
197
|
closeTime: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
|
|
175
|
-
category: 0,
|
|
198
|
+
category: 0, // MarketCategory.Crypto
|
|
199
|
+
// v0.2+: optional. Defaults to MIN_CHALLENGE_PERIOD_SECS (24h).
|
|
200
|
+
// Must be in [MIN_CHALLENGE_PERIOD_SECS, MAX_CHALLENGE_PERIOD_SECS]
|
|
201
|
+
// (24h–48h). Pin shorter for prediction markets that settle fast.
|
|
202
|
+
challengePeriod: 24 * 3600,
|
|
176
203
|
onProgress: (e) => console.log(e.stage),
|
|
177
204
|
});
|
|
178
205
|
```
|
|
@@ -210,13 +237,21 @@ Each refuses pre-flight if the market isn't in the right phase
|
|
|
210
237
|
(`marketPhase` returns `"claimable"` for payout, `"refundable"` for
|
|
211
238
|
refund) so the user never burns gas on a guaranteed-to-fail tx.
|
|
212
239
|
|
|
240
|
+
> **v0.2 note:** `resolveMarket` no longer flips the market to
|
|
241
|
+
> `Resolved`. The reveal callback now sets `state = PendingResolution`
|
|
242
|
+
> and starts the challenge window. `claimPayout` opens only after
|
|
243
|
+
> `finalizeResolution` / `adminOverrideResolution` runs — see
|
|
244
|
+
> [§ Dispute window (v0.2)](#dispute-window-v02) below.
|
|
245
|
+
|
|
213
246
|
### 6. Subscribe to events
|
|
214
247
|
|
|
215
248
|
```ts
|
|
216
249
|
// Real-time (WebSocket):
|
|
217
250
|
const sub = client.events.onBetPlaced((data) => {
|
|
218
251
|
// `data` is BetPlacedEvent — fully typed (camelCase fields, bigint amounts):
|
|
219
|
-
console.log(
|
|
252
|
+
console.log(
|
|
253
|
+
`Bet placed on market ${data.market.toBase58()} — odds ${data.entryOdds}`,
|
|
254
|
+
);
|
|
220
255
|
});
|
|
221
256
|
|
|
222
257
|
// Generic, typed by name:
|
|
@@ -224,6 +259,17 @@ const sub2 = client.events.subscribe("MarketResolvedEvent", (data) => {
|
|
|
224
259
|
console.log("Outcome:", data.outcome, "Payout ratio:", data.payoutRatio);
|
|
225
260
|
});
|
|
226
261
|
|
|
262
|
+
// v0.2: dispute-window events
|
|
263
|
+
const sub3 = client.events.onResolutionFlagged((data) => {
|
|
264
|
+
console.log("Market disputed by:", data.flaggedBy.toBase58());
|
|
265
|
+
});
|
|
266
|
+
const sub4 = client.events.onMarketFinalized((data) => {
|
|
267
|
+
console.log("Market finalized — claims now open. Outcome:", data.outcome);
|
|
268
|
+
});
|
|
269
|
+
const sub5 = client.events.onResolutionOverridden((data) => {
|
|
270
|
+
console.log(`Admin override: ${data.oldOutcome} → ${data.newOutcome}`);
|
|
271
|
+
});
|
|
272
|
+
|
|
227
273
|
// Later
|
|
228
274
|
sub.unsubscribe();
|
|
229
275
|
sub2.unsubscribe();
|
|
@@ -236,9 +282,14 @@ for (const { event, signature, slot } of recent) {
|
|
|
236
282
|
|
|
237
283
|
// Parse events out of a known transaction:
|
|
238
284
|
import { parseLogs, parseLogsFor } from "@cypher-zk/sdk";
|
|
239
|
-
const tx = await connection.getTransaction(sig, {
|
|
240
|
-
|
|
241
|
-
|
|
285
|
+
const tx = await connection.getTransaction(sig, {
|
|
286
|
+
maxSupportedTransactionVersion: 0,
|
|
287
|
+
});
|
|
288
|
+
const allEvents = parseLogs(tx?.meta?.logMessages ?? []);
|
|
289
|
+
const payoutsOnly = parseLogsFor(
|
|
290
|
+
tx?.meta?.logMessages ?? [],
|
|
291
|
+
"PayoutClaimedEvent",
|
|
292
|
+
);
|
|
242
293
|
```
|
|
243
294
|
|
|
244
295
|
### 7. Phase helpers
|
|
@@ -248,18 +299,196 @@ import { marketPhase, projectDeadlines } from "@cypher-zk/sdk";
|
|
|
248
299
|
|
|
249
300
|
// Compute what action is currently available on a market:
|
|
250
301
|
switch (marketPhase(market)) {
|
|
251
|
-
case "betting":
|
|
252
|
-
|
|
253
|
-
case "
|
|
254
|
-
|
|
255
|
-
case "
|
|
256
|
-
|
|
302
|
+
case "betting":
|
|
303
|
+
/* show "Bet" button */ break;
|
|
304
|
+
case "awaitingResolve":
|
|
305
|
+
/* show "Pending resolution" */ break;
|
|
306
|
+
case "pendingResolution":
|
|
307
|
+
/* v0.2: in challenge window — show countdown + "Flag" */ break;
|
|
308
|
+
case "awaitingFinalize":
|
|
309
|
+
/* v0.2: window elapsed — show "Finalize" button */ break;
|
|
310
|
+
case "disputed":
|
|
311
|
+
/* v0.2: flagged — admin override required */ break;
|
|
312
|
+
case "claimable":
|
|
313
|
+
/* show "Claim payout" */ break;
|
|
314
|
+
case "refundable":
|
|
315
|
+
/* show "Claim refund" */ break;
|
|
316
|
+
case "expired":
|
|
317
|
+
/* show "Admin sweep eligible" */ break;
|
|
318
|
+
case "cancelled":
|
|
319
|
+
/* show "Cancelled" */ break;
|
|
257
320
|
}
|
|
258
321
|
|
|
259
322
|
// Preview deadlines for a draft market the user is filling in:
|
|
260
323
|
const projected = projectDeadlines(BigInt(closeTimeSec));
|
|
261
|
-
console.log(
|
|
324
|
+
console.log(
|
|
325
|
+
"Resolution deadline:",
|
|
326
|
+
new Date(Number(projected.resolutionDeadline) * 1000),
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Dispute window (v0.2)
|
|
333
|
+
|
|
334
|
+
After the reveal callback lands, the market enters a configurable
|
|
335
|
+
**24h–48h** challenge window (`market.state === PendingResolution = 4`)
|
|
336
|
+
during which anyone can flag a wrong outcome. Only after the window
|
|
337
|
+
closes does the market move to `Resolved` and claims open.
|
|
338
|
+
|
|
262
339
|
```
|
|
340
|
+
Resolver calls resolveMarket
|
|
341
|
+
│
|
|
342
|
+
▼ reveal_market_outcome_* (Arcium MPC)
|
|
343
|
+
callback writes: outcome, revealedPool*, payoutRatio
|
|
344
|
+
state = PendingResolution
|
|
345
|
+
challengeDeadline = now + challenge_period
|
|
346
|
+
|
|
347
|
+
┌─────────────────────────────────────┐
|
|
348
|
+
│ Challenge window (24h–48h) │
|
|
349
|
+
│ Anyone may flagResolution │
|
|
350
|
+
│ → market.disputed = true │
|
|
351
|
+
└─────────────────────────────────────┘
|
|
352
|
+
│ │
|
|
353
|
+
(undisputed) (disputed)
|
|
354
|
+
│ │
|
|
355
|
+
▼ ▼
|
|
356
|
+
finalizeResolution adminOverrideResolution
|
|
357
|
+
(anyone, post-window) (admin only, recomputes payout_ratio)
|
|
358
|
+
│ │
|
|
359
|
+
└──────────┬───────────────┘
|
|
360
|
+
▼
|
|
361
|
+
state = Resolved
|
|
362
|
+
claim_deadline + refund_deadline set
|
|
363
|
+
claimPayout opens
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Three new actions
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// 1. ANYONE during the challenge window — flag a wrong resolution
|
|
370
|
+
await client.actions.flagResolution({
|
|
371
|
+
flagger: wallet.publicKey,
|
|
372
|
+
marketId,
|
|
373
|
+
onProgress: (e) => console.log(e.stage),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 2. ANYONE after the window closes undisputed — finalize → state = Resolved
|
|
377
|
+
await client.actions.finalizeResolution({
|
|
378
|
+
caller: wallet.publicKey,
|
|
379
|
+
marketId,
|
|
380
|
+
onProgress: (e) => console.log(e.stage),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// 3. ADMIN ONLY when market.disputed === true — re-resolve with corrected outcome
|
|
384
|
+
// Recomputes payout_ratio from the already-revealed plaintext pools.
|
|
385
|
+
await client.actions.adminOverrideResolution({
|
|
386
|
+
admin: wallet.publicKey,
|
|
387
|
+
marketId,
|
|
388
|
+
outcomeValue: 1,
|
|
389
|
+
onProgress: (e) => console.log(e.stage),
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Each emits `validating → submitting → refetching → done` progress
|
|
394
|
+
stages and refuses pre-flight if the market isn't in the right phase
|
|
395
|
+
(SDK throws clean errors pointing at the next valid step — e.g.
|
|
396
|
+
"market is in 'pendingResolution' — call finalizeResolution first").
|
|
397
|
+
|
|
398
|
+
### Phase gating
|
|
399
|
+
|
|
400
|
+
`marketPhase(market)` returns the three new values whenever
|
|
401
|
+
`state === PendingResolution`:
|
|
402
|
+
|
|
403
|
+
| `marketPhase` | Meaning | Clickable |
|
|
404
|
+
| --------------------- | ------------------------------------------ | -------------------------------------- |
|
|
405
|
+
| `"pendingResolution"` | inside challenge window, not flagged | `flagResolution` (any user) |
|
|
406
|
+
| `"awaitingFinalize"` | window elapsed, not flagged | `finalizeResolution` (any user) |
|
|
407
|
+
| `"disputed"` | flagged during window | `adminOverrideResolution` (admin only) |
|
|
408
|
+
| `"claimable"` | finalized → window closed (state=Resolved) | `claimPayout` |
|
|
409
|
+
|
|
410
|
+
`claimPayoutAction` and `useClaimPayout` reject pre-flight in the
|
|
411
|
+
first three phases with a hint to call `finalizeResolution` first.
|
|
412
|
+
|
|
413
|
+
### React example
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
import {
|
|
417
|
+
useMarket,
|
|
418
|
+
useFlagResolution,
|
|
419
|
+
useFinalizeResolution,
|
|
420
|
+
} from "@cypher-zk/sdk/react";
|
|
421
|
+
import { marketPhase } from "@cypher-zk/sdk";
|
|
422
|
+
|
|
423
|
+
function ChallengeWindowControls({ marketId }: { marketId: bigint }) {
|
|
424
|
+
const { data: market } = useMarket(marketId, { refetchInterval: 5_000 });
|
|
425
|
+
const flag = useFlagResolution();
|
|
426
|
+
const finalize = useFinalizeResolution();
|
|
427
|
+
if (!market) return null;
|
|
428
|
+
|
|
429
|
+
const phase = marketPhase(market);
|
|
430
|
+
if (phase === "pendingResolution") {
|
|
431
|
+
return (
|
|
432
|
+
<>
|
|
433
|
+
<p>
|
|
434
|
+
Challenge closes at{" "}
|
|
435
|
+
{new Date(Number(market.challengeDeadline) * 1000).toLocaleString()}
|
|
436
|
+
</p>
|
|
437
|
+
<button
|
|
438
|
+
onClick={() => flag.mutate({ flagger: wallet.publicKey!, marketId })}
|
|
439
|
+
>
|
|
440
|
+
Flag this resolution
|
|
441
|
+
</button>
|
|
442
|
+
</>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (phase === "awaitingFinalize") {
|
|
446
|
+
return (
|
|
447
|
+
<button
|
|
448
|
+
onClick={() => finalize.mutate({ caller: wallet.publicKey!, marketId })}
|
|
449
|
+
>
|
|
450
|
+
Finalize resolution
|
|
451
|
+
</button>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (phase === "disputed") {
|
|
455
|
+
return <p>Awaiting admin override.</p>;
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Defaults & bounds
|
|
462
|
+
|
|
463
|
+
| Constant | Value | Notes |
|
|
464
|
+
| --------------------------- | ----------------- | --------------------------------- |
|
|
465
|
+
| `MIN_CHALLENGE_PERIOD_SECS` | `24 * 3600` (24h) | Action helpers default here |
|
|
466
|
+
| `MAX_CHALLENGE_PERIOD_SECS` | `48 * 3600` (48h) | Hard ceiling enforced by contract |
|
|
467
|
+
|
|
468
|
+
The high-level `client.actions.createMarket` makes `challengePeriod`
|
|
469
|
+
optional and defaults to `MIN_CHALLENGE_PERIOD_SECS`. The raw
|
|
470
|
+
`createMarketIx` builder requires it — out-of-range values throw
|
|
471
|
+
client-side before the tx is built.
|
|
472
|
+
|
|
473
|
+
### Six new error codes
|
|
474
|
+
|
|
475
|
+
| Code | Name | When |
|
|
476
|
+
| ------ | --------------------------- | ------------------------------------- |
|
|
477
|
+
| `6036` | `InvalidChallengePeriod` | `challengePeriod` outside 24h–48h |
|
|
478
|
+
| `6037` | `NotPendingResolution` | flag/finalize/override on wrong state |
|
|
479
|
+
| `6038` | `ChallengePeriodNotElapsed` | finalize called too early |
|
|
480
|
+
| `6039` | `ChallengePeriodElapsed` | flag called after window closed |
|
|
481
|
+
| `6040` | `MarketDisputed` | finalize on a flagged market |
|
|
482
|
+
| `6041` | `MarketNotDisputed` | admin override on a clean market |
|
|
483
|
+
|
|
484
|
+
Use `parseCypherError(err)` to extract a typed `CypherErrorCode` for
|
|
485
|
+
branching.
|
|
486
|
+
|
|
487
|
+
### Three new events
|
|
488
|
+
|
|
489
|
+
`ResolutionFlaggedEvent`, `MarketFinalizedEvent`,
|
|
490
|
+
`ResolutionOverriddenEvent` are added to the discriminated `CypherEvent`
|
|
491
|
+
union and have matching `client.events.on*` helpers.
|
|
263
492
|
|
|
264
493
|
---
|
|
265
494
|
|
|
@@ -300,7 +529,7 @@ function MarketView({ marketId }: { marketId: bigint }) {
|
|
|
300
529
|
});
|
|
301
530
|
|
|
302
531
|
if (isLoading) return <p>Loading market…</p>;
|
|
303
|
-
if (!market)
|
|
532
|
+
if (!market) return <p>Market not found.</p>;
|
|
304
533
|
|
|
305
534
|
return (
|
|
306
535
|
<div>
|
|
@@ -320,7 +549,9 @@ function MarketView({ marketId }: { marketId: bigint }) {
|
|
|
320
549
|
>
|
|
321
550
|
{placeBet.isPending && stage ? `Bet → ${stage.stage}` : "Bet $5 YES"}
|
|
322
551
|
</button>
|
|
323
|
-
{placeBet.error &&
|
|
552
|
+
{placeBet.error && (
|
|
553
|
+
<p style={{ color: "crimson" }}>{placeBet.error.message}</p>
|
|
554
|
+
)}
|
|
324
555
|
</div>
|
|
325
556
|
);
|
|
326
557
|
}
|
|
@@ -328,19 +559,22 @@ function MarketView({ marketId }: { marketId: bigint }) {
|
|
|
328
559
|
|
|
329
560
|
### Available hooks
|
|
330
561
|
|
|
331
|
-
| Hook
|
|
332
|
-
|
|
|
333
|
-
| `useGlobalState()`
|
|
334
|
-
| `useMarket(id)`
|
|
335
|
-
| `useMarkets(filter?)`
|
|
336
|
-
| `useUserPositions(user)`
|
|
337
|
-
| `usePlaceBet()`
|
|
338
|
-
| `useCreateMarket()`
|
|
339
|
-
| `useResolveMarket()`
|
|
340
|
-
| `useClaimPayout()`
|
|
341
|
-
| `useClaimRefund()`
|
|
342
|
-
| `useCancelMarket()`
|
|
343
|
-
| `
|
|
562
|
+
| Hook | Kind | Description |
|
|
563
|
+
| --------------------------------------- | ------------ | ----------------------------------------------------------------- |
|
|
564
|
+
| `useGlobalState()` | Query | Protocol config (fees, mint, admin, counter) |
|
|
565
|
+
| `useMarket(id)` | Query | Single market by ID |
|
|
566
|
+
| `useMarkets(filter?)` | Query | All/filtered markets (creator, state) |
|
|
567
|
+
| `useUserPositions(user)` | Query | All bet positions for a user |
|
|
568
|
+
| `usePlaceBet()` | Mutation | End-to-end private bet |
|
|
569
|
+
| `useCreateMarket()` | Mutation | Create a new market |
|
|
570
|
+
| `useResolveMarket()` | Mutation | Submit outcome + await reveal |
|
|
571
|
+
| `useClaimPayout()` | Mutation | Claim winning payout |
|
|
572
|
+
| `useClaimRefund()` | Mutation | Claim refund on unresolved market |
|
|
573
|
+
| `useCancelMarket()` | Mutation | Cancel a zero-bet market |
|
|
574
|
+
| `useFlagResolution()` _(v0.2)_ | Mutation | Flag a pending resolution during the challenge window |
|
|
575
|
+
| `useFinalizeResolution()` _(v0.2)_ | Mutation | Finalize a pending resolution after the window elapses undisputed |
|
|
576
|
+
| `useAdminOverrideResolution()` _(v0.2)_ | Mutation | Admin re-resolves a disputed market |
|
|
577
|
+
| `useMarketEvents()` | Subscription | Live event stream (component-scoped) |
|
|
344
578
|
|
|
345
579
|
Mutation hooks auto-invalidate the relevant query keys on success. Read
|
|
346
580
|
hooks expose their `queryKey` factories (`marketKeys.one(id)`,
|
|
@@ -367,7 +601,7 @@ cypher-sdk/
|
|
|
367
601
|
│ ├── client.ts # CypherClient — single import surface
|
|
368
602
|
│ ├── accounts/ # fetch + memcmp filter helpers per account
|
|
369
603
|
│ ├── arcium/ # MPC glue (cipher, queue accounts, offsets)
|
|
370
|
-
│ ├── instructions/ # raw TransactionInstruction builders (
|
|
604
|
+
│ ├── instructions/ # raw TransactionInstruction builders (29 ix, v0.2)
|
|
371
605
|
│ ├── actions/ # high-level flows + progress events
|
|
372
606
|
│ ├── events/ # typed event parser + WS/poll subscriptions
|
|
373
607
|
│ ├── idl/ # synced Anchor IDL (source of truth)
|
|
@@ -378,7 +612,7 @@ cypher-sdk/
|
|
|
378
612
|
│ ├── architecture.md # three-surface model + module map
|
|
379
613
|
│ └── flows.md # one diagram per user flow
|
|
380
614
|
└── tests/
|
|
381
|
-
├── unit/ #
|
|
615
|
+
├── unit/ # 150 tests, 712 assertions — pure, no chain
|
|
382
616
|
├── integration/ # INTEGRATION=1 — Arcium localnet lifecycle
|
|
383
617
|
└── devnet/ # DEVNET=1 — devnet read-only + opt-in writes
|
|
384
618
|
```
|
|
@@ -396,11 +630,11 @@ The SDK is **cluster-agnostic at runtime**: it reads the accepted SPL mint
|
|
|
396
630
|
from `GlobalState.accepted_mint` on every flow rather than hard-coding
|
|
397
631
|
CSDC vs USDC. The same build works against any Cypher deployment.
|
|
398
632
|
|
|
399
|
-
| Cluster
|
|
400
|
-
|
|
|
401
|
-
| `devnet`
|
|
402
|
-
| `mainnet`
|
|
403
|
-
| `localnet` | `localhost:8899`
|
|
633
|
+
| Cluster | RPC default | Accepted mint | Arcium offset |
|
|
634
|
+
| ---------- | ----------------------------- | ------------------ | --------------- |
|
|
635
|
+
| `devnet` | `api.devnet.solana.com` | CSDC (`8AF9BABN…`) | `456` |
|
|
636
|
+
| `mainnet` | `api.mainnet-beta.solana.com` | USDC (`EPjFWdd5…`) | (set at deploy) |
|
|
637
|
+
| `localnet` | `localhost:8899` | CSDC (test build) | `1116522022` |
|
|
404
638
|
|
|
405
639
|
```ts
|
|
406
640
|
// Explicit preset:
|
|
@@ -423,17 +657,17 @@ const client = new CypherClient({
|
|
|
423
657
|
|
|
424
658
|
## Scripts
|
|
425
659
|
|
|
426
|
-
| Command
|
|
427
|
-
|
|
|
428
|
-
| `bun install`
|
|
429
|
-
| `bun test`
|
|
430
|
-
| `bun run test:unit`
|
|
431
|
-
| `bun run test:integration` | `INTEGRATION=1` — Arcium localnet lifecycle
|
|
432
|
-
| `bun run test:devnet`
|
|
433
|
-
| `bun run typecheck`
|
|
434
|
-
| `bun run build`
|
|
435
|
-
| `bun run sync:idl`
|
|
436
|
-
| `bun run prepublishOnly`
|
|
660
|
+
| Command | Purpose |
|
|
661
|
+
| -------------------------- | --------------------------------------------- |
|
|
662
|
+
| `bun install` | Install deps |
|
|
663
|
+
| `bun test` | All unit suites (gates skipped) |
|
|
664
|
+
| `bun run test:unit` | Unit-only |
|
|
665
|
+
| `bun run test:integration` | `INTEGRATION=1` — Arcium localnet lifecycle |
|
|
666
|
+
| `bun run test:devnet` | `DEVNET=1` — devnet read-only + opt-in writes |
|
|
667
|
+
| `bun run typecheck` | `tsc --noEmit` (strict) |
|
|
668
|
+
| `bun run build` | ESM + `.d.ts` via tsup → `dist/` |
|
|
669
|
+
| `bun run sync:idl` | Re-copy IDL + types from `../cypher-main` |
|
|
670
|
+
| `bun run prepublishOnly` | sync IDL → typecheck → unit tests → build |
|
|
437
671
|
|
|
438
672
|
---
|
|
439
673
|
|
|
@@ -460,6 +694,36 @@ The SDK adds defense-in-depth client-side:
|
|
|
460
694
|
`Uint8Array` ciphertext asserts `length === 32` before assembling the
|
|
461
695
|
ix, catching the "combined ciphertext arrays" mistake from the Arcium
|
|
462
696
|
skill before it hits the program.
|
|
697
|
+
- **v0.2 dispute-window phase gating** — `claimPayout` rejects pre-flight
|
|
698
|
+
if `market.state === PendingResolution` with a typed error pointing
|
|
699
|
+
the caller at `finalizeResolution`, so users don't pay MPC compute
|
|
700
|
+
fees on a guaranteed-to-fail tx. `flagResolution` /
|
|
701
|
+
`finalizeResolution` / `adminOverrideResolution` validate against
|
|
702
|
+
`market.disputed` + `challengeDeadline` client-side.
|
|
703
|
+
|
|
704
|
+
## Changelog
|
|
705
|
+
|
|
706
|
+
### 0.2.0 — dispute window
|
|
707
|
+
|
|
708
|
+
- **NEW**: 3 instructions (`flagResolution`, `finalizeResolution`,
|
|
709
|
+
`adminOverrideResolution`) + 3 actions + 3 React hooks + 3 events
|
|
710
|
+
- 6 error codes (6036–6041).
|
|
711
|
+
- **NEW**: `MarketState.PendingResolution = 4` and three new
|
|
712
|
+
`marketPhase` values: `pendingResolution`, `awaitingFinalize`,
|
|
713
|
+
`disputed`.
|
|
714
|
+
- **NEW**: `Market` gains `challengePeriod`, `challengeDeadline`,
|
|
715
|
+
`disputed` fields; layout offsets shifted.
|
|
716
|
+
- **NEW**: `MIN_CHALLENGE_PERIOD_SECS` / `MAX_CHALLENGE_PERIOD_SECS`
|
|
717
|
+
constants (24h / 48h).
|
|
718
|
+
- **BREAKING**: `createMarketIx` / `createMarketMultiIx` raw builders
|
|
719
|
+
require a new `challengePeriod` arg. The high-level
|
|
720
|
+
`client.actions.createMarket` makes it optional (defaults to 24h).
|
|
721
|
+
- **BREAKING**: `resolveMarket` now leaves the market in
|
|
722
|
+
`PendingResolution`; `claimPayout` opens only after
|
|
723
|
+
`finalizeResolution` or `adminOverrideResolution` runs.
|
|
724
|
+
- **DX**: `claimPayoutAction` pre-flight error now explicitly names
|
|
725
|
+
`finalizeResolution` as the next step when state is
|
|
726
|
+
`PendingResolution`.
|
|
463
727
|
|
|
464
728
|
---
|
|
465
729
|
|