@vercora-protocol/sdk
v0.0.19
Published
TypeScript SDK for the Vercora protocol on Solana
Readme
@vercora-protocol/sdk
TypeScript SDK for the Vercora prediction market protocol on Solana (Anchor).
Install
npm install @vercora-protocol/sdk
# or
yarn add @vercora-protocol/sdkPeer dependencies (install alongside):
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.jsQuick 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). |
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. |
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).
| addAllowedCollateralMint(mint) | Allowlist a collateral mint. |
| removeAllowedCollateralMint(mint) | Remove from allowlist. |
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 and isEarlyWithdrawAllowed (open pari pool). |
CreateMarketParams (pari-mutuel): optional isEarlyWithdrawAllowed (default true). UpdateParimutuelStateParams requires isEarlyWithdrawAllowed together with penalty fields — creators use updateParimutuelState to change early-withdraw allowance after launch (same instruction as penalty edits; market must still be open and not resolved/voided). When false, early parimutuelWithdraw fails with EarlyWithdrawNotAllowed until close or resolution (voided markets are exempt — full net-stake refunds, no penalty fees). Ignored for complete-set markets. Read fetchMarket(marketPda).isEarlyWithdrawAllowed.
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. |
| revokeResolutionVote(marketPda, params) | Clear vote before changing. |
| finalizeResolution(marketPda, params) | Anyone, once threshold met. |
| 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. 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). |
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. |
| 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) | Initialized resolver slots. |
Profiles
| Method | Purpose |
| ---------------------------------- | ------------------------------------------- |
| upsertUserProfile(params) | Display name / URL. |
| closeUserProfile() | Close caller’s profile. |
| verifyUserProfile(params) | Verifier marks verified. |
| upsertPlatformProfile(params) | Platform profile (scoped by platform_id). |
| 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(0),
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"],
},
);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"],
// 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 penalty split and early withdraw (updateParimutuelState)
Only the market creator can call this while the market is open (not resolved/voided). Pass current penalty bps from fetchParimutuelState and isEarlyWithdrawAllowed from fetchMarket when you only want to toggle early exit:
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
});
// Re-enable early exit later:
await client.updateParimutuelState(marketPda, {
marketId,
earlyWithdrawPenaltyBps: pari.earlyWithdrawPenaltyBps,
penaltyKeptInPoolBps: pari.penaltyKeptInPoolBps,
isEarlyWithdrawAllowed: true,
});Resolution
await client.voteResolution(marketPda, {
marketId,
resolverIndex: 0,
outcomeIndex: 1,
});
await client.finalizeResolution(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
await client.upsertUserProfile({
displayName: "Alice",
url: "https://example.com",
});
const profile = await client.fetchUserProfile(wallet.publicKey);Platforms and categories
On-chain, platform_id is u32 and category_id is u8 in CreateMarketArgs (not pubkeys).
register_platform— assigns the next id fromGlobalConfig.next_platform_id; PDA["platform", platform_id le u32].create_market_category—category_idmust equalPlatformRegistry.next_category_idbefore bump; PDA["market-category", platform_id, category_id].create_market— ifplatform_id == 0thencategory_idmust be0. Ifplatform_id > 0, passplatform_registry; ifcategory_id > 0, passmarket_category.
Use derivePlatformRegistry, derivePlatformProfile, deriveMarketCategory 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.
isEarlyWithdrawAllowedis set at create (default allowed) and can be changed by the creator viaupdateParimutuelState; when disabled, earlyparimutuelWithdrawis blocked until close or resolution. AftervoidMarket, participantsparimutuelWithdrawfull 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,
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.
- Bootstrap:
Connection→AnchorProvider→Program<Vercora>fromIDL→PredictionMarketClient. - Branch:
fetchMarket→market.marketType(completeSetvsparimutuel). - Flows:
- Optional:
registerPlatform→createMarketCategory→createMarketwith ids. - Create:
createMarketFullorcreateMarket+ resolver / outcome / mint or pari init. - Complete-set:
mintCompleteSet→redeemCompleteSet→redeemWinning. - Parimutuel:
parimutuelStake→ optionalparimutuelWithdraw(ifisEarlyWithdrawAllowed, or aftervoidMarketfor full refunds) →parimutuelClaim; creator may toggle early withdraw withupdateParimutuelState(includes penalty fields). - Resolution:
voteResolution→finalizeResolution.
- Optional:
- Discovery:
fetchAllMarkets,getUsersMarkets,fetchMarketsByCreator,fetchVoidedMarkets,fetchVoidedParimutuelSurplusCandidates. - 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.
Releasing (maintainers)
From prediction_market/app/sdk:
- Commit SDK work with conventional messages so the next bump has sensible notes.
npm run release:check— confirmspackage.jsonversion matches git tagv<version>(follow the script’s hints if not).npm run release:patch,release:minor, orrelease:major— bumps version, updatesCHANGELOG.md, creates a release commit, and tagsvX.Y.Z.- Adjust
CHANGELOG.mdor message if needed (git commit --amendor a follow-up commit), thennpm publish.
Preview without writing: npx standard-version --dry-run --release-as patch (only after release:check passes).
