npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cypher-zk/sdk

v0.7.7

Published

TypeScript SDK for the Cypher prediction market (Arcium + Anchor on Solana)

Readme

Cypher SDK

TypeScript SDK for the Cypher privacy-preserving prediction market on Solana, powered by Arcium MPC.

tests typecheck bundle version license

A framework-agnostic core (Node, Bun, browser) with an optional React hooks subpath and end-to-end progress callbacks so frontends can render fine-grained loading state across every multi-step on-chain flow.

v0.2 — dispute window. Reveal callbacks now land the market in PendingResolution, not Resolved. A configurable 24h–48h challenge window lets anyone flag a wrong outcome before claims open. See § Dispute window (v0.2) for the full flow and three new instructions / actions / hooks.


Why this SDK exists

Cypher is a Solana program that wraps prediction-market state in Arcium MPC so user bets stay encrypted on chain. Talking to it directly involves wiring:

  • A typed Anchor Program against the program's IDL
  • Per-flow Arcium queue accounts (cluster offset, mempool, comp def, …)
  • x25519 keypair generation + Rescue cipher encryption for each bet
  • Polling the computation account until the MPC nodes finalize the callback
  • Refetching the position/market after the callback updates state

This SDK gives you a single client.actions.placeBet({...}) call that does all of that, with progress events for the UI and discriminated- union typed events for the indexer.

const result = await client.actions.placeBet({
  payer: wallet.publicKey,
  user: wallet.publicKey,
  marketId: 7n,
  side: 1, // 1 = YES
  amountUsdc: 5_000_000n, // $5 (6 decimals)
  onProgress: (e) => updateLoaderUI(e.stage, e.message),
});
// Persist result.userKeypair.privateKey under the wallet's key — that's
// the only way to later decrypt this position to claim a payout.

What's in the box

  • All 29 program instructions with typed builders that return raw TransactionInstructions (compose, simulate, bundle freely) — 3 admin, 8 init comp def, 4 market lifecycle, 2 bet, 2 resolve, 4 claim, plus 3 dispute-window instructions added in v0.2.
  • Ten high-level action helpers (createMarket, createMarketMulti, placeBet, resolveMarket, claimPayout, claimRefund, cancelMarket, withdrawCreatorFunds, plus flagResolution, finalizeResolution, adminOverrideResolution) that hide the "encrypt → send → await MPC callback → refetch" choreography.
  • Async progress events on every multi-step action, so frontends can render Encrypting…Submitting…Awaiting MPC nodes… instead of one generic spinner.
  • Typed event surface10 discriminated-union events (7 core + 3 dispute-window in v0.2) with parseLogs, parseLogsFor, subscribeAll, onXxx helpers, and a WebSocket-less pollEvents fallback. Decoded fields are camelCase bigints, matching the typed interfaces 1:1.
  • Account fetch + memcmp filters for every program account, with byte offsets drift-tested against the IDL.
  • React hooks (@cypher-zk/sdk/react): CypherProvider, useGlobalState, useMarket, useMarkets, useUserPositions, usePlaceBet, useResolveMarket, useClaimPayout, useClaimRefund, useCreateMarket, useCancelMarket, useFlagResolution, useFinalizeResolution, useAdminOverrideResolution (v0.2), useMarketEvents — all built on TanStack Query with sensible cache-invalidation defaults.
  • Phase-aware UI gatingmarketPhase(market) returns one of nine literal values including pendingResolution, awaitingFinalize, and disputed so buttons only render when the corresponding ix is actually clickable.
  • Cluster-agnostic at runtime — reads GlobalState.accepted_mint on-chain, so the same build works against any deployment (devnet CSDC, mainnet USDC, localnet test mint).
  • 150 unit tests (712 assertions) covering PDA derivations, fee math, deadline phases, IDL drift, Arcium offsets, encryption round-trip, event parser round-trip with all 10 event types, action input validation (including dispute-window phase gating), and React hook wiring. Plus opt-in localnet integration and devnet smoke suites.

Install

bun add @cypher-zk/sdk
# or
npm install @cypher-zk/sdk

Peer dependencies

| Package | Required for | | -------------------------- | ----------------------------------- | | react ^18 || ^19 | @cypher-zk/sdk/react subpath only | | @tanstack/react-query ^5 | @cypher-zk/sdk/react subpath only |

