Skip to main content
The Cyphers SDK emits strongly typed events for every on-chain action. You can subscribe to a live WebSocket stream via client.events.subscribeAll, listen for specific event types, or poll for recent events in environments that do not support WebSockets. All event field names use camelCase and all numeric fields are bigint.
Event fields use camelCase bigint values - not snake_case, not number, not BN. For example, use event.data.payoutAmount (not payout_amount), and event.data.marketId (not market_id). Passing these values directly to a Number() cast is safe for display; use them as bigint for arithmetic.

Event types

All event types are emitted by the Cyphers program and parsed by the SDK into typed objects. Each event has a name discriminant and a data object:

MarketCreatedEvent

Emitted when createMarket or createMarketMulti completes.
FieldTypeDescription
marketIdbigintSequential market counter value
marketTypenumber0 = YesNo, 1 = MultiOutcome
categorynumberMarketCategory enum value
creatorPublicKeyMarket creator wallet
questionstringRaw question text (may include […] suffix)
closeTimebigintUnix timestamp when betting closes

BetPlacedEvent

Emitted after the Arcium MPC callback confirms a bet. Note that encryptedAmount and encryptedSide are ciphertexts - not the original values.
FieldTypeDescription
marketPublicKeyMarket PDA
userPublicKeyBettor wallet
encryptedAmountUint8Array32-byte ciphertext
encryptedSideUint8Array32-byte ciphertext
noncebigintu128 encryption nonce
entryOddsbigintLocked-in odds at time of bet (scaled by 1e9)

MarketResolvedEvent

Emitted when the Arcium MPC callback writes the resolution.
FieldTypeDescription
marketPublicKeyMarket PDA
outcomenumberWinning outcome index
revealedPool0bigintDecrypted pool 0 total
revealedPool1bigintDecrypted pool 1 total
revealedPool2bigintDecrypted pool 2 total (MultiOutcome)
revealedPool3bigintDecrypted pool 3 total (MultiOutcome)
payoutRatiobigintPayout ratio (scaled by 1e9)

MarketCancelledEvent

FieldTypeDescription
marketPublicKeyMarket PDA
creatorPublicKeyCreator who cancelled
bondReturnedbigintUSDC lamports returned to creator

CreatorWithdrawnEvent

FieldTypeDescription
marketPublicKeyMarket PDA
creatorPublicKeyCreator who withdrew
bondbigintCreator bond amount returned
lpFeesbigintLP fees collected
totalbigintTotal USDC transferred

PayoutClaimedEvent

FieldTypeDescription
marketPublicKeyMarket PDA
userPublicKeyClaimant wallet
payoutAmountbigintUSDC lamports paid out

RefundClaimedEvent

FieldTypeDescription
marketPublicKeyMarket PDA
userPublicKeyClaimant wallet
refundAmountbigintUSDC lamports refunded

ResolutionFlaggedEvent (v0.2+)

FieldTypeDescription
marketPublicKeyDisputed market PDA
flaggedByPublicKeyWallet that submitted the flag

MarketFinalizedEvent (v0.2+)

FieldTypeDescription
marketPublicKeyMarket PDA
outcomenumberConfirmed winning outcome index
payoutRatiobigintFinal payout ratio

ResolutionOverriddenEvent (v0.2+)

FieldTypeDescription
marketPublicKeyMarket PDA
oldOutcomenumberOriginal (disputed) outcome
newOutcomenumberAdmin-corrected outcome
newPayoutRatiobigintRecomputed payout ratio
adminPublicKeyAdmin wallet that overrode

Subscribing to all events

Use client.events.subscribeAll to receive a callback for every event emitted by the Cyphers program. The subscription uses Connection.onLogs under the hood.
import { useCypherClient } from "@cypher-zk/sdk/react";
import type { CypherEvent } from "@cypher-zk/sdk";
import { useEffect, useState } from "react";

