# Oracle Markets — LLM / agent reference (@pm-amm/sdk) > Machine-readable, complete API surface for the SDK behind Oracle Markets > (`@pm-amm/sdk`, the pm-AMM engine's Solana client). Optimized > for coding agents (Claude, Codex, …) to generate correct integration code > WITHOUT reading the source. Every public symbol, signature, and type is here. > **Hackathon / first contact: start with `HACKATHON.md` in the repo** (devnet funds, a runnable quickstart, gotchas). Narrative quickstart: README.md. **Most complete reference** (on-chain instructions + SDK + math + end-to-end recipes; Bet Vault v2 is in `doc/bet-vault-v2.md`): `doc/api-reference.md` in the repo, also served at `/api-reference.md`. Human visual guide: the app's `/docs` route. Oracle Markets runs on a faithful implementation of the Paradigm pm-AMM (Moallemi & Robinson, 2024): prediction-market AMM with time-decaying liquidity `L_eff = L0*sqrt(T-t)` (uniform LVR), continuous LP yield (dC_t), and permissionless Commitment Vaults (binary + multi-outcome). Outcomes are resolved by each market's authority (admin-signed) — there is no on-chain oracle. `` below = the Oracle Markets deployment you are reading this from. ## Conventions an agent MUST follow - **Amounts**: `send.*` helpers take USDC in HUMAN units (e.g. `100` = 100 USDC; converted to 6dp internally). EXCEPTIONS that take RAW 6dp micro-units: `send.swap(amountInMicro, minOutputMicro)`, `send.redeemPair(amountMicro)`. `ix.*` builders take raw `BN | number | bigint` for all amounts (no human conversion). - **Side** = `"yes" | "no"`. **SwapDirection** = `"usdcToYes" | "usdcToNo" | "yesToUsdc" | "noToUsdc" | "yesToNo" | "noToYes"`. - **Swap fee (2%)**: every USDC↔YES/NO swap charges a 2% protocol fee on the USDC leg (off the input on a buy, off the curve output on a sell); pure YES↔NO is free. Split 50% protocol DAO / 50% market creator. The SDK handles the fee accounts for you — `ix.swap`/`send.swap` derive `daoUsdc` + (optional) `creatorUsdc` automatically. Consequence: a buy of N USDC trades ~`0.98·N` on the curve. `math.estimateSwapOutput` is **fee-unaware** → apply the 2% yourself when sizing `minOutput`. - **PDAs**: pure `derive*(programId, ...)` functions take programId FIRST. The client exposes bound versions without programId (`client.yesMint(market)`). - **Address override**: the client overrides the bundled IDL `address` with your `programId` once; the bundled value is irrelevant. - **Compute budget + ATAs**: `send.*` auto-prepends the correct CU budget and creates missing associated token accounts. `ix.*` does NEITHER — you compose those yourself. - **Signing**: `send.*` and `flows.*` require a provider (use `fromProvider`). `ix.*`, reads, and PDA helpers work read-only. - **Peer deps** (consumer provides a single copy): `@solana/web3.js@^1.98`, `@anchor-lang/core@^1.0`, `@solana/spl-token@^0.4`. Browser needs a `Buffer` polyfill. `BN` is imported from `@anchor-lang/core`. - **Node ESM caveat**: `@anchor-lang/core` is CJS; importing the SDK's ESM build under raw Node fails on the named `BN` import. Use a bundler (Next/Vite) or the CJS build (`require`). ## Deployments (same PROGRAM_ID on both clusters) ``` PROGRAM_ID = GV1FMGHRYBjQLaghE5fnGuYCuCcpdt3GD5xEX3TwN16y // devnet + mainnet-beta // devnet USDC_MINT = 3WQ8hCqTNwjrh8WzE2XyoZoUrd1miPcwWfMkmFPUMEWZ // mock USDC, 6 decimals // mainnet-beta (LIVE) USDC_MINT = EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v // real USDC (Circle), 6 decimals ``` Pass the cluster's USDC mint as `collateralMint` to the client; nothing else changes. ### Devnet test funds (no real value) ``` // 1,000 mUSDC per claim (1 per wallet per hour); creates the token account. // During events it also drips devnet SOL to low-balance wallets ("sol" field). POST /api/faucet {"wallet":""} -> 200 {"ok":true,"signature","amount":1000,"sol":number} | 400 bad wallet | 429 {"error"} + Retry-After | 503 mainnet/unconfigured // Devnet SOL otherwise: `solana airdrop 1 --url devnet` or https://faucet.solana.com (rate-limited per IP). ``` Reference app (devnet): — market page `/market/` (the numeric id returned by `createMarket`, not the PDA). Runnable Node example (keypair → faucet → create market → swap): `examples/quickstart-devnet/index.cjs`. Burner wallets for an app's end users (browser keypair auto-funded via the faucet with `role: "player"` → 1,000 mUSDC + 0.02 SOL; the faucet allows CORS): `examples/burner-wallet/` (`burner.ts` + React `useBurnerWallet()`); a player wallet trades/LPs/claims, creating markets (~0.04 SOL) is for the builder's own wallet. ### Signing client in Node (CJS) ```js const { AnchorProvider, Wallet } = require("@anchor-lang/core"); const provider = new AnchorProvider(connection, new Wallet(keypair), { commitment: "confirmed" }); const client = PmAmmClient.fromProvider(provider, PROGRAM_ID, USDC_MINT); ``` ## Install ``` pnpm add @pm-amm/sdk @solana/web3.js @anchor-lang/core @solana/spl-token ``` ## Entry: PmAmmClient ```ts import { PmAmmClient } from "@pm-amm/sdk"; interface PmAmmConfig { connection: Connection; programId: PublicKey; // required collateralMint: PublicKey; // required (USDC mint) metaplexProgramId?: PublicKey; // default: Metaplex Token Metadata provider?: AnchorProvider; // present => send.* / flows.* enabled commitment?: Commitment; // default "confirmed" } new PmAmmClient(cfg: PmAmmConfig): PmAmmClient PmAmmClient.readOnly(connection, programId, collateralMint, opts?): PmAmmClient PmAmmClient.fromProvider(provider, programId, collateralMint, opts?): PmAmmClient // instance fields client.connection: Connection client.programId / collateralMint / metaplexProgramId: PublicKey client.program: Program // raw Anchor program (typed) client.accounts: ProgramAccountNamespace client.ix: { ...34 builders } // see "Instruction builders" client.send: { ...send helpers } // see "Send helpers" client.flows: { ...orchestrations } // see "Flows" // instance methods client.requireProvider(): AnchorProvider // throws if read-only client.walletPubkey(): PublicKey // signer pubkey client.sendIxs(ixs: TransactionInstruction[], signers?: Signer[]): Promise ``` ## PDA helpers (bound; all return PublicKey) ```ts client.marketPda(id: number|bigint) client.yesMint(market) / client.noMint(market) / client.marketVault(market) client.lpPosition(market, owner) client.groupPda(id) client.vaultPda(id) / client.vaultCollateral(vault) / client.commitPosition(vault, owner) client.vaultGroupPda(id) / client.vaultGroupCollateral(vault) / client.commitGroupPosition(vault, owner) client.metadataPda(mint) ``` Pure equivalents (exported, take programId first): `deriveMarketPda`, `deriveYesMint`, `deriveNoMint`, `deriveMarketVault`, `deriveLpPosition`, `deriveGroupPda`, `deriveVaultPda`, `deriveVaultCollateralPda`, `deriveCommitPositionPda`, `deriveVaultGroupPda`, `deriveVaultGroupCollateralPda`, `deriveCommitGroupPositionPda`, `deriveMetadataPda(mint, metaplexProgramId?)`. ## Reads (typed, decoded; `*All` return `{publicKey, account}[]`) ```ts client.fetchMarket(pda): Promise client.fetchAllMarkets(dataSize?: number) // pass 443 to filter to current Market layout client.fetchGroup(pda): Promise client.fetchAllGroups(dataSize?) client.fetchLpPosition(market, owner): Promise client.fetchVault(pda): Promise client.fetchAllVaults(dataSize?) client.fetchCommitPosition(vault, owner): Promise client.fetchVaultGroup(pda): Promise client.fetchAllVaultGroups(dataSize?) client.fetchCommitGroupPosition(vault, owner): Promise ``` ## Instruction builders — client.ix.* (each async => TransactionInstruction) Binary market: ```ts client.ix.initializeMarket({ authority: PublicKey, marketId: number|bigint, endTs: number|bigint, name: string, initialPriceBps: number }) client.ix.depositLiquidity({ signer: PublicKey, market: PublicKey, amount: BN|number|bigint }) client.ix.swap({ signer, market, direction: SwapDirection, amountIn: BN|number|bigint, minOutput: BN|number|bigint, creatorAuthority?: PublicKey }) // 2% fee auto-handled; creatorAuthority is fetched from the market if omitted (pass it to skip the RPC) client.ix.withdrawLiquidity({ signer, market, sharesToBurn: BN|number|bigint }) // shares are Q64.64 client.ix.accrue({ market }) client.ix.claimLpResiduals({ signer, market }) client.ix.redeemPair({ signer, market, amount }) client.ix.suggestLZero({ market, budgetUsdc, sigmaBps }) // view-only, emits LZeroSuggestion event client.ix.resolveMarket({ signer, market, side: Side }) // authority-only, after expiry client.ix.claimWinnings({ signer, market, amount?: BN|number|bigint }) // amount ignored on-chain ``` Group (multi-outcome categorical): ```ts client.ix.initializeGroupMarket({ authority, groupId, endTs, name, legCount: number }) // legCount 2..32 client.ix.attachLegToGroup({ authority, group: PublicKey, market: PublicKey, legIndex: number }) client.ix.resolveGroup({ authority, group, winningLeg: number }) client.ix.resolveGroupLeg({ group, market, legIndex }) // permissionless cascade client.ix.cancelGroupMarket({ authority, group }) ``` Commitment vault (binary, Sprint 22): ```ts client.ix.initializeVault({ authority, vaultId, name, commitDurationSecs: number|bigint, marketDurationSecs: number|bigint, minTotal: BN|number|bigint }) client.ix.vaultCommit({ signer, vault, side: Side, amount }) client.ix.launchVaultMarket({ payer, vault, marketId }) // permissionless after commit_end_ts client.ix.claimCommitter({ signer, vault, market }) client.ix.refundCommit({ signer, vault }) ``` Commitment vault group (multi-outcome, Sprint 23): ```ts client.ix.initializeVaultGroup({ authority, vaultId, name, legNames: string[], commitDurationSecs, marketDurationSecs, minTotal }) // legNames 2..8 client.ix.vaultCommitGroup({ signer, vault, legIndex: number, amount }) client.ix.launchVaultGroupMarket({ payer, vault, groupId }) // step 1: wrapper group client.ix.launchVaultGroupLeg({ payer, vault, group, legIndex, marketId }) // step 2: once per leg client.ix.claimCommitterGroup({ signer, vault, group, market, legIndex }) client.ix.refundCommitGroup({ signer, vault }) ``` ## Send helpers — client.send.* (require provider; auto CU + ATA; return signature unless noted) ```ts // binary market client.send.createMarket(input: CreateMarketInput): Promise<{ marketId: number; marketPda: string; signature: string }> client.send.swap(market, direction: SwapDirection, amountInMicro: number|BN, minOutputMicro: number|BN): Promise client.send.depositLiquidity(market, amountUsdc: number): Promise client.send.withdrawLiquidity(market, sharesToBurn: BN|number): Promise client.send.redeemPair(market, amountMicro: number|BN): Promise client.send.claimWinnings(market): Promise client.send.claimLpResiduals(market): Promise client.send.resolveMarket(market, side: Side): Promise client.send.accrue(market): Promise // binary vault client.send.createVault(input: CreateVaultInput): Promise<{ vaultId: number; vaultPda: string; signature: string }> client.send.vaultCommit(vault, side: Side, amountUsdc: number): Promise client.send.launchVaultMarket(vault): Promise<{ marketId: number; marketPda: string; signature: string }> client.send.claimCommitter(vault, market): Promise client.send.refundCommit(vault): Promise // bet vault v2 (winner takes the pot; amounts in HUMAN units of the vault's collateral) client.send.createBetVault(input: CreateBetVaultInput): Promise<{ vaultId: number; betVaultPda: string; signature: string }> client.send.betCommit(betVault, side: Side, amount: number): Promise // before commitEndTs; allowlist enforced client.send.launchBetVault(betVault): Promise<{ marketId: number; marketPda: string; signature: string }> // authority or resolver only, after commitEndTs client.send.resolveBetVault(betVault, side: Side): Promise // resolver only, after marketEndTs client.send.settleBetVault(betVault): Promise // permissionless, after resolve client.send.voidBetVault(betVault): Promise // permissionless, after marketEndTs + voidGraceSecs if never resolved client.send.claimBet(betVault): Promise // after settle (winners pro-rata) or void (everyone refunded pro-rata) client.send.refundBet(betVault): Promise // 1:1 refund if the vault can never launch (< minTotal, one-sided, odds outside 1..99%) // multi-outcome vault client.send.createVaultGroup(input: CreateVaultGroupInput): Promise<{ vaultId: number; vaultPda: string; signature: string }> client.send.vaultCommitGroup(vault, legIndex: number, amountUsdc: number): Promise client.send.launchVaultGroupMarket(vault): Promise<{ groupId: number; groupPda: string; signature: string }> client.send.launchVaultGroupLeg(vault, group, legIndex: number): Promise<{ marketId: number; marketPda: string; signature: string }> client.send.claimCommitterGroup(vault, group, market, legIndex: number): Promise client.send.refundCommitGroup(vault): Promise ``` ## Flows — client.flows.* (require provider; multi-transaction) ```ts client.flows.createGroup( input: GroupCreateInput, onProgress?: (label: string, step: number) => void, ): Promise<{ groupId: number; groupPda: string; legMarketIds: number[] }> client.flows.resolveGroup(args: { group: PublicKey; legMarkets: (PublicKey | null)[]; // index = leg index; null = unattached slot winningLeg: number | null; // null = cancel (all legs resolve NO) onProgress?: (label: string, i: number, total: number) => void; }): Promise client.flows.findClaimableLegs( legMarkets: (PublicKey | null)[], owner?: PublicKey, // defaults to wallet ): Promise // ClaimableLeg = { market: PublicKey; legIndex: number; yesBalance: number; noBalance: number } client.flows.claimAllGroupWinnings(args: { legMarkets: (PublicKey | null)[]; onProgress?: (label: string, i: number, total: number) => void; }): Promise<{ legsClaimed: number }> ``` ## Input types ```ts interface CreateMarketInput { name: string; durationSecs: number; initialPriceBps?: number; depositUsdc?: number } interface CreateVaultInput { name: string; commitDurationSecs: number; marketDurationSecs: number; minTotalUsdc: number } interface CreateVaultGroupInput { name: string; legNames: string[]; commitDurationSecs: number; marketDurationSecs: number; minTotalUsdc: number } // legNames 2..8 interface CreateBetVaultInput { name: string; commitDurationSecs: number; marketDurationSecs: number; minTotal: number; // HUMAN units of collateral lpBps?: number; // share of the pot deposited as AMM liquidity; default 5000; capped at launch to the favourite's stake share resolver?: PublicKey; // default: creator allowlist?: PublicKey[]; // up to 8 keys allowed to commit; empty = open voidGraceSecs?: number; // 300 .. 30 days; default 7 days collateralMint?: PublicKey; // default: client's collateral } interface GroupCreateInput { name: string; legNames: string[]; durationSecs: number; budgetPerLegUsdc: number } // legNames 2..32 ``` ## Account types (decoded; BN for u64/u128/i64, PublicKey for pubkeys; `name` is number[]) ```ts MarketAccount { authority, marketId, collateralMint, yesMint, noMint, vault, startTs, endTs, lZero, reserveYes, reserveNo, lastAccrualTs, cumYesPerShare, cumNoPerShare, totalYesDistributed, totalNoDistributed, totalLpShares, resolved: boolean, winningSide: number, bump, name, initialPriceBps: number, group: PublicKey } GroupMarketAccount { authority, groupId, startTs, endTs, legCount: number, legs: PublicKey[], resolved, winningLeg: number, bump, name, totalSeededBps: number } LpPositionAccount { owner, market, shares, collateralDeposited, yesPerShareCheckpoint, noPerShareCheckpoint, bump } CommitmentVaultAccount { authority, vaultId, collateralMint, name, commitEndTs, marketEndTs, yesTotal, noTotal, commitCount: number, minTotal, launched: boolean, winningPriceBps: number, market, lpPosition, bump } CommitPositionAccount { vault, owner, yesAmount, noAmount, claimed: boolean, bump } CommitmentVaultGroupAccount { authority, vaultId, collateralMint, name, legCount: number, legNames: number[][], legTotals: BN[], commitEndTs, marketEndTs, commitCount: number, minTotal, groupMarketInitialized: boolean, legsLaunched: number, groupMarket, bump } CommitPositionGroupAccount { vault, owner, legAmounts: BN[], claimed: boolean, bump } ``` Bet vault (Sprint 25): `client.betVaultPda(id)`, `client.betPosition(vault, owner)`, `client.fetchBetVault(pda)`, `client.fetchAllBetVaults()`, `client.fetchBetPosition(vault, owner)`. ```ts BetVaultAccount { authority, resolver, vaultId, collateralMint, name, commitEndTs, marketEndTs, yesTotal, noTotal, commitCount, minTotal, lpBps, effectiveLpBps, allowlistLen, allowlist, launched, priceBps /* launch odds = yesTotal/total */, market, winningSide /* 0 | 1=YES | 2=NO */, settled, ... } BetPositionAccount { vault, owner, yesAmount, noAmount, bump } ``` The vault PDA is `market.authority` of the launched market: it collects the creator half of the fee (into the pot) and only `resolveBetVault` can resolve that market. ## Current price (how the reference app computes it) ```ts const lEff = i80f48ToNumber(m.lZero) * Math.sqrt(Math.max(m.endTs.toNumber() - nowSecs, 1)); const price = priceFromReserves(i80f48ToNumber(m.reserveYes), i80f48ToNumber(m.reserveNo), lEff); // minOutput for a USDC->YES buy of `amountIn` raw units, 1% slippage (fee-aware): const { output } = estimateSwapOutput(x, y, lEff, amountIn * 0.98, "yes"); const minOut = Math.floor(output * 0.99); ``` ## Limits enforced on-chain Market duration >= 300 s · vault commit window >= 60 s · min commit 1 USDC · names 1..64 bytes (leg names 1..32) · initialPriceBps 100..9900 (0 = 50/50) · group legs 2..32 · vault-group legs 2..8. Resolution is admin-only (the creator / resolver), no oracle. ## Math — `@pm-amm/sdk/math` (pure, no chain deps; float64, display/estimation) ```ts i80f48ToNumber(raw): number // Q64.64 BN/bigint -> number (precision-safe) phi(z) / capitalPhi(z) / phiInv(p): number // normal pdf / cdf / inverse-cdf priceFromReserves(x, y, lEff): number // P = Phi((y-x)/lEff) poolValue(price, lEff): number // V(P) = lEff * phi(Phi^-1(P)) estimateSwapOutput(reserveYes, reserveNo, lEff, amountIn, side: "yes"|"no"): { output, priceAfter, priceImpact } expectedDailyLvr(price, lEff, remainingSecs): number expectedTerminalWealth(deposited): number simulateLpDeposit(amount, price, lEff, totalShares, remainingSecs, lZero): { newShares, poolSharePct, newPoolValue, estDailyYield } lpPositionPnl(shares, totalShares, deposited, price, lEff, cumYesPerShare?, cumNoPerShare?, yesCheckpoint?, noCheckpoint?, walletYes?, walletNo?, reserveYes?, reserveNo?): LpPnlResult formatUsdc(lamports): string // 6dp -> "1,234.56" formatPrice(price): string // 0.512 -> "51.2%" formatTimeRemaining(endTs): string // group helpers expectedLegSeedBps(legCount) / expectedLegSeedPrice(legCount) sumProbabilities(prices: number[]) / groupDriftPct(prices: number[]) legBudgetAllocations(legCount, totalLamports): number[] expectedDriftAfterBet(legReserveYes, legReserveNo, legLEff, betUsd, side?): number ``` ## Errors & constants ```ts import { mapAnchorError, PM_AMM_ERRORS } from "@pm-amm/sdk"; mapAnchorError(err: unknown): { code: number; name: string; msg?: string } | null import { METAPLEX_PROGRAM_ID, SYSVAR_RENT_PUBKEY, CU, SEEDS, PROTOCOL_DAO, SWAP_FEE_BPS, solscanTxUrl, solscanAccountUrl } from "@pm-amm/sdk"; // CU = { DEFAULT: 400000, HEAVY: 1400000 } // SWAP_FEE_BPS = 200 (2%); PROTOCOL_DAO = 4qXyczAr5DuBVaHUwmZT5Xt6hgQ6RwqBYcFGtrv8QEph (50% of the fee; creator gets the other 50%) // solscanTxUrl(sig, cluster?) / solscanAccountUrl(addr, cluster?) cluster default "devnet" ``` ## Recipes ```ts // Read-only: list markets + price const client = PmAmmClient.readOnly(conn, PROGRAM_ID, USDC_MINT); const markets = await client.fetchAllMarkets(443); // Create + bootstrap a market (one tx) await client.send.createMarket({ name: "Will X ship?", durationSecs: 86_400, initialPriceBps: 5000, depositUsdc: 100 }); // Buy YES with 10 USDC, 1% slippage (compute minOut off-chain via math) await client.send.swap(market, "usdcToYes", 10_000_000, minOutMicro); // Crowd-bootstrapped binary market const { vaultPda } = await client.send.createVault({ name, commitDurationSecs: 3600, marketDurationSecs: 86_400, minTotalUsdc: 50 }); await client.send.vaultCommit(vault, "yes", 25); const { marketPda } = await client.send.launchVaultMarket(vault); await client.send.claimCommitter(vault, marketPda); // Compose a custom tx from raw builders (no auto CU/ATA) import { ComputeBudgetProgram, Transaction } from "@solana/web3.js"; const ix = await client.ix.swap({ signer, market, direction: "usdcToYes", amountIn: 10_000_000, minOutput: minOut }); const tx = new Transaction().add(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), ix); await provider.sendAndConfirm(tx, []); ```