Core SDK works in any TypeScript environment with no peer requirements.


Quickstart

1. Construct a client

import { Connection } from "@solana/web3.js";
import { CypherClient } from "@cypher-zk/sdk";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");

// Wallet can be:
//   - any @solana/wallet-adapter wallet (browser)
//   - `keypairToWallet(Keypair)` (Node / scripts / tests)
const client = new CypherClient({ connection, wallet, cluster: "devnet" });

2. Read protocol state

const gs = await client.globalState.fetch();
console.log("Protocol fee:", gs.protocolFeeRate, "bps");

const market = await client.markets.fetch(0n);
const active = await client.markets.byState(0); // MarketState.Active
const mine = await client.markets.byCreator(wallet.publicKey);
const myBets = await client.positions.byUser(wallet.publicKey);

3. Place a private bet — with live progress

import { computeFees } from "@cypher-zk/sdk";

// Preview the fee split before showing a confirm modal:
const preview = computeFees(5_000_000n, {
  protocolFeeRateBps: gs.protocolFeeRate,
  lpFeeRateBps: gs.lpFeeRate,
});
console.log(
  "Net stake:",
  preview.netAmount,
  "after fees:",
  preview.protocolFee + preview.lpFee,
);

// Fire the end-to-end flow:
const { signature, position, userKeypair } = await client.actions.placeBet({
  payer: wallet.publicKey,
  user: wallet.publicKey,
  marketId: 0n,
  side: 1, // 0 = NO, 1 = YES
  amountUsdc: 5_000_000n, // $5 (USDC has 6 decimals)
  onProgress: ({ stage, message, signature }) => {
    // stage ∈ "validating" | "fetching-state" | "encrypting" | "submitting"
    //       | "awaiting-callback" | "refetching" | "done"
    console.log(stage, message ?? "", signature ?? "");
  },
});

// IMPORTANT: persist userKeypair.privateKey somewhere the user controls
// (e.g. localStorage encrypted under the wallet's signature) — without
// it, the user cannot decrypt this position later when claiming.
saveSecretForLater(position!.market, userKeypair.privateKey);

The progress callback lets you drive multi-step UI:

[ Validating … ]                  ◄ instant, client-side
[ Fetching protocol state … ]
[ Encrypting your bet … ]
[ Submitting transaction … ]      ◄ tx signature available here
[ Awaiting MPC nodes (~10s) … ]
[ Updating position … ]
[ Done! ]

4. Create a market

const { marketId, marketPda, signature } = await client.actions.createMarket({
  creator: wallet.publicKey,
  question: "Will ETH hit $10k by end of 2026?",
  closeTime: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
  category: 0, // MarketCategory.Crypto
  // v0.2+: optional. Defaults to MIN_CHALLENGE_PERIOD_SECS (24h).
  // Must be in [MIN_CHALLENGE_PERIOD_SECS, MAX_CHALLENGE_PERIOD_SECS]
  // (24h–48h). Pin shorter for prediction markets that settle fast.
  challengePeriod: 24 * 3600,
  onProgress: (e) => console.log(e.stage),
});

Multi-outcome variant — createMarketMulti with numOutcomes: 2 | 3 | 4.

5. Resolve, claim payout, claim refund

// Resolver (oracle / DAO):
await client.actions.resolveMarket({
  payer: wallet.publicKey,
  resolver: wallet.publicKey,
  marketId,
  outcomeValue: 1,
  onProgress: (e) => console.log(e.stage),
});

// Winning bettor:
await client.actions.claimPayout({
  payer: wallet.publicKey,
  user: wallet.publicKey,
  marketId,
});

// Bettor on an unresolved market (past resolution_deadline):
await client.actions.claimRefund({
  payer: wallet.publicKey,
  user: wallet.publicKey,
  marketId,
});

Each refuses pre-flight if the market isn't in the right phase (marketPhase returns "claimable" for payout, "refundable" for refund) so the user never burns gas on a guaranteed-to-fail tx.

v0.2 note: resolveMarket no longer flips the market to Resolved. The reveal callback now sets state = PendingResolution and starts the challenge window. claimPayout opens only after finalizeResolution / adminOverrideResolution runs — see § Dispute window (v0.2) below.

