Skip to main content

Tutorial: Automated Market Resolution

This tutorial builds a complete resolution flow that monitors markets, resolves them when they expire, redeems all winnings, and claims all creator fees.

1. Find markets ready to resolve

import {
  ParimutuelClient,
  PMX_PROGRAM_ID,
  toRawUsdc,
  fromRawUsdc,
} from "@pmx/parimutuel-sdk";

// Find all markets that are active but past their resolution time
const expiredMarkets = await client.getExpiredUnresolved();

console.log(`Found ${expiredMarkets.length} markets ready to resolve:`);
for (const m of expiredMarkets) {
  const expiredAgo = Math.floor(Date.now() / 1000) - m.resolutionTime;
  console.log(`  #${m.id} "${m.title}" — expired ${Math.floor(expiredAgo / 60)} min ago`);
}

2. Resolve each market

for (const market of expiredMarkets) {
  // Determine the winning side based on your resolution logic
  const winningSide = await determineWinner(market);  // your logic here

  try {
    const tx = await client.buildResolveTx({
      marketId: market.id,
      wallet: wallet.publicKey,
      winningSide,
      usdcMint,
    });
    const sig = await signAndSend(tx);
    console.log(`✓ Resolved #${market.id}${winningSide} (${sig})`);
  } catch (err) {
    console.log(`✗ #${market.id}: ${err.message}`);
  }

  await new Promise(r => setTimeout(r, 500));
}

3. Redeem all winning positions

const positions = await client.getWalletPositions(wallet.publicKey);
const redeemable = positions.filter(p => p.needsRedemption);

for (const pos of redeemable) {
  try {
    const tx = await client.buildRedeemTx({
      marketId: pos.marketId,
      wallet: wallet.publicKey,
      side: pos.winningSide!,
      usdcMint,
    });
    await signAndSend(tx);
    console.log(`✓ Redeemed #${pos.marketId}: $${pos.redeemableValueUsdc.toFixed(2)}`);
  } catch (err) {
    console.log(`✗ #${pos.marketId}: ${err.message}`);
  }
  await new Promise(r => setTimeout(r, 500));
}

4. Claim all creator fees

const myMarkets = await client.listMarkets({
  creator: wallet.publicKey.toBase58(),
  status: "resolved",
});

for (const market of myMarkets) {
  const unclaimedFees = market.creatorFeesAccrued - market.creatorFeesClaimed;
  if (unclaimedFees <= 0) continue;

  try {
    const tx = await client.buildClaimFeesTx({
      marketId: market.id,
      wallet: wallet.publicKey,
      usdcMint,
    });
    await signAndSend(tx);
    console.log(`✓ Claimed $${fromRawUsdc(unclaimedFees).toFixed(4)} fees from #${market.id}`);
  } catch (err) {
    console.log(`✗ #${market.id}: ${err.message}`);
  }
  await new Promise(r => setTimeout(r, 500));
}

5. Run it on a loop

async function resolutionLoop() {
  while (true) {
    console.log(`[${new Date().toISOString()}] Resolution sweep...`);

    // Resolve expired markets
    const expired = await client.getExpiredUnresolved();
    for (const m of expired) {
      const winner = await determineWinner(m);
      try {
        const tx = await client.buildResolveTx({
          marketId: m.id, wallet: wallet.publicKey,
          winningSide: winner, usdcMint,
        });
        await signAndSend(tx);
        console.log(`Resolved #${m.id}${winner}`);
      } catch (err) {
        console.log(`Resolve #${m.id} failed: ${err.message}`);
      }
      await new Promise(r => setTimeout(r, 500));
    }

    // Redeem winnings
    const positions = await client.getWalletPositions(wallet.publicKey);
    for (const pos of positions.filter(p => p.needsRedemption)) {
      try {
        const tx = await client.buildRedeemTx({
          marketId: pos.marketId, wallet: wallet.publicKey,
          side: pos.winningSide!, usdcMint,
        });
        await signAndSend(tx);
      } catch {}
      await new Promise(r => setTimeout(r, 300));
    }

    // Claim fees
    const mine = await client.listMarkets({ creator: wallet.publicKey.toBase58() });
    for (const m of mine) {
      if (m.creatorFeesAccrued - m.creatorFeesClaimed <= 0) continue;
      try {
        const tx = await client.buildClaimFeesTx({
          marketId: m.id, wallet: wallet.publicKey, usdcMint,
        });
        await signAndSend(tx);
      } catch {}
      await new Promise(r => setTimeout(r, 300));
    }

    await new Promise(r => setTimeout(r, 5 * 60 * 1000));  // every 5 minutes
  }
}

