pnp-markets-solana
🎯Skillfrom pnp-protocol/solana-skill
Enables creating, trading, and settling permissionless prediction markets on Solana using SPL tokens for high-throughput, low-latency forecasting and market-based information discovery.
Installation
npx ts-node trade.ts --buy --market <address> --outcome YES --amount 10npx ts-node trade.ts --info --market <address>Skill Details
Create, trade, and settle permissionless prediction markets on Solana Mainnet with any SPL token collateral. Use when building prediction market infrastructure, running contests, crowdsourcing probability estimates, adding utility to tokens, creating social media markets (Twitter/YouTube), or tapping into true information finance via market-based forecasting.
Overview
# PNP Markets (Solana)
Create and manage prediction markets on Solana Mainnet with any SPL token collateral. Optimized for high-throughput, low-latency, and permissionless operation.
When to Use This Skill
Use this skill when the user wants to:
- Create prediction markets on Solana (V2 AMM or P2P)
- Trade on markets (buy/sell YES/NO outcome tokens)
- Settle markets as an oracle after the trading period ends
- Redeem winning positions after settlement
- Create social media markets (Twitter engagement, YouTube views)
- Use custom tokens as prediction market collateral
- Build info finance infrastructure with autonomous market resolution
Prerequisites
Before running any scripts, ensure you have:
- Solana Wallet: Base58-encoded private key with SOL for fees (~0.05 SOL minimum)
- USDC Tokens: Mainnet USDC (
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) for market liquidity - RPC Endpoint: Mainnet RPC URL (public or dedicated like Helius/QuickNode)
> [!CAUTION]
> MAINNET ONLY: All scripts are configured for Solana Mainnet. Never use devnet. RPC defaults to https://api.mainnet-beta.solana.com.
> [!IMPORTANT]
> ALWAYS USE USDC: All markets must be created with USDC as collateral. Mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (6 decimals).
```bash
# Install dependencies
cd scripts && npm install
# Set environment variables
export PRIVATE_KEY=
export RPC_URL=https://api.mainnet-beta.solana.com # or dedicated RPC
```
---
Market Creation Flow
Solana prediction markets support two primary architectures:
- V2 AMM Markets (Default): Use Automated Market Makers with virtual liquidity. Best for publicly traded markets.
- P2P Markets: Direct peer-to-peer betting where the creator takes one side. Best for private or specific bets.
Standard V2 AMM Market Creation
Function: client.market.createMarket(params)
Creates a standard prediction market using PNP's global oracle for resolution.
```typescript
import 'dotenv/config';
import { PublicKey } from '@solana/web3.js';
import { PNPClient } from 'pnp-sdk';
const RPC_URL = process.env.RPC_URL || 'https://api.mainnet-beta.solana.com';
const PRIVATE_KEY = process.env.PRIVATE_KEY || '';
async function main() {
const secretKey = PNPClient.parseSecretKey(PRIVATE_KEY);
const client = new PNPClient(RPC_URL, secretKey);
const result = await client.market.createMarket({
question: 'Will Bitcoin reach $100K by end of 2025?',
initialLiquidity: 1_000_000n, // 1 USDC (6 decimals)
endTime: BigInt(Math.floor(Date.now() / 1000) + 30 24 60 * 60),
baseMint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
});
console.log('Market Address:', result.market.toBase58());
}
main().catch(console.error);
```
Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| question | string | ✅ | The prediction question |
| initialLiquidity | bigint | ✅ | Initial liquidity in raw units |
| endTime | bigint | ✅ | Unix timestamp when trading ends |
| baseMint | PublicKey | ✅ | Collateral token mint address |
Returns: { signature: string, market: PublicKey }
---
Custom Oracle Markets (For AI Agents)
Custom oracles let AI agents become their own market resolvers—bypassing PNP's global oracle entirely. This is the key primitive for autonomous agent-driven prediction markets.
Why Custom Oracles?
| Use Case | Description |
|----------|-------------|
| AI Agents | Build autonomous agents that create and resolve markets based on real-world data feeds |
| Private Forecasting | Run internal prediction markets with proprietary resolution logic |
| Custom Data Sources | Integrate any API—sports feeds, weather data, on-chain events, social metrics |
Custom Oracle Functions
#### createMarketWithCustomOracle
When to use: Call this when you want your AI agent to have full control over market resolution. Your agent's wallet becomes the oracle—only it can settle the market.
Why: Bypasses PNP's global AI oracle. Essential for autonomous agents that need to resolve markets based on their own data sources or logic.
```typescript
await client.createMarketWithCustomOracle({
question: 'Will BTC hit $150K by Dec 2026?',
initialLiquidity: 10_000_000n, // 10 USDC (6 decimals)
endTime: BigInt(Math.floor(Date.now() / 1000) + 30 24 60 * 60),
collateralMint: USDC_MINT,
settlerAddress: ORACLE_WALLET, // Your agent's wallet
yesOddsBps: 5000, // Optional: 50/50 odds (range: 100-9900)
});
```
#### setMarketResolvable
When to use: Call this immediately after creating a custom oracle market—within 15 minutes.
Why: Markets are created in a frozen state. This activates trading. If not called within 15 minutes, the market is permanently untradeable.
```typescript
await client.setMarketResolvable(marketAddress, true);
```
#### settleMarket
When to use: Call this after the market's end time when you (as the oracle) know the outcome.
Why: Only the designated oracle can settle. This determines which side (YES/NO) wins and allows winners to redeem.
```typescript
await client.settleMarket({
market: marketAddress,
yesWinner: true, // false if NO wins
});
```
> Critical: After market creation, you have a 15-minute buffer window to call setMarketResolvable(true). If not activated, the market is permanently frozen.
---
Social Media Markets
PNP provides native support for creating prediction markets linked to Twitter/X and YouTube content—ideal for social AI agents.
Twitter Markets
#### createMarketTwitter
When to use: Call this when creating a prediction market about tweet engagement (replies, likes, retweets, views).
Why: Automatically links the market to a specific tweet. PNP's oracle can track tweet metrics for resolution.
```typescript
await client.createMarketTwitter({
question: 'Will this tweet cross 5000 replies?',
tweetUrl: 'https://x.com/username/status/123456789',
initialLiquidity: 1_000_000n, // 1 USDC
endTime: BigInt(Math.floor(Date.now() / 1000) + 30 24 60 * 60),
collateralTokenMint: USDC_MINT,
});
```
Supported URL formats:
https://x.com/username/status/123456789https://twitter.com/username/status/123456789
YouTube Markets
#### createMarketYoutube
When to use: Call this when creating a prediction market about video performance (views, likes, subscribers).
Why: Automatically links the market to a specific YouTube video. PNP's oracle can track video metrics for resolution.
```typescript
await client.createMarketYoutube({
question: 'Will this video cross 1B views?',
youtubeUrl: 'https://youtu.be/VIDEO_ID',
initialLiquidity: 1_000_000n, // 1 USDC
endTime: BigInt(Math.floor(Date.now() / 1000) + 30 24 60 * 60),
collateralTokenMint: USDC_MINT,
});
```
Supported URL formats:
https://youtu.be/VIDEO_IDhttps://youtube.com/watch?v=VIDEO_IDhttps://www.youtube.com/watch?v=VIDEO_ID
Social Market Use Cases
- Engagement Prediction: Bet on whether a tweet will go viral
- Content Performance: Predict video view counts
- Influencer Markets: Create markets around creator milestones
- Trend Detection: Use market prices as signals for trending content
---
Programmatic SDK Usage (For AI Agents)
This section provides the complete programmatic reference for AI agents to discover, analyze, trade, and settle markets without running CLI commands.
1. Client Initialization
```typescript
import { PNPClient } from 'pnp-sdk';
import { PublicKey } from '@solana/web3.js';
// Initialize with RPC and Private Key (Uint8Array or Base58)
const client = new PNPClient(
'https://api.mainnet-beta.solana.com',
Uint8Array.from([/ 64-byte array /])
);
> [!TIP]
> Use const secretKey = PNPClient.parseSecretKey(process.env.PRIVATE_KEY) to automatically handle both Base58 strings and Uint8Array formats.
```
2. Market Discovery
AI agents should use these methods to find active markets before trading or analyzing.
| Method | Purpose |
|--------|---------|
| client.fetchMarketAddresses() | Returns an array of V2 AMM market addresses from the proxy server. |
| client.fetchV3MarketAddresses() | Returns an array of P2P (V3) market addresses. |
| client.fetchMarkets() | Fetches on-chain data for all markets (higher latency, use sparingly). |
| client.fetchMarket(pubkey) | Fetches detailed on-chain data for a specific market. |
```typescript
// Example: Find all P2P markets
const p2pAddresses = await client.fetchV3MarketAddresses();
console.log(Found ${p2pAddresses.length} P2P markets.);
```
3. Market Intelligence & Data Fetching
AI agents need comprehensive market data for decision-making. Use these SDK methods for analysis.
#### Price & Multiplier Analysis (Read-Only)
Function: client.getMarketPriceV2(marketAddress)
Returns real-time AMM pricing data with implied probabilities and payout multipliers.
```typescript
const priceData = await client.getMarketPriceV2(marketAddress);
// Response structure:
// {
// yesPrice: 0.65, // YES token price (0-1 range)
// noPrice: 0.35, // NO token price (0-1 range)
// yesMultiplier: 1.54, // Payout ratio if YES wins
// noMultiplier: 2.85, // Payout ratio if NO wins
// marketReserves: 1000.0, // Total USDC locked
// yesTokenSupply: 650.0, // YES tokens minted
// noTokenSupply: 350.0 // NO tokens minted
// }
// Calculate implied probabilities
const yesProbability = priceData.yesPrice * 100; // e.g., 65%
const noProbability = priceData.noPrice * 100; // e.g., 35%
// Calculate potential returns
const betAmount = 10; // $10 USDC
const yesProfit = betAmount * (priceData.yesMultiplier - 1); // e.g., $5.40
const noProfit = betAmount * (priceData.noMultiplier - 1); // e.g., $18.50
```
#### Comprehensive Market Data Analysis
Utility: Use the market-data.ts pattern to fetch complete market intelligence:
```typescript
async function getComprehensiveMarketInfo(marketId: string) {
const client = new PNPClient(RPC_URL); // Read-only
// 1. Fetch on-chain market account data
const { account: marketData } = await client.fetchMarket(new PublicKey(marketId));
// Market account fields:
// - question: string
// - creator: PublicKey
// - resolvable: boolean (can it be settled?)
// - resolved: boolean (is it settled?)
// - end_time: bigint (Unix timestamp)
// - winning_token_id: 'yes' | 'no' | null
// - yes_token_mint: PublicKey
// - no_token_mint: PublicKey
// - collateral_token: PublicKey
// 2. Fetch settlement criteria from proxy server
const criteria = await client.fetchSettlementCriteria(marketId);
// Settlement criteria fields:
// - resolvable: boolean
// - winning_token_id: 'yes' | 'no' | undefined
// - reasoning: string (AI explanation)
// - category: string (e.g., 'coin-predictions')
// - resolution_plan: Array<{step, action, fallback}>
// - resolution_sources: string[] (API endpoints)
// 3. Fetch settlement decision from proxy
const settlementData = await client.fetchSettlementData(marketId);
// Settlement data fields:
// - answer: 'YES' | 'NO'
// - reasoning: string (detailed explanation)
// 4. Determine market state
let state: string;
if (marketData.resolved) {
state = 'RESOLVED';
} else if (!marketData.resolvable) {
state = 'NOT RESOLVABLE';
} else if (Date.now() > Number(marketData.end_time) * 1000) {
state = 'ENDED (pending resolution)';
} else {
state = 'ACTIVE';
}
return { marketData, criteria, settlementData, state };
}
```
Key SDK Functions for Data Fetching:
| Function | Returns | Use Case |
|----------|---------|----------|
| client.fetchMarket(pubkey) | Market account data | Get on-chain market state |
| client.getMarketPriceV2(address) | Price & multiplier data | Calculate trading outcomes |
| client.fetchSettlementCriteria(address) | AI resolution criteria | Check if market can be settled |
| client.fetchSettlementData(address) | AI settlement decision | Get suggested outcome |
| client.trading.getMarketInfo(pubkey) | Extended market info | Get detailed trading data |
4. Market Creation Lifecycle
Markets must follow a strict lifecycle, especially Custom Oracle markets.
#### V2 / Social Markets
client.createMarketTwitter(params)client.createMarketYoutube(params)client.market.createMarket(params)(Standard V2)
#### Custom Oracle (Agent-Controlled)
Step 1: Create
```typescript
const result = await client.createMarketWithCustomOracle({
question: '...',
initialLiquidity: 10_000_000n,
endTime: daysFromNow(7),
collateralMint: USDC_MINT,
settlerAddress: AGENT_WALLET, // You are the oracle
});
```
Step 2: Activate (Critical 15-Minute Buffer)
> [!WARNING]
> You must call this within 15 minutes of creation, or the market is permanently frozen.
```typescript
await client.setMarketResolvable(result.market, true);
```
#### P2P (V3) Markets
client.createP2PMarketGeneral(params)client.createP2PMarketTwitter(params)client.createP2PMarketYoutube(params)
5. Trading Operations
AI agents can execute trades programmatically on both AMM and P2P markets.
#### V2 AMM Trading Functions
Buy Tokens: client.trading.buyTokensUsdc(params)
Purchases YES or NO outcome tokens using USDC collateral. AMM automatically calculates token price.
```typescript
const result = await client.trading.buyTokensUsdc({
market: marketPubkey,
buyYesToken: true, // true = YES, false = NO
amountUsdc: 10, // Amount in USDC units (not raw)
});
// Returns: { signature: string, tokensReceived?: bigint }
console.log('Transaction:', result.signature);
```
Sell Tokens (Method 1): client.trading.sellTokensUsdc(params)
```typescript
const result = await client.trading.sellTokensUsdc({
market: marketPubkey,
sellYesToken: true, // true = YES, false = NO
tokenAmount: 5_000_000_000_000_000_000n, // Raw units (18 decimals)
});
// Returns: { signature: string, usdcReceived?: bigint }
```
Sell Tokens (Method 2): client.trading.burnDecisionTokensDerived(params)
Lower-level burn function used by scripts. Converts raw token amount to collateral.
```typescript
const amountRaw = BigInt(Math.floor(sellAmount * 1e18)); // 18 decimals
const result = await client.trading.burnDecisionTokensDerived({
market: marketPubkey,
amount: amountRaw,
burnYesToken: true, // true = YES, false = NO
});
// Returns: { signature: string }
```
#### P2P Trading Function
Trade P2P Market: client.tradeP2PMarket(params)
Takes the opposite position from the market creator. If creator bet YES, you bet NO (and vice versa).
```typescript
const result = await client.tradeP2PMarket({
market: marketPubkey,
side: 'yes', // 'yes' or 'no'
amount: 1_000_000n, // Raw collateral units (e.g., 1 USDC = 1_000_000)
});
// Returns: { signature: string, market: string }
```
Key Differences:
- V2 AMM: Uses
amountUsdcin human-readable units (e.g.,10for 10 USDC) - P2P: Uses
amountin raw base units (e.g.,1_000_000nfor 1 USDC with 6 decimals) - Token Decimals: Outcome tokens use 18 decimals, collateral varies (USDC = 6, SOL = 9)
6. Settlement & Proxy Integration
Settlement Functions (Oracle-only operations)
Settle Market: client.settleMarket(params)
Resolves a market by declaring the winning outcome. Only callable by the designated oracle.
```typescript
const result = await client.settleMarket({
market: marketPubkey,
yesWinner: true, // true = YES wins, false = NO wins
});
// Returns: { signature: string }
console.log('Market settled:', result.signature);
```
Prerequisites:
- Current time must be past market's
end_time - Oracle wallet must match the market's designated oracle
- For custom oracle markets: Must have called
setMarketResolvable(true)beforehand
Automated Settlement Flow:
```typescript
// Fetch AI-suggested resolution from proxy
const criteria = await client.getSettlementCriteria(marketPubkey);
if (criteria.resolvable) {
await client.settleMarket({
market: marketPubkey,
yesWinner: criteria.winning_token_id === 'yes',
});
}
```
Check Market Settlement Status:
```typescript
const { account: market } = await client.fetchMarket(marketPubkey);
if (market.resolved) {
console.log('Winner:', market.winning_token_id); // 'yes' | 'no' | null
} else {
const now = Math.floor(Date.now() / 1000);
const ended = now > Number(market.end_time);
console.log('Can settle:', ended && market.resolvable);
}
```
7. Redemption & Refunds
Redeem Winnings: client.redeemPosition(marketPubkey)
Converts winning outcome tokens back to collateral after market settlement.
```typescript
// First check if market is settled
const { account: market } = await client.fetchMarket(marketPubkey);
if (!market.resolved) {
throw new Error('Market not settled yet');
}
// Redeem winning position
const result = await client.redeemPosition(marketPubkey);
// Returns: { signature: string }
console.log('Redeemed:', result.signature);
```
Claim Creator Refund:
For markets that can't be resolved, creators can reclaim their initial liquidity.
V2/Custom Oracle Markets: client.claimMarketRefund(marketPubkey)
```typescript
const result = await client.claimMarketRefund(marketPubkey);
// Returns: { signature: string }
```
P2P Markets: client.claimP2PMarketRefund(marketPubkey)
```typescript
const result = await client.claimP2PMarketRefund(marketPubkey);
// Returns: { signature: string }
```
Refund Eligibility:
- Market must be unresolvable (checked via proxy or on-chain flag)
- Caller must be the market creator
- For custom oracle markets: 15-minute buffer period must have expired without activation
- V2/Custom Oracle:
await client.claimMarketRefund(marketPubkey) - P2P Markets:
await client.claimP2PMarketRefund(marketPubkey)
---
Core Data Types
AI agents should understand these response structures:
MarketAccount (MarketType)
account.resolved: boolean - Is trading over?account.resolvable: boolean - Can it be settled now?account.end_time: bigint - Unix timestamp.account.winning_token_id: string ('yes'|'no'|null) - The winner.
PriceData (MarketPriceV2Data)
yesPrice: number (0-1)noPrice: number (0-1)yesMultiplier: number (e.g., 2.0)marketReserves: number (Total USDC locked)
---
Common Token Mints (Ready-to-Use)
```typescript
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const USDT_MINT = new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB');
const WSOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
```
Helper Functions
```typescript
// Liquidity (USDC 6 decimals)
const usdcToRaw = (amount: number) => BigInt(amount * 1_000_000);
// End Time
const daysFromNow = (days: number) => BigInt(Math.floor(Date.now() / 1000) + days * 86400);
```
---
Supported Collateral
Markets can be created with any SPL token. Pre-configured aliases:
| Alias | Mint Address | Decimals |
|-------|--------------|----------|
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 |
| SOL | So11111111111111111111111111111111111111112 | 9 |
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | 6 |
Script Quick Reference
All scripts are located in scripts/ and use dotenv/config to load environment variables from .env.
| Script | Purpose | Run Command |
|--------|---------|-------------|
| create-market.ts | Create standard V2 AMM market | tsx scripts/create-market.ts |
| create-market-x.ts | Create Twitter/X engagement market | tsx scripts/create-market-x.ts |
| create-market-yt.ts | Create YouTube views market | tsx scripts/create-market-yt.ts |
| create-market-p2p.ts | Create P2P betting market | tsx scripts/create-market-p2p.ts |
| create-market-custom.ts | Create custom oracle market | tsx scripts/create-market-custom.ts |
| market-data.ts | Fetch market info & settlement data | tsx scripts/market-data.ts |
| trade.ts | Buy/sell outcome tokens | tsx scripts/trade.ts --buy --market |
| settle.ts | Resolve market as oracle | tsx scripts/settle.ts --market |
| redeem.ts | Claim winnings after settlement | tsx scripts/redeem.ts --market |
Environment Setup
Create a .env file in the root directory:
```bash
PRIVATE_KEY="your_base58_private_key_here"
RPC_URL="https://api.mainnet-beta.solana.com"
```
> [!IMPORTANT]
> All scripts require the PRIVATE_KEY environment variable. Use PNPClient.parseSecretKey() to handle both Base58 strings and Uint8Arrays.
---
Common Errors & Recovery
| Error | Cause | Solution |
|-------|-------|----------|
| 0x1770 (InvalidLiquidity) | Liquidity below minimum | Use ≥1 USDC or ≥0.05 SOL |
| Insufficient funds for rent | Not enough SOL for account creation | Ensure ≥0.05 SOL in wallet |
| Blockhash not found | Transaction expired before confirmation | Retry; use faster RPC (Helius/QuickNode) |
| 15-minute buffer expired | Didn't call setMarketResolvable in time | Market is permanently frozen; create new one |
| TokenAccountNotFound | Missing Associated Token Account | SDK auto-creates, but ensure SOL for rent |
| Oracle mismatch | Wrong wallet trying to settle | Only designated oracle can settle |
| Market not ended | Trying to settle before end time | Wait until market's endTime passes |
---
SDK Quick Reference
| Method | When to Use |
|--------|-------------|
| createMarketWithCustomOracle({...}) | When your agent needs to be the oracle and control resolution |
| setMarketResolvable(market, true) | Immediately after creating custom oracle market (within 15 min) |
| settleMarket({market, yesWinner}) | After market ends, to declare the winner as oracle |
| createMarketTwitter({...}) | When creating markets about tweet engagement |
| createMarketYoutube({...}) | When creating markets about video performance |
| market.createMarket({...}) | For standard V2 AMM markets (PNP oracle resolves) |
| createP2PMarketGeneral({...}) | When taking a position on one side of the bet |
| trading.buyTokensUsdc({...}) | To buy YES/NO outcome tokens |
| redeemPosition(market) | After settlement, to convert winning tokens to collateral |
| getMarketPriceV2(market) | To fetch current prices (read-only, no wallet needed) |
| fetchMarket(market) | To get on-chain market data |
---
Resources
- Mainnet Explorer: [Solscan](https://solscan.io)
- PNP SDK Docs: [https://docs.pnp.exchange/pnp-sdk](https://docs.pnp.exchange/pnp-sdk)
- SDK Documentation: [references/api-reference.md](references/api-reference.md)
- Advanced Examples: [references/examples.md](references/examples.md)