Skip to main content
This guide walks through every step of a prediction market’s lifecycle using the PMX REST API. Every write endpoint returns an unsigned Solana transaction — your app deserializes it, signs locally, and submits to the network.

Prerequisites

  • A Solana wallet with SOL (for tx fees) and USDC (for seeding / trading)
  • @solana/web3.js and bs58 installed
import { Connection, Transaction, Keypair } from "@solana/web3.js";
import bs58 from "bs58";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY));
const API = "https://api.pmx.trade";

async function signAndSend(base64Tx) {
  const tx = Transaction.from(Buffer.from(base64Tx, "base64"));
  tx.sign(wallet);
  const sig = await connection.sendRawTransaction(tx.serialize());
  await connection.confirmTransaction(sig, "confirmed");
  return sig;
}

Step 1 — Create the Market

Build and submit the createMarket transaction. This registers the market on-chain with your wallet as the creator (authority, fee recipient, and resolver).
const res = await fetch(`${API}/v2/markets/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    title: "BTC above $95,000",
    description: "Will Bitcoin be above $95,000 at 3:00 PM UTC?",
    yesTicker: "BTCUP",
    noTicker: "BTCDN",
    resolutionTime: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
    seedAmount: 10_000_000, // 10 USDC (raw units)
    tradeFeeBps: 200, // 2% fee — required
  }),
});
seedAmount is in raw USDC units (6 decimals). 10 USDC = 10_000_000. Minimum is 1_000_000 ($1).

Step 2 — Get the New Market ID

The marketId is returned directly in the create response — no extra API call needed.
const { data } = await res.json();
const marketId = data.marketId; // already in the create response
const createSig = await signAndSend(data.transaction);

Step 3 — Initialize Token Mints

This step is required. Without it, the market’s YES/NO token mints are uninitialized and no one can trade.
const mintsRes = await fetch(`${API}/v2/markets/${marketId}/init-mints`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
  }),
});
const mintsData = await mintsRes.json();
const mintsSig = await signAndSend(mintsData.data.transaction);
If you skip this step, the market will appear on-chain but all buy/sell transactions will fail with AccountNotInitialized. Always call init-mints immediately after create.

Step 4 — Confirm to Backend (Optional)

If you want the market to appear on the PMX website, persist it to the backend database:
await fetch(`${API}/v2/markets/confirm`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    onchain_market_id: marketId,
    title: "BTC above $95,000",
    description: "Will Bitcoin be above $95,000 at 3:00 PM UTC?",
    creator_address: wallet.publicKey.toBase58(),
    market_mode: "amm",
    yes_ticker: "BTCUP",
    no_ticker: "BTCDN",
    resolution_time: Math.floor(Date.now() / 1000) + 3600,
    seed_amount: 10_000_000,
    trade_fee_bps: 200,
    create_tx_sig: createSig,
  }),
});

Step 5 — Trade (Buy & Sell)

Once mints are initialized, anyone can trade.

Get a quote first

const quoteRes = await fetch(
  `${API}/v2/markets/${marketId}/quote?side=YES&amount=5000000&action=buy`
);
const quote = await quoteRes.json();
// quote.data → { effectivePrice, outputAmount, priceImpactBps, tradeFee, preOdds, postOdds }

Buy tokens

const buyRes = await fetch(`${API}/v2/markets/${marketId}/buy`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    side: "YES", // or use the market's ticker e.g. "BTCUP"
    amount: 5_000_000, // 5 USDC
  }),
});
const buyData = await buyRes.json();
const buySig = await signAndSend(buyData.data.transaction);

Sell tokens (AMM only)

const sellRes = await fetch(`${API}/v2/markets/${marketId}/sell`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    side: "YES",
    amount: 3_000_000, // 3 tokens to sell
  }),
});
const sellData = await sellRes.json();
const sellSig = await signAndSend(sellData.data.transaction);

Check positions

const posRes = await fetch(`${API}/v2/positions/${wallet.publicKey.toBase58()}`);
const positions = await posRes.json();
// positions.data → [{ marketId, marketTitle, yesBalance, noBalance, yesBalanceHuman, noBalanceHuman,
//                     hasWinningTokens, redeemableValueUsdc, needsRedemption, isResolved, winningSide, ... }]

Step 6 — Resolve the Market

After resolutionTime has passed, the market creator resolves it by declaring the winning side.
const resolveRes = await fetch(`${API}/v2/markets/${marketId}/resolve`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    winningSide: "YES", // or "NO"
  }),
});
const resolveData = await resolveRes.json();
const resolveSig = await signAndSend(resolveData.data.transaction);
Resolution requires Clock.unix_timestamp >= market.resolutionTime and both YES and NO mints must have non-zero supply (at least one trade on each side).

Step 7 — Redeem Winnings

After resolution, holders of the winning outcome token redeem them for USDC.
  • AMM markets: Each winning token = exactly $1 USDC.
  • Pool markets: USDC is split proportionally among all winning token holders.
const redeemRes = await fetch(`${API}/v2/markets/${marketId}/redeem`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
    side: "YES", // redeem the side you hold
  }),
});
const redeemData = await redeemRes.json();
const redeemSig = await signAndSend(redeemData.data.transaction);

Step 8 — Claim Creator Fees

The market creator earns 50% of all trade fees. These can be claimed at any time — while active, after resolution, or even after cancellation.
const feeRes = await fetch(`${API}/v2/markets/${marketId}/claim-fees`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    wallet: wallet.publicKey.toBase58(),
  }),
});
const feeData = await feeRes.json();
const feeSig = await signAndSend(feeData.data.transaction);

Complete Flow Summary

StepEndpointWhoWhen
1. Create marketPOST /v2/markets/createCreatorOnce — response includes marketId
2. Init mintsPOST /v2/markets/:id/init-mintsCreatorImmediately after create
3. Confirm (optional)POST /v2/markets/confirmCreatorAfter init mints
4a. QuoteGET /v2/markets/:id/quoteAnyoneBefore every trade
4b. BuyPOST /v2/markets/:id/buyAnyoneWhile market is active
4c. SellPOST /v2/markets/:id/sellAnyoneWhile active (AMM only)
4d. PositionsGET /v2/positions/:walletAnyoneAnytime
5. ResolvePOST /v2/markets/:id/resolveCreatorAfter resolution time
7. RedeemPOST /v2/markets/:id/redeemToken holdersAfter resolution
8. Claim feesPOST /v2/markets/:id/claim-feesCreatorAnytime
For a working reference implementation of this entire flow as an autonomous agent, see the simpler_agent/ directory in the PMX repository. It runs a Creator/Resolver agent and a Trader agent in a continuous loop.