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

@vercora-protocol/sdk

v0.8.0

Published

TypeScript SDK for Vercora Protocol on Solana — client for the outcome-markets Anchor program (issuer utility infra; more product lanes on the roadmap).

Downloads

946

Readme

@vercora-protocol/sdk

TypeScript SDK for Vercora Protocol on Solana (Anchor). Vercora is an outcome markets protocol with a public platform + creator agency narrative; this package documents only the outcome markets program today (PredictionMarketClient, bundled IDL). Other protocol surfaces are not exposed in this SDK until they are documented in this package.

Install

npm install @vercora-protocol/sdk
# or
yarn add @vercora-protocol/sdk

Peer dependencies (install alongside, keep versions aligned; see IDL, account metas, and peer deps for SendTransactionError notes):

npm install @coral-xyz/anchor @solana/web3.js @solana/spl-token bn.js
# or
yarn add @coral-xyz/anchor @solana/web3.js @solana/spl-token bn.js

Quick start

import * as anchor from "@coral-xyz/anchor";
import { Connection, clusterApiUrl } from "@solana/web3.js";
import { IDL, PROGRAM_ID, PredictionMarketClient } from "@vercora-protocol/sdk";
import type { Vercora } from "@vercora-protocol/sdk";

const connection = new Connection(clusterApiUrl("devnet"));
const provider = new anchor.AnchorProvider(connection, wallet, {});
anchor.setProvider(provider);

// Bundled IDL, program id is embedded in `IDL.address` (`PROGRAM_ID` matches it)
const program = new anchor.Program<Vercora>(IDL, provider);
const client = new PredictionMarketClient(program);

Most transaction methods use provider.wallet.publicKey as the signer (user, authority, creator, etc.). Pass a wallet that can sign; read-only flows can use a read-only provider for fetch helpers only.

Package exports

