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
| Step | Endpoint | Who | When |
|---|
| 1. Create market | POST /v2/markets/create | Creator | Once — response includes marketId |
| 2. Init mints | POST /v2/markets/:id/init-mints | Creator | Immediately after create |
| 3. Confirm (optional) | POST /v2/markets/confirm | Creator | After init mints |
| 4a. Quote | GET /v2/markets/:id/quote | Anyone | Before every trade |
| 4b. Buy | POST /v2/markets/:id/buy | Anyone | While market is active |
| 4c. Sell | POST /v2/markets/:id/sell | Anyone | While active (AMM only) |
| 4d. Positions | GET /v2/positions/:wallet | Anyone | Anytime |
| 5. Resolve | POST /v2/markets/:id/resolve | Creator | After resolution time |
| 7. Redeem | POST /v2/markets/:id/redeem | Token holders | After resolution |
| 8. Claim fees | POST /v2/markets/:id/claim-fees | Creator | Anytime |
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.