Resolving a Market

After a market’s resolutionTime has passed, the creator (or the designated resolver) declares the winning outcome.
const tx = await client.buildResolveTx({
  marketId: 1,
  wallet: wallet.publicKey,
  winningSide: "YES",       // or "NO" (case-insensitive)
  usdcMint,
});

const sig = await signAndSend(tx);
console.log(`Market resolved: ${sig}`);
The resolve transaction:
  • Automatically creates the creator’s USDC ATA if it doesn’t exist (needed for the LP residual payout)
  • Auto-derives the creatorTokenAccount from the wallet and USDC mint — no need to pass it manually
  • Accepts any casing for winningSide (e.g. "yes", "YES", "Yes" all work)

Finding Markets to Resolve

const expired = await client.getExpiredUnresolved();

for (const market of expired) {
  console.log(`Market #${market.id} "${market.title}" expired — ready to resolve`);
}

Who Can Resolve?

resolutionTypeResolver
"creator" (default)The wallet that created the market
"resolver"The protocol’s configured resolver address
const market = await client.getMarket(1);
console.log(market.resolutionType); // "creator" or "resolver"

Validation

The SDK checks preconditions before building the transaction:
try {
  await client.buildResolveTx({ marketId: 999, wallet: wallet.publicKey, winningSide: "YES", usdcMint });
} catch (err) {
  // "Market 999 not found" or "Market is not active"
  console.log(err.message);
}
The on-chain program additionally requires that Clock.unix_timestamp >= market.resolutionTime. If you try to resolve too early, the Solana transaction will fail.

Redeeming Winnings

After resolution, holders of the winning outcome token exchange them for USDC.
const tx = await client.buildRedeemTx({
  marketId: 1,
  wallet: wallet.publicKey,
  side: "YES",       // redeem the side you hold
  usdcMint,
});

await signAndSend(tx);

How Payouts Work

Market TypePayout
AMMEach winning token = exactly $1 USDC
PoolTotal pool is split proportionally among winning token holders

Redeeming Both Sides

If you hold tokens on both sides (e.g. from arbitrage or market making), redeem each separately:
// Redeem winning side (gets USDC payout)
await signAndSend(await client.buildRedeemTx({
  marketId: 1, wallet: wallet.publicKey, side: "YES", usdcMint,
}));

// Redeem losing side (clears position, $0 payout)
await signAndSend(await client.buildRedeemTx({
  marketId: 1, wallet: wallet.publicKey, side: "NO", usdcMint,
}));

Smart Redemption with getWalletPositions

Instead of guessing which side to redeem, use getWalletPositions to find exactly what you hold:
const positions = await client.getWalletPositions(wallet.publicKey);

for (const pos of positions) {
  if (!pos.needsRedemption) continue;

  const tx = await client.buildRedeemTx({
    marketId: pos.marketId,
    wallet: wallet.publicKey,
    side: pos.winningSide!,
    usdcMint,
  });
  await signAndSend(tx);
  console.log(`Redeemed #${pos.marketId}: $${pos.redeemableValueUsdc.toFixed(2)}`);
}

Cancelled Markets

Cancelled markets also allow redemption — both sides get their pro-rata share of the vault returned:
const market = await client.getMarket(1);
if (market.status === "cancelled") {
  // Both YES and NO holders can redeem for a proportional refund
  if (yesBalance > 0) {
    await signAndSend(await client.buildRedeemTx({
      marketId: 1, wallet: wallet.publicKey, side: "YES", usdcMint,
    }));
  }
}

Claiming Creator Fees