| Export | Description | | ------------------------ | -------------------------------------------------------------------------------------------- | | PredictionMarketClient | High-level program wrapper (see below). | | IDL, PROGRAM_ID | Anchor IDL JSON and PublicKey for the deployed program. | | pda helpers | deriveMarket, deriveVault, deriveParimutuelState, … (see PDA helpers). | | types | Params and account shapes (CreateMarketParams, ListedMarket, …). | | marketUi | Pure UI helpers (getMarketLifecycleStatus, formatTimeLeft, …). | | Vercora (type) | Anchor Program generic for Program<Vercora>. | | agents/skill.md | Short AI agent entrypoint (production: https://vercora.xyz/agents/skill.md). Human UI with the same resolution narrative: https://vercora.xyz/docs/agents. |


IDL, account metas, and peer deps (browser + raw Anchor)

These topics come up often in integrator issues; the published IDL in this package (dist/idl/vercora.json, same bytes as IDL export) is generated from the same Anchor build as the on-chain program we ship against.

platform_treasury_wallet writable flag (SOL fee CPIs)

For claim_resolver_stake, resolve_refute, and upsert_user_profile, the program may transfer platform_fee_lamports (lamports) from the signer to platform_treasury_wallet before SPL CPIs (profile upsert has no SPL CPIs, but uses the same SOL fee path). The treasury wallet account must therefore appear as writable in the outer transaction’s AccountMetas. If your IDL marks it read-only, the runtime can raise PrivilegeEscalation / “writable privilege escalated” on that pubkey.

Mitigation: Use the IDL bundled with the same @vercora-protocol/sdk version you installed (or regenerate from the program source). Do not fork the JSON IDL with weaker writable flags for those instructions.

Served IDL vs package IDL (drift and disputerCollateral not provided)

If the app loads IDL from fetchIdl(), a static public/idl/vercora.json, or a CDN URL, it can drift from node_modules/@vercora-protocol/sdk/dist/idl/vercora.json. Anchor then builds wrong or incomplete account lists while PredictionMarketClient assumes the bundled layout, e.g. Account 'disputerCollateral' not provided when metas omit accounts present in the current IDL.

Recommendation: Prefer import { IDL } from '@vercora-protocol/sdk' (or copy dist/idl/vercora.json from the exact npm version) so the Program client and high-level client agree. If you must hot-reload IDL from HTTP, version and cache-bust the URL to match the deployed program + SDK release.

Disputer collateral on resolve_refute

disputer_collateral is the refuter’s collateral SPL ATA for the market mint, the same role as refuter_collateral on open_refute. It is always in the resolve_refute account list: on dismiss the handler does not transfer the refute bond into it, but the account must still be present and valid (mint + owner = ResolutionState.disputer). On accept, the bond is refunded into this ATA.

It is not the market trading vault, pari pool vault, or treasury ATA.

Who may sign resolve_refute (vs market resolvers)

Signers: GlobalConfig primary or secondary authority, or PlatformRegistry.profile_authority (platform operator). These are config / registry keys, not the per-market voteResolution resolver wallets.

GlobalConfig.dispute_resolution_authority (if still present in older docs) is legacy / unused for resolve_refute authorization; do not assume market resolver PDAs can sign it.

SendTransactionError: Unknown action 'undefined' (tooling)

Some @coral-xyz/anchor + @solana/web3.js combinations surface this when error deserialization paths disagree. It is not Vercora-specific. Stay on the peer dependency ranges listed in this package’s package.json, or align @solana/web3.js with the version Anchor’s release notes recommend for your Anchor minor line.


PredictionMarketClient API

Public properties: program, connection, globalConfig (PDA).

Global config (authority)

| Method | Purpose | | ----------------------------------- | ---------------------------------------------- | | initializeConfig(params) | One-time init; authority = connected wallet. | | updateConfig(params) | Update fees, treasury, authorities. | | addAllowedCollateralMint(mint) | Allowlist a collateral mint. | | removeAllowedCollateralMint(mint) | Remove from allowlist. |

secondaryAuthority in InitializeConfigParams / UpdateConfigParams: both map to the instruction data field secondary_authority on-chain (not only the secondaryAuthority account meta). Every updateConfig rewrites GlobalConfig.secondary_authority from params.secondaryAuthority. When you only change fees or treasury, pass the existing secondary pubkey you want to keep (from fetchGlobalConfig()); passing the primary key here collapses secondary into primary and removes the backup signer used for platform-only actions (e.g. voiding markets that still hold user liquidity).

Platforms & categories

| Method | Purpose | | ------------------------------ | ------------------------------------------------------------------------ | | registerPlatform(params) | Next platform_id from GlobalConfig; returns { platformId, sig }. | | createMarketCategory(params) | Next category id per PlatformRegistry (must match next_category_id). | | updateMarketCategory(params) | Rename / toggle active. |

Market creation

| Method | Purpose | | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | createMarket(creator, collateralMint, creatorFeeAccount, params) | Step 1: market + vault. | | initializeMarketResolverSlots(marketPda, params, opts?, parimutuelStateParams?) | Resolver PDAs; optional parimutuelStateParams appends initializeParimutuelState in the same tx. | | initializeMarketOutcomeSlots(marketPda, params) | MarketOutcome labels (complete-set & pari). | | initializeMarketMints(marketPda, marketId) | Mint 8 outcome SPLs (complete-set only). | | initializeParimutuelState(marketPda, params) | Standalone pari pool + penalty params (if not bundled with resolvers). | | createMarketFull(creator, collateralMint, creatorFeeAccount, resolverPubkeys, params) | Runs create → outcomes → resolvers (+ mints or pari state in one flow). | | updateParimutuelState(marketPda, params) | Creator updates penalty split, isEarlyWithdrawAllowed, maxWalletOutcomeInvestment, and isWalletOutcomeStakeExact (open pari pool). |

CreateMarketParams: requires platformId (> 0, BN) with a registered platform + upsertPlatformProfile (global config authority; resolver stake, challenge window, and refute bond are set on the profile, not per market). CreateMarketParams (pari-mutuel): optional isEarlyWithdrawAllowed (default true). Optional maxWalletOutcomeInvestment (BN, 0 = unlimited per-wallet per-outcome net stake cap in collateral base units). Optional isWalletOutcomeStakeExact: when true (parimutuel only), each stake must bring the wallet’s net on that outcome to exactly maxWalletOutcomeInvestment; requires maxWalletOutcomeInvestment > 0. Setting exact mode on a complete-set market fails with InvalidWalletOutcomeStakeExactConfig. Ignored for complete-set markets otherwise.

UpdateParimutuelStateParams: every call writes all fields, pass current values from fetchMarket / fetchParimutuelState for anything you are not changing. Includes earlyWithdrawPenaltyBps, penaltyKeptInPoolBps, isEarlyWithdrawAllowed, maxWalletOutcomeInvestment, isWalletOutcomeStakeExact. If isWalletOutcomeStakeExact is true, maxWalletOutcomeInvestment must be > 0 (same rule as create). Use this to tighten or relax per-wallet caps and to toggle exact vs ceiling mode after launch (market must still be open and not resolved/voided).

When isEarlyWithdrawAllowed is false, early parimutuelWithdraw fails with EarlyWithdrawNotAllowed until close or resolution (voided markets are exempt, full net-stake refunds, no penalty fees). Read fetchMarket(marketPda) for isEarlyWithdrawAllowed, maxWalletOutcomeInvestment, isWalletOutcomeStakeExact.

Complete-set trading

| Method | Purpose | | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | mintCompleteSet(user, marketPda, collateralMint, userCollateralAta, platformTreasury, creatorFeeAccount, params, opts?, tokenProgram?) | Mint full set; creates missing outcome ATAs. | | redeemCompleteSet(user, marketPda, collateralMint, userCollateralAta, params) | Burn full set for collateral. | | redeemWinning(user, marketPda, collateralMint, userCollateralAta, params) | After resolution, redeem winning outcome tokens. |

Parimutuel trading

| Method | Purpose | | --------------------------------------- | ------------------------------------------------------- | | parimutuelStake(marketPda, params) | Stake; signer is provider wallet (user on-chain). | | parimutuelWithdraw(marketPda, params) | Early exit (penalty may apply) or, after voidMarket, full net-stake refund (no fees). Early path errors if isEarlyWithdrawAllowed is false; void path ignores that flag. | | parimutuelClaim(marketPda, params) | Claim after resolution. |

Resolution & lifecycle

| Method | Purpose | | ----------------------------------------- | --------------------------------------------- | | voteResolution(marketPda, params) | Resolver vote; may escrow resolver stake to the market’s resolver stake vault (see platform policy). | | revokeResolutionVote(marketPda, params) | Clear vote before changing (blocked once a proposal exists). | | finalizeResolution(marketPda, params) | When M-of-N agree on one outcome, proposes that outcome, sets ResolutionState.proposed_outcome + proposal_ts; does not set Market.winning_outcome_index yet. | | confirmResolution(marketPda, params) | After the challenge window elapses with no open dispute, anyone can call this to set Market.winning_outcome_index (final approval / settlement). | | openRefute(marketPda, params) | During the challenge window: post a refute bond and name an alternative winning outcome (dispute). | | resolveRefute(marketPda, params) | params.accept === false: slash refute bond to treasury. accept === true: refund bond to refuter and set winning_outcome_index to disputed outcome (no confirmResolution). Signer (not market resolvers): global primary or secondary authority, or platform profileAuthority. Optional params.disputerCollateral defaults to the refuter’s ATA derived from ResolutionState.disputer. | | claimResolverStake(marketPda, params) | After resolution: matching vote → stake returned to resolver; otherwise stake slashed to platform treasury. | | fetchResolutionState(marketPda) | Read proposal, dispute, refute bond locked (ResolutionState). Challenge length and resolver stake size come from fetchPlatformProfile. | | closeMarketEarly(marketPda, params) | Creator / config authority before close_at. | | voidMarket(marketPda, params) | Void market. Creator cannot void while pari outcome pools (active stakes) or complete-set outstanding is non-zero, use a global config authority to cancel live markets. Cannot void while a refute dispute is open. Parimutuel: then parimutuelWithdraw for full net refunds. | | claimVoidedParimutuelSurplus(marketPda, params) | Global config authority only. After void, if pari outcome pools are zero but total_pool still holds early-exit pool-keep (no stakers left), sweeps that amount from the vault to the platform treasury. | | abandonMarket(marketPda, params) | Creator abandons empty market (reclaim rent). |

resolveRefute, account meta disputerCollateral (common pitfall): Anchor’s JS client expects camelCase keys in .accounts() / .accountsStrict(), matching the camelCase IDL. The JSON IDL field is disputer_collateral; if you pass disputer_collateral in the accounts object, Anchor ignores it and you get Account 'disputerCollateral' not provided. Use disputerCollateral. The account is always required for the instruction (see Disputer collateral on resolve_refute under IDL, account metas, and peer deps, dismiss does not fund it, but it must be present). For raw program.methods, supply every remaining account explicitly; as never on the accounts object is only to satisfy stale generated TypeScript, the chain still needs the meta. IDL drift (served JSON vs npm bundle) also produces “not provided”; prefer import { IDL } from '@vercora-protocol/sdk' or the same dist/idl/vercora.json as your installed package version.

resolveRefute examples

High-level client (derives refuter ATA when disputerCollateral is omitted):

await client.resolveRefute(marketPda, {
  marketId,
  accept: false, // dismiss: bond → platform treasury
});

// Optional: pass the refuter collateral ATA explicitly (same as derive from ResolutionState.disputer)
import { disputerCollateralAta } from "@vercora-protocol/sdk";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
const rs = await client.fetchResolutionState(marketPda);
const mint = (await client.fetchMarket(marketPda)).collateralMint;
// Use TOKEN_2022_PROGRAM_ID when the collateral mint uses Token-2022.
const ata = disputerCollateralAta(mint, rs.disputer as PublicKey, TOKEN_PROGRAM_ID);
await client.resolveRefute(marketPda, { marketId, accept: true, disputerCollateral: ata });

Raw Anchor (wallet = authority signer). Every account meta below uses camelCase names:

import { SystemProgram } from "@solana/web3.js";
import {
  disputerCollateralAta,
  deriveGlobalConfig,
  derivePlatformRegistry,
  deriveResolverStakeVault,
} from "@vercora-protocol/sdk";
import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";

const marketAccount = await program.account.market.fetch(marketPda);
// After openRefute: read disputer from chain (use your resolution PDA)
const rs = await program.account.resolutionState.fetch(resolutionStatePk);
const disputer = rs.disputer as PublicKey;
const disputerCollateral = disputerCollateralAta(collateralMint, disputer, TOKEN_PROGRAM_ID);
const treasuryAta = getAssociatedTokenAddressSync(
  collateralMint,
  platformTreasuryWallet,
  false,
  TOKEN_PROGRAM_ID,
  ASSOCIATED_TOKEN_PROGRAM_ID
);

await program.methods
  .resolveRefute({ marketId, accept: false })
  .accounts({
    authority: wallet.publicKey,
    market: marketPda,
    resolutionState: resolutionStatePk,
    resolverStakeVault: deriveResolverStakeVault(program.programId, marketPda),
    collateralMint,
    disputerCollateral, // required, camelCase key (`disputer_collateral` in JSON IDL is ignored)
    globalConfig: deriveGlobalConfig(program.programId),
    platformRegistry: derivePlatformRegistry(program.programId, marketAccount.platformId),
    platformTreasuryWallet,
    platformTreasuryAta: treasuryAta,
    collateralTokenProgram: TOKEN_PROGRAM_ID,
    systemProgram: SystemProgram.programId,
  } as any) // narrow only if your typegen omits `disputerCollateral`
  .rpc();

See Resolution approval flow below for the full sequence and SDK usage.

Discovery & reads

| Method | Purpose | | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | fetchGlobalConfig() | Global config account. | | fetchMarket(marketPda) | Market account. | | fetchAllMarkets(platformId?) | All markets (optional memcmp on platform_id); includes outcome labels. | | fetchMarketsByPlatform(platformId) | Same as fetchAllMarkets(platformId). | | fetchVoidedMarkets() | Market accounts with is_voided only (RPC memcmp + dataSize). | | fetchVoidedParimutuelSurplusCandidates() | Subset: voided pari, not resolved, sum(outcome_pools)=0, total_pool > 0 (claimable). | | fetchMarketsByCreator(creator) | Raw rows { pubkey, account }[], memcmp on creator. | | getUsersMarkets(creator, filters?) | ListedMarket[] for creator; optional platformId (RPC) + categoryId (client filter). | | fetchMarketOutcomeLabels(marketPda, outcomeCount) | Outcome labels. | | fetchVaultBalance(marketPda) | Vault balance (bigint base units). | | fetchOutcomeBalance(marketPda, user, outcomeIndex) | Single outcome token balance (complete-set). | | fetchAllOutcomeBalances(marketPda, user, outcomeCount) | All outcome balances for user. | | fetchMarketCategory(categoryPda) | One category. | | fetchAllMarketCategories() | All categories (sorted). | | fetchResolutionVote(marketPda, resolverIndex) | Vote PDA or null. | | fetchResolutionState(marketPda) | Proposal, dispute, refute bond (ResolutionState PDA). Use fetchPlatformProfile for challenge window and resolver stake. | | fetchMarketOutcomesSnapshot(marketPda, outcomeCount) | Decoded outcomes + tallies. | | fetchOutcomeTallyCounts(marketPda) | Quick tally array. | | fetchAllowedCollateralMints() | Allowlist mints. | | fetchUserProfile(wallet) | User profile or null. | | fetchPlatformProfile(platformId) | Platform profile or null. | | fetchParimutuelState(marketPda) | Pari pool state. | | fetchParimutuelPosition(marketPda, user, outcomeIndex) | Position or null. | | fetchParimutuelActiveStakesBatch(marketPda, user, outcomeCount) | Active stakes per outcome. | | computeParimutuelOdds(state, outcomeCount) | Implied probs + payout multipliers. | | fetchResolver(marketPda, index) | One resolver account. | | fetchAllResolvers(marketPda, numResolvers, { debug }) | Initialized resolver slots; optional debug: true logs PDA presence to the console. |

Profiles

| Method | Purpose | | ---------------------------------- | ------------------------------------------- | | upsertUserProfile(params) | Display name / URL; charges flat SOL fee per save when configured. | | closeUserProfile() | Close caller’s profile. | | verifyUserProfile(params) | Verifier marks verified. | | upsertPlatformProfile(params) | Platform profile (scoped by platform_id); global config primary or secondary authority must sign. | | closePlatformProfile(platformId) | Close profile. | | verifyPlatformProfile(params) | Verify platform profile. |


Examples

Global config (first deploy)

import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";

await client.initializeConfig({
  // System program / “none” placeholder, use a real secondary authority pubkey in production
  secondaryAuthority: new PublicKey("11111111111111111111111111111111"),
  depositPlatformFeeBps: 100,
  platformTreasuryWallet: treasuryPubkey,
  platformFeeLamports: new BN(357_000),
  parimutuelWithdrawPlatformFeeBps: 50,
});

Register platform & category

const { platformId } = await client.registerPlatform({
  profileAuthority: profileSignerPubkey,
});

await client.createMarketCategory({
  platformId,
  name: "Politics",
});

Create a market (manual steps, complete-set)

import BN from "bn.js";

const marketId = new BN(Date.now());
const { marketPda } = await client.createMarket(
  creatorPubkey,
  collateralMint,
  creatorFeeAta,
  {
    marketId,
    outcomeCount: 2,
    resolutionThreshold: 1,
    closeAt: new BN(Math.floor(Date.now() / 1000) + 86400),
    creatorFeeBps: 50,
    depositPlatformFeeBps: 0,
    numResolvers: 1,
    maxOutcomeInvestment: new BN(0),
    title: "Will it rain tomorrow?",
    marketType: "completeSet",
    platformId: new BN(1), // must be > 0, register_platform + upsert_platform_profile first
    categoryId: new BN(0),
  },
);

await client.initializeMarketOutcomeSlots(marketPda, {
  marketId,
  labels: ["Yes", "No"],
});
await client.initializeMarketResolverSlots(marketPda, {
  marketId,
  resolverPubkeys: [resolverPubkey],
});
await client.initializeMarketMints(marketPda, marketId);

Create market in one call (createMarketFull)

Complete-set (mints outcome tokens):

const marketPda = await client.createMarketFull(
  creatorPubkey,
  collateralMint,
  creatorFeeAta,
  [resolverPubkey],
  {
    marketId,
    outcomeCount: 2,
    resolutionThreshold: 1,
    closeAt: new BN(Math.floor(Date.now() / 1000) + 86400),
    creatorFeeBps: 50,
    depositPlatformFeeBps: 0,
    numResolvers: 1,
    maxOutcomeInvestment: new BN(0),
    title: "Two-outcome market",
    marketType: "completeSet",
    outcomeLabels: ["Yes", "No"],
    platformId: new BN(1),
    categoryId: new BN(0),
  },
);

Parimutuel (resolver tx also initializes pari state; optional parimutuelInit overrides defaults):

const marketPda = await client.createMarketFull(
  creatorPubkey,
  collateralMint,
  creatorFeeAta,
  [resolverPubkey],
  {
    marketId,
    outcomeCount: 2,
    resolutionThreshold: 1,
    closeAt: new BN(Math.floor(Date.now() / 1000) + 86400),
    creatorFeeBps: 50,
    depositPlatformFeeBps: 0,
    numResolvers: 1,
    maxOutcomeInvestment: new BN(0),
    title: "Pari pool",
    marketType: "parimutuel",
    outcomeLabels: ["A", "B"],
    platformId: new BN(1),
    categoryId: new BN(0),
    // Optional, default true. Set false to lock stakes until close/resolution (no early parimutuelWithdraw).
    isEarlyWithdrawAllowed: true,
    parimutuelInit: {
      earlyWithdrawPenaltyBps: 500,
      penaltyKeptInPoolBps: 8000,
    },
  },
);

Complete-set trading

const treasury = (await client.fetchGlobalConfig()).platformTreasury;

await client.mintCompleteSet(
  userPubkey,
  marketPda,
  collateralMint,
  userCollateralAta,
  treasury,
  creatorFeeAta,
  { marketId, amount: new BN(1_000_000) },
);

await client.redeemCompleteSet(
  userPubkey,
  marketPda,
  collateralMint,
  userCollateralAta,
  { marketId },
);

await client.redeemWinning(
  userPubkey,
  marketPda,
  collateralMint,
  userCollateralAta,
  {
    marketId,
    amount: winningAmountBn,
  },
);

Parimutuel trading

Signer is always the connected wallet (no user first argument):

await client.parimutuelStake(marketPda, {
  marketId,
  outcomeIndex: 0,
  amount: new BN(500_000),
});

await client.parimutuelWithdraw(marketPda, {
  marketId,
  outcomeIndex: 0,
  amount: new BN(500_000),
});
// If Market.isEarlyWithdrawAllowed is false (from create or updateParimutuelState), parimutuelWithdraw throws EarlyWithdrawNotAllowed, unless the market is voided (then full net stake back, no fees).

// After voidMarket on a pari pool, recover stakes (repeat per outcome / until active stake is 0):
// await client.voidMarket(marketPda, { marketId });
// await client.parimutuelWithdraw(marketPda, { marketId, outcomeIndex: 0, amount: userActiveStakeBn });

await client.parimutuelClaim(marketPda, {
  marketId,
  outcomeIndex: 0,
});

const state = await client.fetchParimutuelState(marketPda);
const odds = client.computeParimutuelOdds(state, 2);

Creator: update pari pool settings (updateParimutuelState)

Only the market creator can call this while the market is open (not resolved/voided). The instruction updates penalty bps, early-withdraw allowance, and per-wallet per-outcome stake rules in one transaction. Pass current values from fetchParimutuelState and fetchMarket for any field you are not changing:

const m = await client.fetchMarket(marketPda);
const pari = await client.fetchParimutuelState(marketPda);

await client.updateParimutuelState(marketPda, {
  marketId,
  earlyWithdrawPenaltyBps: pari.earlyWithdrawPenaltyBps,
  penaltyKeptInPoolBps: pari.penaltyKeptInPoolBps,
  isEarlyWithdrawAllowed: false, // block parimutuelWithdraw until close/resolution
  maxWalletOutcomeInvestment: m.maxWalletOutcomeInvestment, // keep existing cap
  isWalletOutcomeStakeExact: m.isWalletOutcomeStakeExact, // keep exact vs ceiling mode
});

// Re-enable early exit later (still pass wallet fields from `m`):
await client.updateParimutuelState(marketPda, {
  marketId,
  earlyWithdrawPenaltyBps: pari.earlyWithdrawPenaltyBps,
  penaltyKeptInPoolBps: pari.penaltyKeptInPoolBps,
  isEarlyWithdrawAllowed: true,
  maxWalletOutcomeInvestment: m.maxWalletOutcomeInvestment,
  isWalletOutcomeStakeExact: m.isWalletOutcomeStakeExact,
});

// Example: set a per-wallet cap (base units) and ceiling mode only:
await client.updateParimutuelState(marketPda, {
  marketId,
  earlyWithdrawPenaltyBps: pari.earlyWithdrawPenaltyBps,
  penaltyKeptInPoolBps: pari.penaltyKeptInPoolBps,
  isEarlyWithdrawAllowed: m.isEarlyWithdrawAllowed,
  maxWalletOutcomeInvestment: new BN(1_000_000),
  isWalletOutcomeStakeExact: false,
});

Staking errors when caps apply: WalletOutcomeInvestmentCapExceeded (6050), WalletOutcomeExactStakeMismatch (6052) when exact mode is on, InvalidWalletOutcomeStakeExactConfig (6051) if exact mode is enabled with a zero cap (create or update).

Resolution (minimal)

Resolvers vote, then finalizeResolution records a proposal. After the challenge window, if no dispute blocks confirmation, confirmResolution sets the final winner on the market account. See the full flow in the next section.

await client.voteResolution(marketPda, {
  marketId,
  resolverIndex: 0,
  outcomeIndex: 1,
});

// Records proposed_outcome + proposal_ts when M-of-N threshold is met (not final settlement yet).
await client.finalizeResolution(marketPda, { marketId });

// After challenge_window_secs (and no open refute blocking), anyone confirms final outcome:
await client.confirmResolution(marketPda, { marketId });

Discovery

// Every market (heavy on RPC)
const all = await client.fetchAllMarkets();

// By platform (memcmp)
const byPlatform = await client.fetchAllMarkets(new BN(1));

// Markets created by a wallet, full rows with labels + filters
const mine = await client.getUsersMarkets(creatorPubkey, {
  platformId: new BN(1), // optional RPC filter
  categoryId: 2, // optional; applied after decode
});

// Raw accounts only (no labels)
const raw = await client.fetchMarketsByCreator(creatorPubkey);

User profile

Requires sufficient SOL for the configured platform_fee_lamports on each save (plus rent on first create).

await client.upsertUserProfile({
  displayName: "Alice",
  url: "https://example.com",
});

const profile = await client.fetchUserProfile(wallet.publicKey);

Resolution approval flow (propose → challenge → confirm or refute-accept)

Settlement is two-phase: first a proposal is recorded on ResolutionState, then, after an optional challenge window, the market is approved into a final winner on the Market account.

  1. Platform prerequisite, create_market requires platform_id > 0: a registered PlatformRegistry, an initialized PlatformProfile (single resolver_stake, challenge_window_secs, refute_bond for that platform), and PDAs for resolution_state and resolver_stake_vault.

  2. Votes, Each assigned resolver calls voteResolution with their outcome. Votes update per-outcome tallies; resolver stake (from PlatformProfile.resolver_stake) transfers into the market’s resolver stake vault.

  3. Propose (finalizeResolution), When at least resolution_threshold resolvers agree on the same outcome, anyone may call finalizeResolution. On-chain this does not immediately set Market.winning_outcome_index. It sets ResolutionState.proposed_outcome, proposal_ts, and starts the challenge window (duration from PlatformProfile.challenge_window_secs).

  4. Challenge window, Until proposal_ts + challenge_window_secs elapses, a participant may openRefute with exactly platform_profile.refute_bond: lock that collateral and assert a different winning outcome. An authorized party (global primary or secondary authority, or platform profileAuthority) may resolveRefute: accept: false dismisses (bond → treasury), accept: true accepts (bond refunded, winning_outcome_index set immediately). While a dispute is active, confirmResolution is rejected. voidMarket is rejected while a dispute is open.

  5. Confirm (final approval), After proposal_ts + challenge_window, if there is no blocking dispute and the market was not already finalized by resolveRefute(accept: true), anyone calls confirmResolution. This writes Market.winning_outcome_index, the outcome is now final for redeemWinning / parimutuelClaim.

  6. Resolver stake, After settlement, resolvers use claimResolverStake: stake returns to the resolver if their vote matched the final winner; otherwise it is transferred to the platform treasury.

Reads

  • fetchResolutionState(marketPda), proposed_outcome, proposal_ts, dispute_active, refute_bond_amount, etc.
  • fetchPlatformProfile(platformId), challenge_window_secs, resolver_stake, refute_bond (challenge length and stake are not stored on ResolutionState).
  • fetchMarket(marketPda), winningOutcomeIndex is null until confirmResolution or resolveRefute({ accept: true }) succeeds.

UI / agent logic

  • Treat finalizeResolution as “proposal passed”; show countdown until confirmResolution is allowed.
  • Gate “resolved” UX on winningOutcomeIndex !== null, not on proposal alone.

Platforms and categories

On-chain, platform_id is u32 and category_id is u8 in CreateMarketArgs (not pubkeys).

  1. register_platform, assigns the next id from GlobalConfig.next_platform_id (starts at 1); PDA ["platform", platform_id le u32].
  2. upsert_platform_profile, metadata + resolution policy (resolver_stake, challenge_window_secs, refute_bond); signed by global config primary or secondary authority.
  3. create_market_category, category_id must equal PlatformRegistry.next_category_id; PDA ["market-category", platform_id, category_id].
  4. create_market, requires platform_id > 0, platform_registry, platform_profile, resolution_state, resolver_stake_vault. Use category_id == 0 for uncategorized markets (omit market_category account). Non-zero category_id requires an active market_category PDA for that platform.

Use derivePlatformRegistry, derivePlatformProfile, deriveMarketCategory, deriveResolutionState, deriveResolverStakeVault when building instructions manually.

Market types

Use marketType: 'parimutuel' or 'completeSet' at creation; the choice is permanent.

  • Parimutuel, pooled stakes, no outcome SPLs; good default for prediction apps. isEarlyWithdrawAllowed is set at create (default allowed) and can be changed by the creator via updateParimutuelState; when disabled, early parimutuelWithdraw is blocked until close or resolution. maxWalletOutcomeInvestment and isWalletOutcomeStakeExact are set at create and can be updated via updateParimutuelState (same instruction as penalty / early-withdraw edits). After voidMarket, participants parimutuelWithdraw full net stakes (no penalty / withdraw fees).
  • Complete-set, outcome SPL tokens; you still need external liquidity for single-leg trading.

PDA helpers

From @vercora-protocol/sdk:

import {
  PROGRAM_ID,
  deriveGlobalConfig,
  deriveAllowedMint,
  deriveMarket,
  deriveVault,
  deriveOutcomeMint,
  deriveAllOutcomeMints,
  deriveResolver,
  deriveAllResolvers,
  deriveResolutionVote,
  deriveMarketOutcome,
  deriveAllMarketOutcomes,
  deriveParimutuelState,
  deriveParimutuelPosition,
  deriveUserProfile,
  derivePlatformRegistry,
  derivePlatformProfile,
  deriveMarketCategory,
  deriveResolutionState,
  deriveResolverStakeVault,
  disputerCollateralAta,
  bnLike,
  bnToU32,
  bnToU8,
} from "@vercora-protocol/sdk";

const marketPda = deriveMarket(PROGRAM_ID, creatorPubkey, marketId);
const vaultPda = deriveVault(PROGRAM_ID, marketPda);

Offsets MARKET_ACCOUNT_CREATOR_MEMCMP_OFFSET, MARKET_ACCOUNT_PLATFORM_ID_MEMCMP_OFFSET, and MARKET_ACCOUNT_IS_VOIDED_MEMCMP_OFFSET are exported for custom getProgramAccounts filters. Use marketIsVoidedMemcmp(true) for the Borsh-encoded bool bytes at that offset. fetchVoidedMarkets / fetchVoidedParimutuelSurplusCandidates apply the voided filter so listing cost scales with voided markets only.

marketUi helpers

import {
  getMarketLifecycleStatus,
  formatTimeLeft,
  listedMarketFeedTag,
} from "@vercora-protocol/sdk";

const status = getMarketLifecycleStatus({
  isVoided: false,
  isClosedEarly: false,
  winningOutcomeIndex: null,
  closeAt: ts,
});

TypeScript types

import type {
  CreateMarketParams,
  UpdateParimutuelStateParams,
  ListedMarket,
  GetUsersMarketsFilters,
  MarketAccount,
  ParimutuelStateAccount,
  ParimutuelOdds,
  GlobalConfigAccount,
  UserProfileAccount,
} from "@vercora-protocol/sdk";

AI agent integration

Start from agents/skill.md in this package for a stable entrypoint; the full playbook is at https://vercora.xyz/agents/playbook.md (production), or docs/AI-AGENT-SDK-PLAYBOOK.md in the monorepo. Humans: the web app serves the same playbook at https://vercora.xyz/docs/agents with a Resolution at a glance summary (aligned with https://vercora.xyz/docs#resolution-flow).

  1. Bootstrap: ConnectionAnchorProviderProgram<Vercora> from IDLPredictionMarketClient.
  2. Branch: fetchMarketmarket.marketType (completeSet vs parimutuel).
  3. Flows:
    • Optional: registerPlatformcreateMarketCategorycreateMarket with ids.
    • Create: createMarketFull or createMarket + resolver / outcome / mint or pari init.
    • Complete-set: mintCompleteSetredeemCompleteSetredeemWinning.
    • Parimutuel: parimutuelStake → optional parimutuelWithdraw (if isEarlyWithdrawAllowed, or after voidMarket for full refunds) → parimutuelClaim; creator may change penalty split, early withdraw, and per-wallet caps (maxWalletOutcomeInvestment, isWalletOutcomeStakeExact) with updateParimutuelState (pass full params from fetchMarket / fetchParimutuelState when only changing some fields).
    • Resolution: voteResolutionfinalizeResolution (proposal) → challenge window → optional openRefute / resolveRefute (dismiss or accept) → confirmResolution if not already finalized by accept → claimResolverStake; then redeem/claim for traders.
  4. Discovery: fetchAllMarkets, getUsersMarkets, fetchMarketsByCreator, fetchVoidedMarkets, fetchVoidedParimutuelSurplusCandidates.
  5. Safety: verify signers, PDAs, ATAs, and RPC limits on getProgramAccounts.

Changelog

See CHANGELOG.md in the package (published to npm). Releases are generated with standard-version from Conventional Commits in app/sdk (e.g. feat:, fix:). After running a release script you can edit CHANGELOG.md or amend the release commit before npm publish.

Links