Friendlier read-only wallet error.
readonlyWallet now throws a typed ReadonlyWalletError instead of a generic Error, with a user-friendly message and a stable code for programmatic detection.- New export:
ReadonlyWalletError(code === "READONLY_WALLET",name === "ReadonlyWalletError"). - New default message:
"Wallet is read-only. Connect a wallet before sending a transaction." - Detect the case with
err instanceof ReadonlyWalletErrorto show a “connect wallet” prompt instead of leaking the SDK detail to your user.
RPC batching layer. New
src/rpc/ module that chunks bulk reads past Solana’s 100-key getMultipleAccountsInfo cap, runs with bounded concurrency, and retries transient errors (429 / 5xx / network blips) with exponential backoff. Fixes the practical bug where fetchMarketQuestions broke once a deployment had more than 100 markets.- New low-level helper:
batchedGetMultipleAccountsInfo(connection, pubkeys, overrides?). - New
CypherClientOptions.rpcOptions?: Partial<BatchOptions>— tunechunkSize,concurrency,retries,retryBaseMsper client. Conservative defaults (chunkSize 100, concurrency 5, retries 3) survive free-tier RPCs; bumpconcurrencyon paid plans. - New paginated fetcher:
fetchMarketsByIds(client, ids[])/client.markets.byIds(ids)— fetch a known list of markets without pulling the entire program payload. Preferred over.all()once the program has more than a few hundred markets. client.marketQuestions.fetchMany(markets)— exposes the existing batch helper on the client surface, now routed through the chunked fetcher.- React:
useMarkets({ ids })filter mode that routes tobyIds. - React:
useMarketQuestions(markets)— TanStack-Query wrapper overfetchManywith a 60s stale time (questions are immutable after market creation). - New query-key factory:
marketQuestionKeys.byMarkets(pdas)for manual invalidation.
Multi-bet support. Users can now hold multiple positions per market. Each bet gets an auto-incrementing
betIndex.placeBetActionreadsuser_state.next_bet_indexautomatically and retries up to 3 times on concurrent-bet index conflicts.ClaimInputsanduseClaimPayout/useClaimRefundnow acceptbetIndex(defaults to0n).EncryptedPositionAccountaddsbetIndex: bigintfield.saveSecret(market, betIndex, privateKey)- signature changed to includebetIndex.positionPda(market, user, betIndex?)-betIndexdefaults to0n.
MarketQuestion PDA. Question text for new markets moves to a dedicated account to save space in MarketAccount.- Use
fetchMarketQuestions(client, marketPublicKeys)to hydrate questions in batch. - Legacy markets still have
account.inlineQuestion. marketFormatVersion(accountSize)returns"v1"|"v2"|"v3"|null.
- New actions:
flagResolutionAction,finalizeResolutionAction,adminOverrideResolutionAction. - New React hooks:
useFlagResolution,useFinalizeResolution,useAdminOverrideResolution. - New events:
ResolutionFlaggedEvent,MarketFinalizedEvent,ResolutionOverriddenEvent. - Markets pass through
PendingResolution(state4) between reveal and claim. - New constants:
MIN_CHALLENGE_PERIOD_SECS(24h),MAX_CHALLENGE_PERIOD_SECS(48h).
Breaking change. The
betIndex parameter was removed from the low-level placePrivateBetYesnoIx and placePrivateBetMultiIx instruction builders. The on-chain user_state PDA now tracks next_bet_index automatically.userStatePda(user)- new PDA for per-user bet index counter.readNextBetIndex(client, user)- helper to fetch the current index when building low-level instructions. Not needed if you useplaceBetAction.- High-level
placeBetActionhandles index tracking and retries automatically - no changes needed for callers usingclient.actions.placeBet.
Position decoder fix.
fetchUserPositions (and client.positions.byUser) now correctly handles mixed legacy 208-byte and current 216-byte position accounts.- Previously, a single legacy account in the result set would cause the entire query to fail silently (returning zero positions).
- The fix uses per-account size-aware decoding instead of Anchor’s all-or-nothing decoder.
v0.8.0 and earlier
- One position per user per market (no
betIndex). saveSecret(market, secret)took two arguments (nobetIndex).- Questions always stored inline in
MarketAccount.inlineQuestion. - No challenge window - markets moved directly from
PendingResolutiontoResolvedafter the MPC callback.