@cypher-zk/sdk 0.4.0 → 0.5.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 +252 -21
- 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 +6 -2
- package/dist/react/index.js +34 -1
- 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
|
|
@@ -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
|
|
|
@@ -173,6 +191,10 @@ const { marketId, marketPda, signature } = await client.actions.createMarket({
|
|
|
173
191
|
question: "Will ETH hit $10k by end of 2026?",
|
|
174
192
|
closeTime: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
|
|
175
193
|
category: 0, // MarketCategory.Crypto
|
|
194
|
+
// v0.2+: optional. Defaults to MIN_CHALLENGE_PERIOD_SECS (24h).
|
|
195
|
+
// Must be in [MIN_CHALLENGE_PERIOD_SECS, MAX_CHALLENGE_PERIOD_SECS]
|
|
196
|
+
// (24h–48h). Pin shorter for prediction markets that settle fast.
|
|
197
|
+
challengePeriod: 24 * 3600,
|
|
176
198
|
onProgress: (e) => console.log(e.stage),
|
|
177
199
|
});
|
|
178
200
|
```
|
|
@@ -210,6 +232,12 @@ Each refuses pre-flight if the market isn't in the right phase
|
|
|
210
232
|
(`marketPhase` returns `"claimable"` for payout, `"refundable"` for
|
|
211
233
|
refund) so the user never burns gas on a guaranteed-to-fail tx.
|
|
212
234
|
|
|
235
|
+
> **v0.2 note:** `resolveMarket` no longer flips the market to
|
|
236
|
+
> `Resolved`. The reveal callback now sets `state = PendingResolution`
|
|
237
|
+
> and starts the challenge window. `claimPayout` opens only after
|
|
238
|
+
> `finalizeResolution` / `adminOverrideResolution` runs — see
|
|
239
|
+
> [§ Dispute window (v0.2)](#dispute-window-v02) below.
|
|
240
|
+
|
|
213
241
|
### 6. Subscribe to events
|
|
214
242
|
|
|
215
243
|
```ts
|
|
@@ -224,6 +252,17 @@ const sub2 = client.events.subscribe("MarketResolvedEvent", (data) => {
|
|
|
224
252
|
console.log("Outcome:", data.outcome, "Payout ratio:", data.payoutRatio);
|
|
225
253
|
});
|
|
226
254
|
|
|
255
|
+
// v0.2: dispute-window events
|
|
256
|
+
const sub3 = client.events.onResolutionFlagged((data) => {
|
|
257
|
+
console.log("Market disputed by:", data.flaggedBy.toBase58());
|
|
258
|
+
});
|
|
259
|
+
const sub4 = client.events.onMarketFinalized((data) => {
|
|
260
|
+
console.log("Market finalized — claims now open. Outcome:", data.outcome);
|
|
261
|
+
});
|
|
262
|
+
const sub5 = client.events.onResolutionOverridden((data) => {
|
|
263
|
+
console.log(`Admin override: ${data.oldOutcome} → ${data.newOutcome}`);
|
|
264
|
+
});
|
|
265
|
+
|
|
227
266
|
// Later
|
|
228
267
|
sub.unsubscribe();
|
|
229
268
|
sub2.unsubscribe();
|
|
@@ -248,8 +287,11 @@ import { marketPhase, projectDeadlines } from "@cypher-zk/sdk";
|
|
|
248
287
|
|
|
249
288
|
// Compute what action is currently available on a market:
|
|
250
289
|
switch (marketPhase(market)) {
|
|
251
|
-
case "betting":
|
|
252
|
-
case "awaitingResolve":
|
|
290
|
+
case "betting": /* show "Bet" button */ break;
|
|
291
|
+
case "awaitingResolve": /* show "Pending resolution" */ break;
|
|
292
|
+
case "pendingResolution": /* v0.2: in challenge window — show countdown + "Flag" */ break;
|
|
293
|
+
case "awaitingFinalize": /* v0.2: window elapsed — show "Finalize" button */ break;
|
|
294
|
+
case "disputed": /* v0.2: flagged — admin override required */ break;
|
|
253
295
|
case "claimable": /* show "Claim payout" */ break;
|
|
254
296
|
case "refundable": /* show "Claim refund" */ break;
|
|
255
297
|
case "expired": /* show "Admin sweep eligible" */ break;
|
|
@@ -263,6 +305,162 @@ console.log("Resolution deadline:", new Date(Number(projected.resolutionDeadline
|
|
|
263
305
|
|
|
264
306
|
---
|
|
265
307
|
|
|
308
|
+
## Dispute window (v0.2)
|
|
309
|
+
|
|
310
|
+
After the reveal callback lands, the market enters a configurable
|
|
311
|
+
**24h–48h** challenge window (`market.state === PendingResolution = 4`)
|
|
312
|
+
during which anyone can flag a wrong outcome. Only after the window
|
|
313
|
+
closes does the market move to `Resolved` and claims open.
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
Resolver calls resolveMarket
|
|
317
|
+
│
|
|
318
|
+
▼ reveal_market_outcome_* (Arcium MPC)
|
|
319
|
+
callback writes: outcome, revealedPool*, payoutRatio
|
|
320
|
+
state = PendingResolution
|
|
321
|
+
challengeDeadline = now + challenge_period
|
|
322
|
+
|
|
323
|
+
┌─────────────────────────────────────┐
|
|
324
|
+
│ Challenge window (24h–48h) │
|
|
325
|
+
│ Anyone may flagResolution │
|
|
326
|
+
│ → market.disputed = true │
|
|
327
|
+
└─────────────────────────────────────┘
|
|
328
|
+
│ │
|
|
329
|
+
(undisputed) (disputed)
|
|
330
|
+
│ │
|
|
331
|
+
▼ ▼
|
|
332
|
+
finalizeResolution adminOverrideResolution
|
|
333
|
+
(anyone, post-window) (admin only, recomputes payout_ratio)
|
|
334
|
+
│ │
|
|
335
|
+
└──────────┬───────────────┘
|
|
336
|
+
▼
|
|
337
|
+
state = Resolved
|
|
338
|
+
claim_deadline + refund_deadline set
|
|
339
|
+
claimPayout opens
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Three new actions
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
// 1. ANYONE during the challenge window — flag a wrong resolution
|
|
346
|
+
await client.actions.flagResolution({
|
|
347
|
+
flagger: wallet.publicKey,
|
|
348
|
+
marketId,
|
|
349
|
+
onProgress: (e) => console.log(e.stage),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// 2. ANYONE after the window closes undisputed — finalize → state = Resolved
|
|
353
|
+
await client.actions.finalizeResolution({
|
|
354
|
+
caller: wallet.publicKey,
|
|
355
|
+
marketId,
|
|
356
|
+
onProgress: (e) => console.log(e.stage),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// 3. ADMIN ONLY when market.disputed === true — re-resolve with corrected outcome
|
|
360
|
+
// Recomputes payout_ratio from the already-revealed plaintext pools.
|
|
361
|
+
await client.actions.adminOverrideResolution({
|
|
362
|
+
admin: wallet.publicKey,
|
|
363
|
+
marketId,
|
|
364
|
+
outcomeValue: 1,
|
|
365
|
+
onProgress: (e) => console.log(e.stage),
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Each emits `validating → submitting → refetching → done` progress
|
|
370
|
+
stages and refuses pre-flight if the market isn't in the right phase
|
|
371
|
+
(SDK throws clean errors pointing at the next valid step — e.g.
|
|
372
|
+
"market is in 'pendingResolution' — call finalizeResolution first").
|
|
373
|
+
|
|
374
|
+
### Phase gating
|
|
375
|
+
|
|
376
|
+
`marketPhase(market)` returns the three new values whenever
|
|
377
|
+
`state === PendingResolution`:
|
|
378
|
+
|
|
379
|
+
| `marketPhase` | Meaning | Clickable |
|
|
380
|
+
| --- | --- | --- |
|
|
381
|
+
| `"pendingResolution"` | inside challenge window, not flagged | `flagResolution` (any user) |
|
|
382
|
+
| `"awaitingFinalize"` | window elapsed, not flagged | `finalizeResolution` (any user) |
|
|
383
|
+
| `"disputed"` | flagged during window | `adminOverrideResolution` (admin only) |
|
|
384
|
+
| `"claimable"` | finalized → window closed (state=Resolved) | `claimPayout` |
|
|
385
|
+
|
|
386
|
+
`claimPayoutAction` and `useClaimPayout` reject pre-flight in the
|
|
387
|
+
first three phases with a hint to call `finalizeResolution` first.
|
|
388
|
+
|
|
389
|
+
### React example
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import {
|
|
393
|
+
useMarket,
|
|
394
|
+
useFlagResolution,
|
|
395
|
+
useFinalizeResolution,
|
|
396
|
+
} from "@cypher-zk/sdk/react";
|
|
397
|
+
import { marketPhase } from "@cypher-zk/sdk";
|
|
398
|
+
|
|
399
|
+
function ChallengeWindowControls({ marketId }: { marketId: bigint }) {
|
|
400
|
+
const { data: market } = useMarket(marketId, { refetchInterval: 5_000 });
|
|
401
|
+
const flag = useFlagResolution();
|
|
402
|
+
const finalize = useFinalizeResolution();
|
|
403
|
+
if (!market) return null;
|
|
404
|
+
|
|
405
|
+
const phase = marketPhase(market);
|
|
406
|
+
if (phase === "pendingResolution") {
|
|
407
|
+
return (
|
|
408
|
+
<>
|
|
409
|
+
<p>Challenge closes at {new Date(Number(market.challengeDeadline) * 1000).toLocaleString()}</p>
|
|
410
|
+
<button onClick={() => flag.mutate({ flagger: wallet.publicKey!, marketId })}>
|
|
411
|
+
Flag this resolution
|
|
412
|
+
</button>
|
|
413
|
+
</>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (phase === "awaitingFinalize") {
|
|
417
|
+
return (
|
|
418
|
+
<button onClick={() => finalize.mutate({ caller: wallet.publicKey!, marketId })}>
|
|
419
|
+
Finalize resolution
|
|
420
|
+
</button>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (phase === "disputed") {
|
|
424
|
+
return <p>Awaiting admin override.</p>;
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Defaults & bounds
|
|
431
|
+
|
|
432
|
+
| Constant | Value | Notes |
|
|
433
|
+
| --- | --- | --- |
|
|
434
|
+
| `MIN_CHALLENGE_PERIOD_SECS` | `24 * 3600` (24h) | Action helpers default here |
|
|
435
|
+
| `MAX_CHALLENGE_PERIOD_SECS` | `48 * 3600` (48h) | Hard ceiling enforced by contract |
|
|
436
|
+
|
|
437
|
+
The high-level `client.actions.createMarket` makes `challengePeriod`
|
|
438
|
+
optional and defaults to `MIN_CHALLENGE_PERIOD_SECS`. The raw
|
|
439
|
+
`createMarketIx` builder requires it — out-of-range values throw
|
|
440
|
+
client-side before the tx is built.
|
|
441
|
+
|
|
442
|
+
### Six new error codes
|
|
443
|
+
|
|
444
|
+
| Code | Name | When |
|
|
445
|
+
| --- | --- | --- |
|
|
446
|
+
| `6036` | `InvalidChallengePeriod` | `challengePeriod` outside 24h–48h |
|
|
447
|
+
| `6037` | `NotPendingResolution` | flag/finalize/override on wrong state |
|
|
448
|
+
| `6038` | `ChallengePeriodNotElapsed` | finalize called too early |
|
|
449
|
+
| `6039` | `ChallengePeriodElapsed` | flag called after window closed |
|
|
450
|
+
| `6040` | `MarketDisputed` | finalize on a flagged market |
|
|
451
|
+
| `6041` | `MarketNotDisputed` | admin override on a clean market |
|
|
452
|
+
|
|
453
|
+
Use `parseCypherError(err)` to extract a typed `CypherErrorCode` for
|
|
454
|
+
branching.
|
|
455
|
+
|
|
456
|
+
### Three new events
|
|
457
|
+
|
|
458
|
+
`ResolutionFlaggedEvent`, `MarketFinalizedEvent`,
|
|
459
|
+
`ResolutionOverriddenEvent` are added to the discriminated `CypherEvent`
|
|
460
|
+
union and have matching `client.events.on*` helpers.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
266
464
|
## React hooks
|
|
267
465
|
|
|
268
466
|
```tsx
|
|
@@ -340,6 +538,9 @@ function MarketView({ marketId }: { marketId: bigint }) {
|
|
|
340
538
|
| `useClaimPayout()` | Mutation | Claim winning payout |
|
|
341
539
|
| `useClaimRefund()` | Mutation | Claim refund on unresolved market |
|
|
342
540
|
| `useCancelMarket()` | Mutation | Cancel a zero-bet market |
|
|
541
|
+
| `useFlagResolution()` *(v0.2)* | Mutation | Flag a pending resolution during the challenge window |
|
|
542
|
+
| `useFinalizeResolution()` *(v0.2)* | Mutation | Finalize a pending resolution after the window elapses undisputed |
|
|
543
|
+
| `useAdminOverrideResolution()` *(v0.2)* | Mutation | Admin re-resolves a disputed market |
|
|
343
544
|
| `useMarketEvents()` | Subscription | Live event stream (component-scoped) |
|
|
344
545
|
|
|
345
546
|
Mutation hooks auto-invalidate the relevant query keys on success. Read
|
|
@@ -367,7 +568,7 @@ cypher-sdk/
|
|
|
367
568
|
│ ├── client.ts # CypherClient — single import surface
|
|
368
569
|
│ ├── accounts/ # fetch + memcmp filter helpers per account
|
|
369
570
|
│ ├── arcium/ # MPC glue (cipher, queue accounts, offsets)
|
|
370
|
-
│ ├── instructions/ # raw TransactionInstruction builders (
|
|
571
|
+
│ ├── instructions/ # raw TransactionInstruction builders (29 ix, v0.2)
|
|
371
572
|
│ ├── actions/ # high-level flows + progress events
|
|
372
573
|
│ ├── events/ # typed event parser + WS/poll subscriptions
|
|
373
574
|
│ ├── idl/ # synced Anchor IDL (source of truth)
|
|
@@ -378,7 +579,7 @@ cypher-sdk/
|
|
|
378
579
|
│ ├── architecture.md # three-surface model + module map
|
|
379
580
|
│ └── flows.md # one diagram per user flow
|
|
380
581
|
└── tests/
|
|
381
|
-
├── unit/ #
|
|
582
|
+
├── unit/ # 150 tests, 712 assertions — pure, no chain
|
|
382
583
|
├── integration/ # INTEGRATION=1 — Arcium localnet lifecycle
|
|
383
584
|
└── devnet/ # DEVNET=1 — devnet read-only + opt-in writes
|
|
384
585
|
```
|
|
@@ -460,6 +661,36 @@ The SDK adds defense-in-depth client-side:
|
|
|
460
661
|
`Uint8Array` ciphertext asserts `length === 32` before assembling the
|
|
461
662
|
ix, catching the "combined ciphertext arrays" mistake from the Arcium
|
|
462
663
|
skill before it hits the program.
|
|
664
|
+
- **v0.2 dispute-window phase gating** — `claimPayout` rejects pre-flight
|
|
665
|
+
if `market.state === PendingResolution` with a typed error pointing
|
|
666
|
+
the caller at `finalizeResolution`, so users don't pay MPC compute
|
|
667
|
+
fees on a guaranteed-to-fail tx. `flagResolution` /
|
|
668
|
+
`finalizeResolution` / `adminOverrideResolution` validate against
|
|
669
|
+
`market.disputed` + `challengeDeadline` client-side.
|
|
670
|
+
|
|
671
|
+
## Changelog
|
|
672
|
+
|
|
673
|
+
### 0.2.0 — dispute window
|
|
674
|
+
|
|
675
|
+
- **NEW**: 3 instructions (`flagResolution`, `finalizeResolution`,
|
|
676
|
+
`adminOverrideResolution`) + 3 actions + 3 React hooks + 3 events
|
|
677
|
+
+ 6 error codes (6036–6041).
|
|
678
|
+
- **NEW**: `MarketState.PendingResolution = 4` and three new
|
|
679
|
+
`marketPhase` values: `pendingResolution`, `awaitingFinalize`,
|
|
680
|
+
`disputed`.
|
|
681
|
+
- **NEW**: `Market` gains `challengePeriod`, `challengeDeadline`,
|
|
682
|
+
`disputed` fields; layout offsets shifted.
|
|
683
|
+
- **NEW**: `MIN_CHALLENGE_PERIOD_SECS` / `MAX_CHALLENGE_PERIOD_SECS`
|
|
684
|
+
constants (24h / 48h).
|
|
685
|
+
- **BREAKING**: `createMarketIx` / `createMarketMultiIx` raw builders
|
|
686
|
+
require a new `challengePeriod` arg. The high-level
|
|
687
|
+
`client.actions.createMarket` makes it optional (defaults to 24h).
|
|
688
|
+
- **BREAKING**: `resolveMarket` now leaves the market in
|
|
689
|
+
`PendingResolution`; `claimPayout` opens only after
|
|
690
|
+
`finalizeResolution` or `adminOverrideResolution` runs.
|
|
691
|
+
- **DX**: `claimPayoutAction` pre-flight error now explicitly names
|
|
692
|
+
`finalizeResolution` as the next step when state is
|
|
693
|
+
`PendingResolution`.
|
|
463
694
|
|
|
464
695
|
---
|
|
465
696
|
|