The market creator earns 50% of all trade fees. These accrue with every buy and sell, and can be claimed at any time.
const tx = await client.buildClaimFeesTx({
  marketId: 1,
  wallet: wallet.publicKey,
  usdcMint,
});

await signAndSend(tx);

When to Claim

Fees can be claimed:
  • While the market is still active (partial claim)
  • After resolution
  • After cancellation
  • Multiple times (each claim collects only newly accrued fees)

Checking Accrued vs Claimed Fees

const market = await client.getMarket(1);
const unclaimed = market.creatorFeesAccrued - market.creatorFeesClaimed;

console.log(`Total accrued:  $${fromRawUsdc(market.creatorFeesAccrued).toFixed(4)}`);
console.log(`Already claimed: $${fromRawUsdc(market.creatorFeesClaimed).toFixed(4)}`);
console.log(`Unclaimed:       $${fromRawUsdc(unclaimed).toFixed(4)}`);

if (unclaimed > 0) {
  const tx = await client.buildClaimFeesTx({
    marketId: market.id, wallet: wallet.publicKey, usdcMint,
  });
  await signAndSend(tx);
  console.log("Fees claimed!");
}

Batch Fee Claiming

Claim fees from all your markets at once:
const myMarkets = await client.listMarkets({
  creator: wallet.publicKey.toBase58(),
});

let totalClaimed = 0;
for (const market of myMarkets) {
  const unclaimed = market.creatorFeesAccrued - market.creatorFeesClaimed;
  if (unclaimed <= 0) continue;

  try {
    const tx = await client.buildClaimFeesTx({
      marketId: market.id, wallet: wallet.publicKey, usdcMint,
    });
    await signAndSend(tx);
    totalClaimed += unclaimed;
    console.log(`✓ #${market.id}: $${fromRawUsdc(unclaimed).toFixed(4)}`);
  } catch (err) {
    console.log(`✗ #${market.id}: ${err.message}`);
  }
  await new Promise(r => setTimeout(r, 300));
}

console.log(`\nTotal claimed: $${fromRawUsdc(totalClaimed).toFixed(4)}`);

Complete Lifecycle Example

import {
  ParimutuelClient,
  PMX_PROGRAM_ID,
  toRawUsdc,
  fromRawUsdc,
} from "@pmx/parimutuel-sdk";

const client = new ParimutuelClient(connection, new PublicKey(PMX_PROGRAM_ID));

// 1. Create market (with optional token image)
const { createTx, initMintsTx, setMetadataTx, marketId } = await client.createMarketFull({
  wallet: wallet.publicKey,
  title: "ETH above $4,000",
  question: "Will ETH be above $4,000 at resolution?",
  yesTicker: "ETHUP",
  noTicker: "ETHDN",
  resolutionTime: Math.floor(Date.now() / 1000) + 1800,
  seedAmount: toRawUsdc(10),
  tradeFeeBps: 200,
  usdcMint,
  imageUrl: "https://s2.coinmarketcap.com/static/img/coins/128x128/1027.png",
});
await signAndSend(createTx);
await signAndSend(initMintsTx);
if (setMetadataTx) await signAndSend(setMetadataTx);
console.log(`Created market #${marketId}`);

// 2. Trade
const { tx: buyTx } = await client.buildBuyTx({
  marketId, wallet: wallet.publicKey, side: "YES",
  amount: toRawUsdc(5), usdcMint,
});
await signAndSend(buyTx);

// 3. Wait for resolution time... then resolve
const resolveTx = await client.buildResolveTx({
  marketId, wallet: wallet.publicKey, winningSide: "YES", usdcMint,
});
await signAndSend(resolveTx);

// 4. Redeem winnings
const redeemTx = await client.buildRedeemTx({
  marketId, wallet: wallet.publicKey, side: "YES", usdcMint,
});
await signAndSend(redeemTx);

// 5. Claim creator fees
const feesTx = await client.buildClaimFeesTx({
  marketId, wallet: wallet.publicKey, usdcMint,
});
await signAndSend(feesTx);

console.log("Full lifecycle complete!");