6. Subscribe to events

// Real-time (WebSocket):
const sub = client.events.onBetPlaced((data) => {
  // `data` is BetPlacedEvent — fully typed (camelCase fields, bigint amounts):
  console.log(
    `Bet placed on market ${data.market.toBase58()} — odds ${data.entryOdds}`,
  );
});

// Generic, typed by name:
const sub2 = client.events.subscribe("MarketResolvedEvent", (data) => {
  console.log("Outcome:", data.outcome, "Payout ratio:", data.payoutRatio);
});

// v0.2: dispute-window events
const sub3 = client.events.onResolutionFlagged((data) => {
  console.log("Market disputed by:", data.flaggedBy.toBase58());
});
const sub4 = client.events.onMarketFinalized((data) => {
  console.log("Market finalized — claims now open. Outcome:", data.outcome);
});
const sub5 = client.events.onResolutionOverridden((data) => {
  console.log(`Admin override: ${data.oldOutcome} → ${data.newOutcome}`);
});

// Later
sub.unsubscribe();
sub2.unsubscribe();

// Poll-based fallback (no WS required):
const recent = await client.events.pollEvents({ limit: 20 });
for (const { event, signature, slot } of recent) {
  console.log(event.name, "in tx", signature, "at slot", slot);
}

// Parse events out of a known transaction:
import { parseLogs, parseLogsFor } from "@cypher-zk/sdk";
const tx = await connection.getTransaction(sig, {
  maxSupportedTransactionVersion: 0,
});
const allEvents = parseLogs(tx?.meta?.logMessages ?? []);
const payoutsOnly = parseLogsFor(
  tx?.meta?.logMessages ?? [],
  "PayoutClaimedEvent",
);

7. Phase helpers

import { marketPhase, projectDeadlines } from "@cypher-zk/sdk";

// Compute what action is currently available on a market:
switch (marketPhase(market)) {
  case "betting":
    /* show "Bet" button */ break;
  case "awaitingResolve":
    /* show "Pending resolution" */ break;
  case "pendingResolution":
    /* v0.2: in challenge window — show countdown + "Flag" */ break;
  case "awaitingFinalize":
    /* v0.2: window elapsed — show "Finalize" button */ break;
  case "disputed":
    /* v0.2: flagged — admin override required */ break;
  case "claimable":
    /* show "Claim payout" */ break;
  case "refundable":
    /* show "Claim refund" */ break;
  case "expired":
    /* show "Admin sweep eligible" */ break;
  case "cancelled":
    /* show "Cancelled" */ break;
}

// Preview deadlines for a draft market the user is filling in:
const projected = projectDeadlines(BigInt(closeTimeSec));
console.log(
  "Resolution deadline:",
  new Date(Number(projected.resolutionDeadline) * 1000),
);

Dispute window (v0.2)

After the reveal callback lands, the market enters a configurable 24h–48h challenge window (market.state === PendingResolution = 4) during which anyone can flag a wrong outcome. Only after the window closes does the market move to Resolved and claims open.

Resolver calls resolveMarket
       │
       ▼ reveal_market_outcome_* (Arcium MPC)
  callback writes: outcome, revealedPool*, payoutRatio
                   state = PendingResolution
                   challengeDeadline = now + challenge_period

  ┌─────────────────────────────────────┐
  │  Challenge window (24h–48h)         │
  │  Anyone may flagResolution          │
  │    → market.disputed = true         │
  └─────────────────────────────────────┘
       │                          │
   (undisputed)               (disputed)
       │                          │
       ▼                          ▼
  finalizeResolution      adminOverrideResolution
  (anyone, post-window)   (admin only, recomputes payout_ratio)
       │                          │
       └──────────┬───────────────┘
                  ▼
         state = Resolved
       claim_deadline + refund_deadline set
              claimPayout opens

Three new actions

// 1. ANYONE during the challenge window — flag a wrong resolution
await client.actions.flagResolution({
  flagger: wallet.publicKey,
  marketId,
  onProgress: (e) => console.log(e.stage),
});

// 2. ANYONE after the window closes undisputed — finalize → state = Resolved
await client.actions.finalizeResolution({
  caller: wallet.publicKey,
  marketId,
  onProgress: (e) => console.log(e.stage),
});