function useAllEvents(maxItems = 50) {
  const client = useCypherClient();
  const [events, setEvents] = useState<CypherEvent[]>([]);

  useEffect(() => {
    const sub = client.events.subscribeAll((event) => {
      setEvents((prev) => [event, ...prev].slice(0, maxItems));
    });
    return () => sub.unsubscribe();
  }, [client]);

  return events;
}
The callback receives a CypherEvent union - use event.name as the discriminant:
client.events.subscribeAll((event) => {
  switch (event.name) {
    case "MarketCreatedEvent":
      console.log("New market:", event.data.marketId, event.data.question);
      break;
    case "BetPlacedEvent":
      console.log("Bet on", event.data.market.toBase58(), "odds:", event.data.entryOdds);
      break;
    case "PayoutClaimedEvent":
      console.log("Payout:", Number(event.data.payoutAmount) / 1_000_000, "USDC");
      break;
    case "ResolutionFlaggedEvent":
      console.warn("Disputed:", event.data.market.toBase58());
      break;
  }
});
Returns EventSubscription with an unsubscribe() method. Always call unsubscribe() in your cleanup / useEffect return to avoid memory leaks and dangling WebSocket listeners.

Filtering events for a specific market

subscribeAll fires for every event across the entire Cyphers program. Filter client-side by checking event.data.market:
useEffect(() => {
  const sub = client.events.subscribeAll((evt) => {
    const hasMarketField =
      evt.name === "BetPlacedEvent" ||
      evt.name === "MarketResolvedEvent" ||
      evt.name === "PayoutClaimedEvent" ||
      evt.name === "RefundClaimedEvent" ||
      evt.name === "ResolutionFlaggedEvent" ||
      evt.name === "MarketFinalizedEvent";

    if (hasMarketField && evt.data.market.equals(myMarketPda)) {
      handleMarketEvent(evt);
    }
  });

  return () => sub.unsubscribe();
}, [client, myMarketPda.toBase58()]);

Typed per-event subscribers

For single-event subscriptions, use the named helpers on client.events:
// Only fires for BetPlacedEvent
const sub = client.events.onBetPlaced((data) => {
  console.log("Bet placed on", data.market.toBase58());
});

// Only fires for PayoutClaimedEvent
const sub2 = client.events.onPayoutClaimed((data) => {
  console.log("Payout:", data.payoutAmount);
});
Available helpers: onMarketCreated, onBetPlaced, onMarketResolved, onMarketCancelled, onCreatorWithdrawn, onPayoutClaimed, onRefundClaimed.

React hook

For React applications, the useMarketEvents hook from @cypher-zk/sdk/react manages the subscription lifecycle automatically:
import { useMarketEvents } from "@cypher-zk/sdk/react";
import type { CypherEvent } from "@cypher-zk/sdk";

function ActivityFeed() {
  const events: CypherEvent[] = useMarketEvents();

  return (
    <ul>
      {events.map((e, i) => (
        <li key={i}>
          <strong>{e.name}</strong>
          {e.name === "BetPlacedEvent" && (
            <span> - {e.data.market.toBase58().slice(0, 8)}… odds: {e.data.entryOdds.toString()}</span>
          )}
          {e.name === "PayoutClaimedEvent" && (
            <span> - ${Number(e.data.payoutAmount) / 1_000_000} to {e.data.user.toBase58().slice(0, 8)}…</span>
          )}
        </li>
      ))}
    </ul>
  );
}

Polling fallback

In environments without WebSocket support (some serverless edge runtimes), use the polling approach:
import { useQuery } from "@tanstack/react-query";
import { useCypherClient } from "@cypher-zk/sdk/react";

function usePolledEvents(refetchIntervalMs = 5_000) {
  const client = useCypherClient();
  return useQuery({
    queryKey: ["cyphers", "events", "polled"],
    queryFn: () => client.events.pollEvents({ limit: 50 }),
    refetchInterval: refetchIntervalMs,
  });
}
WebSocket connections on most RPC providers reconnect automatically when they drop. For extra resilience, key your useEffect dependency on client.cluster.rpc so the subscription is re-established if you switch RPC endpoints at runtime.
useEffect(() => {
  const sub = client.events.subscribeAll(handler);
  return () => sub.unsubscribe();
}, [client.cluster.rpc]); // re-subscribe on RPC URL change

Parsing logs manually

If you receive raw transaction logs from your own RPC listener, use parseLogs to extract typed events:
import { parseLogs } from "@cypher-zk/sdk";

const events = parseLogs(transaction.meta?.logMessages ?? []);
for (const event of events) {
  console.log(event.name, event.data);
}