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?
resolutionType | Resolver |
|---|
"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 Type | Payout |
|---|
| AMM | Each winning token = exactly $1 USDC |
| Pool | Total 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!");