// 3. ADMIN ONLY when market.disputed === true — re-resolve with corrected outcome
//    Recomputes payout_ratio from the already-revealed plaintext pools.
await client.actions.adminOverrideResolution({
  admin: wallet.publicKey,
  marketId,
  outcomeValue: 1,
  onProgress: (e) => console.log(e.stage),
});

Each emits validating → submitting → refetching → done progress stages and refuses pre-flight if the market isn't in the right phase (SDK throws clean errors pointing at the next valid step — e.g. "market is in 'pendingResolution' — call finalizeResolution first").

Phase gating

marketPhase(market) returns the three new values whenever state === PendingResolution:

| marketPhase | Meaning | Clickable | | --------------------- | ------------------------------------------ | -------------------------------------- | | "pendingResolution" | inside challenge window, not flagged | flagResolution (any user) | | "awaitingFinalize" | window elapsed, not flagged | finalizeResolution (any user) | | "disputed" | flagged during window | adminOverrideResolution (admin only) | | "claimable" | finalized → window closed (state=Resolved) | claimPayout |

claimPayoutAction and useClaimPayout reject pre-flight in the first three phases with a hint to call finalizeResolution first.

React example

import {
  useMarket,
  useFlagResolution,
  useFinalizeResolution,
} from "@cypher-zk/sdk/react";
import { marketPhase } from "@cypher-zk/sdk";

function ChallengeWindowControls({ marketId }: { marketId: bigint }) {
  const { data: market } = useMarket(marketId, { refetchInterval: 5_000 });
  const flag = useFlagResolution();
  const finalize = useFinalizeResolution();
  if (!market) return null;

  const phase = marketPhase(market);
  if (phase === "pendingResolution") {
    return (
      <>
        <p>
          Challenge closes at{" "}
          {new Date(Number(market.challengeDeadline) * 1000).toLocaleString()}
        </p>
        <button
          onClick={() => flag.mutate({ flagger: wallet.publicKey!, marketId })}
        >
          Flag this resolution
        </button>
      </>
    );
  }
  if (phase === "awaitingFinalize") {
    return (
      <button
        onClick={() => finalize.mutate({ caller: wallet.publicKey!, marketId })}
      >
        Finalize resolution
      </button>
    );
  }
  if (phase === "disputed") {
    return <p>Awaiting admin override.</p>;
  }
  return null;
}

Defaults & bounds

| Constant | Value | Notes | | --------------------------- | ----------------- | --------------------------------- | | MIN_CHALLENGE_PERIOD_SECS | 24 * 3600 (24h) | Action helpers default here | | MAX_CHALLENGE_PERIOD_SECS | 48 * 3600 (48h) | Hard ceiling enforced by contract |

The high-level client.actions.createMarket makes challengePeriod optional and defaults to MIN_CHALLENGE_PERIOD_SECS. The raw createMarketIx builder requires it — out-of-range values throw client-side before the tx is built.

Six new error codes

| Code | Name | When | | ------ | --------------------------- | ------------------------------------- | | 6036 | InvalidChallengePeriod | challengePeriod outside 24h–48h | | 6037 | NotPendingResolution | flag/finalize/override on wrong state | | 6038 | ChallengePeriodNotElapsed | finalize called too early | | 6039 | ChallengePeriodElapsed | flag called after window closed | | 6040 | MarketDisputed | finalize on a flagged market | | 6041 | MarketNotDisputed | admin override on a clean market |

Use parseCypherError(err) to extract a typed CypherErrorCode for branching.

Three new events

ResolutionFlaggedEvent, MarketFinalizedEvent, ResolutionOverriddenEvent are added to the discriminated CypherEvent union and have matching client.events.on* helpers.


React hooks

import {
  CypherProvider,
  useGlobalState,
  useMarket,
  useMarkets,
  usePlaceBet,
} from "@cypher-zk/sdk/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
import { CypherClient, keypairToWallet, MarketState } from "@cypher-zk/sdk";
import type { ActionProgressEvent } from "@cypher-zk/sdk";

const queryClient = new QueryClient();
const client = new CypherClient({ connection, wallet, cluster: "devnet" });

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <CypherProvider client={client}>
        <MarketView marketId={0n} />
      </CypherProvider>
    </QueryClientProvider>
  );
}

function MarketView({ marketId }: { marketId: bigint }) {
  const { data: market, isLoading } = useMarket(marketId);
  const [stage, setStage] = useState<ActionProgressEvent | null>(null);

  const placeBet = usePlaceBet({
    onSuccess: ({ userKeypair }) => persistUserSecret(userKeypair.privateKey),
  });

  if (isLoading) return <p>Loading market…</p>;
  if (!market) return <p>Market not found.</p>;

  return (
    <div>
      <h2>{market.question}</h2>
      <button
        disabled={placeBet.isPending}
        onClick={() =>
          placeBet.mutate({
            payer: wallet.publicKey,
            user: wallet.publicKey,
            marketId,
            side: 1,
            amountUsdc: 5_000_000n,
            onProgress: setStage,
          })
        }
      >
        {placeBet.isPending && stage ? `Bet → ${stage.stage}` : "Bet $5 YES"}
      </button>
      {placeBet.error && (
        <p style={{ color: "crimson" }}>{placeBet.error.message}</p>
      )}
    </div>
  );
}

Available hooks

| Hook | Kind | Description | | --------------------------------------- | ------------ | ----------------------------------------------------------------- | | useGlobalState() | Query | Protocol config (fees, mint, admin, counter) | | useMarket(id) | Query | Single market by ID | | useMarkets(filter?) | Query | All/filtered markets (creator, state) | | useUserPositions(user) | Query | All bet positions for a user | | usePlaceBet() | Mutation | End-to-end private bet | | useCreateMarket() | Mutation | Create a new market | | useResolveMarket() | Mutation | Submit outcome + await reveal | | useClaimPayout() | Mutation | Claim winning payout | | useClaimRefund() | Mutation | Claim refund on unresolved market | | useCancelMarket() | Mutation | Cancel a zero-bet market | | useFlagResolution() (v0.2) | Mutation | Flag a pending resolution during the challenge window | | useFinalizeResolution() (v0.2) | Mutation | Finalize a pending resolution after the window elapses undisputed | | useAdminOverrideResolution() (v0.2) | Mutation | Admin re-resolves a disputed market | | useMarketEvents() | Subscription | Live event stream (component-scoped) |

Mutation hooks auto-invalidate the relevant query keys on success. Read hooks expose their queryKey factories (marketKeys.one(id), positionKeys.byUser(user), globalStateKeys.all) for manual invalidation.

Live example under examples/react-vite/ — a small Vite app that renders the protocol state, lists active markets, and drives usePlaceBet with live progress.


Layout

cypher-sdk/
├── src/                    # framework-agnostic core (ESM)
│   ├── config.ts           # program ID, cluster presets, constants
│   ├── pda.ts              # PDA derivations
│   ├── wallet.ts           # Wallet interface + Keypair adapter
│   ├── fees.ts             # mirrors on-chain fee math
│   ├── deadlines.ts        # market phase computation
│   ├── errors.ts           # CypherErrorCode + Anchor error walker
│   ├── client.ts           # CypherClient — single import surface
│   ├── accounts/           # fetch + memcmp filter helpers per account
│   ├── arcium/             # MPC glue (cipher, queue accounts, offsets)
│   ├── instructions/       # raw TransactionInstruction builders (29 ix, v0.2)
│   ├── actions/            # high-level flows + progress events
│   ├── events/             # typed event parser + WS/poll subscriptions
│   ├── idl/                # synced Anchor IDL (source of truth)
│   └── node/               # node-only admin helpers
├── react/src/              # @cypher-zk/sdk/react hooks subpath
├── examples/react-vite/    # minimal Vite app smoke
├── docs/
│   ├── architecture.md     # three-surface model + module map
│   └── flows.md            # one diagram per user flow
└── tests/
    ├── unit/               # 150 tests, 712 assertions — pure, no chain
    ├── integration/        # INTEGRATION=1 — Arcium localnet lifecycle
    └── devnet/             # DEVNET=1 — devnet read-only + opt-in writes

See docs/architecture.md for the three-surface model (circuit / program / client), the account topology, and the full runtime flow of a private bet. See docs/flows.md for per-flow ASCII diagrams.


Cluster strategy

The SDK is cluster-agnostic at runtime: it reads the accepted SPL mint from GlobalState.accepted_mint on every flow rather than hard-coding CSDC vs USDC. The same build works against any Cypher deployment.

| Cluster | RPC default | Accepted mint | Arcium offset | | ---------- | ----------------------------- | ------------------ | --------------- | | devnet | api.devnet.solana.com | CSDC (8AF9BABN…) | 456 | | mainnet | api.mainnet-beta.solana.com | USDC (EPjFWdd5…) | (set at deploy) | | localnet | localhost:8899 | CSDC (test build) | 1116522022 |

// Explicit preset:
const client = new CypherClient({ connection, wallet, cluster: "devnet" });

// Custom config — override RPC and/or Arcium cluster offset:
const client = new CypherClient({
  connection,
  wallet,
  cluster: {
    name: "devnet",
    rpc: "https://my-helius-endpoint.example",
    arciumClusterOffset: 456,
    expectedMint: KNOWN_MINTS.devnetCSDC,
  },
});

Scripts

| Command | Purpose | | -------------------------- | --------------------------------------------- | | bun install | Install deps | | bun test | All unit suites (gates skipped) | | bun run test:unit | Unit-only | | bun run test:integration | INTEGRATION=1 — Arcium localnet lifecycle | | bun run test:devnet | DEVNET=1 — devnet read-only + opt-in writes | | bun run typecheck | tsc --noEmit (strict) | | bun run build | ESM + .d.ts via tsup → dist/ | | bun run sync:idl | Re-copy IDL + types from ../cypher-main | | bun run prepublishOnly | sync IDL → typecheck → unit tests → build |


Security

The Cypher protocol underwent a two-round security audit. All 9 findings — 3 critical, 2 high, 4 medium/low — have been remediated and verified. See cypher-main/audit_report.md for the full report.

The SDK adds defense-in-depth client-side:

  • computeFees mirrors the on-chain math exactlyplaceBet pre-asserts the encrypted net_amount will match what the contract computes from the gross. The contract's circuit also verifies this on-chain (audit fix H-1) — the SDK guard surfaces the failure as a clean rejection instead of a wasted MPC computation.
  • marketPhase blocks known-bad claimsclaimPayout / claimRefund refuse to send if the market isn't in the right phase, saving users from on-chain MarketStillOpen / ClaimPeriodExpired rejections.
  • Position double-claim guardclaimPayout reads position.claimed before submitting (the contract enforces it again as audit fix H-2).
  • Strict ciphertext-length enforcement — every builder that takes a Uint8Array ciphertext asserts length === 32 before assembling the ix, catching the "combined ciphertext arrays" mistake from the Arcium skill before it hits the program.
  • v0.2 dispute-window phase gatingclaimPayout rejects pre-flight if market.state === PendingResolution with a typed error pointing the caller at finalizeResolution, so users don't pay MPC compute fees on a guaranteed-to-fail tx. flagResolution / finalizeResolution / adminOverrideResolution validate against market.disputed + challengeDeadline client-side.

Changelog

0.2.0 — dispute window

  • NEW: 3 instructions (flagResolution, finalizeResolution, adminOverrideResolution) + 3 actions + 3 React hooks + 3 events
    • 6 error codes (6036–6041).
  • NEW: MarketState.PendingResolution = 4 and three new marketPhase values: pendingResolution, awaitingFinalize, disputed.
  • NEW: Market gains challengePeriod, challengeDeadline, disputed fields; layout offsets shifted.
  • NEW: MIN_CHALLENGE_PERIOD_SECS / MAX_CHALLENGE_PERIOD_SECS constants (24h / 48h).
  • BREAKING: createMarketIx / createMarketMultiIx raw builders require a new challengePeriod arg. The high-level client.actions.createMarket makes it optional (defaults to 24h).
  • BREAKING: resolveMarket now leaves the market in PendingResolution; claimPayout opens only after finalizeResolution or adminOverrideResolution runs.
  • DX: claimPayoutAction pre-flight error now explicitly names finalizeResolution as the next step when state is PendingResolution.

License

Source Available — use is permitted, redistribution is not. See LICENSE for full terms.