Skip to main content

Trade API Documentation

Overview

The Trade API provides functionality to list markets, get trade quotes, and execute trades on prediction markets.

Example

import { createPublicClient, createWalletClient, http, type Hex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
})

const TRADE_API_URL = '<https://api.foresight.now/trade>'

// 1. Get available markets
const marketsResponse = await fetch(`${TRADE_API_URL}/markets`)
const markets = await marketsResponse.json()
console.log('Available markets:', markets)

// 2. Get a quote for a trade
const marketAddress = '0x...' // Market contract address
const amount = '100000000' // 100 USDC in base units (6 decimals)
const outcome = 1 // 1 = YES, 0 = NO
const tradeType = 'Buy'

const quoteResponse = await fetch(`${TRADE_API_URL}/quote`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    market: marketAddress,
    amount: amount,
    outcome: outcome,
    type: tradeType
  })
})
const quote = await quoteResponse.json()
console.log('Trade quote:', quote)

// 3. Check and handle token approval (required for buying)
const account = '0x...' // User wallet address
const tokenAddress = '0x...' // USDC token address
const spenderAddress = '0x...' // Market maker contract address

// Check current allowance
const allowance = await publicClient.readContract({
  address: tokenAddress,
  abi: erc20ABI,
  functionName: 'allowance',
  args: [account, spenderAddress]
})

// If allowance is insufficient, approve the spender
if (allowance < BigInt(amount)) {
  const approveHash = await walletClient.writeContract({
    address: tokenAddress,
    abi: erc20ABI,
    functionName: 'approve',
    args: [spenderAddress, BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')]
  })

  // Wait for approval confirmation
  await publicClient.waitForTransactionReceipt({ hash: approveHash })
  console.log('Token approval confirmed')
}

// 4. Create a trade intent and get execution data
//    POST /trade requires a platform API key.
const executeResponse = await fetch(`${TRADE_API_URL}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
  },
  body: JSON.stringify({
    market: marketAddress,
    amount: amount,
    outcome: outcome,
    type: tradeType,
    account: account
  })
})
const tradeData = await executeResponse.json()
console.log('Trade execution data:', tradeData)
// tradeData: { intentId, marketType: 'AMM' | 'CLOB', market, outcome, amount,
//              estimatedReturn, price, tx? (AMM) | typedData? (CLOB) }

// For AMM markets, tradeData.tx is the encoded transaction to send.
// (For CLOB markets you receive tradeData.typedData to sign instead.)
if (tradeData.tx) {
  // Simulate the transaction first (sign/send from the user's own account)
  const callResult = await publicClient.call({
    account: account,
    data: tradeData.tx.data,
    to: tradeData.tx.to,
    value: BigInt(tradeData.tx.value),
  })
  console.log('Simulation result:', callResult)

  // Send the actual transaction
  const PRIVATE_KEY = process.env.PRIVATE_KEY as Hex
  const walletClient = createWalletClient({
    chain: mainnet,
    transport: http(),
  })
  const hash = await walletClient.sendTransaction({
    account: privateKeyToAccount(PRIVATE_KEY),
    data: tradeData.tx.data,
    to: tradeData.tx.to,
    value: BigInt(tradeData.tx.value),
  })
  console.log('Transaction hash:', hash)

  // 5. Save the transaction hash. Call this right after broadcasting —
  //    you do not need to wait for the transaction receipt.
  await fetch(`${TRADE_API_URL}/save`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
    },
    body: JSON.stringify({ txHash: hash, chainId: 8453 }), // chainId = chain the trade was sent on
  })
}

// 6. Get user positions and redeem winning positions
async function redeemWinningPositions(userAddress: string) {
  // Get resolved positions
  const positionsResponse = await fetch(`${TRADE_API_URL}/positions?walletAddress=${userAddress}&type=resolved&pg=1&ps=20`)
  const positionsData = await positionsResponse.json()

  // Filter for claimable positions (resolved markets where user has winning shares)
  const claimablePositions = positionsData.positions.filter(position =>
    position.resolutionOutcome === position.outcome && position.totalShareAmount > 0
  )

  console.log(`Found ${claimablePositions.length} claimable positions`)

  // Redeem each claimable position
  for (const position of claimablePositions) {
    try {
      // POST /trade/redeem requires a platform API key.
      const redeemResponse = await fetch(`${TRADE_API_URL}/redeem`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
        },
        body: JSON.stringify({
          market: position.market,
          account: userAddress
        })
      })
      const redeemData = await redeemResponse.json()

      if (redeemData.tx) {
        // Simulate the transaction first
        const callResult = await publicClient.call({
          account: redeemData.tx.account,
          data: redeemData.tx.data,
          to: redeemData.tx.to,
          value: BigInt(redeemData.tx.value),
        })
        console.log('Redeem simulation successful')

        // Send the actual transaction
        const redeemHash = await walletClient.sendTransaction({
          data: redeemData.tx.data,
          to: redeemData.tx.to,
          value: BigInt(redeemData.tx.value),
        })
        console.log('Redeem transaction hash:', redeemHash)

        // Wait for confirmation — the indexer auto-processes the redeem.
        // Redeems need no /trade/save or /trade/complete call.
        await publicClient.waitForTransactionReceipt({ hash: redeemHash })
      }
    } catch (error) {
      console.error(`Error redeeming position for market ${position.market}:`, error)
    }
  }
}

// Usage example
// await redeemWinningPositions('0x...') // User wallet address

API Endpoints

1. GET /trade/markets - Get All Tradeable Markets

Returns a list of all available prediction markets. Public — no API key required. Query Parameters:
interface MarketsQuery {
  chainId?: number; // Optional. If omitted, defaults to the legacy default chain.
}
Response:
interface MarketInfo {
  address: string;
  question: string;
  marketType: 'AMM' | 'CLOB'; // Which trading API this market needs (a chain can host both)
  outcome1Price: number; // Current price for the YES outcome (index 1)
  outcome0Price: number; // Current price for the NO outcome (index 0)
  endDate: string; // ISO-8601 (market group end)
  volume: number; // All-time cumulative volume in human USD (already normalized by chain decimals — do NOT divide again)
  transactionCount: number; // All-time cumulative trade count
  createdAt: string; // ISO-8601 — when the market instance became tradeable on its chain
}
volume is normalized server-side (raw / 10^chainDecimals), so it is already in human USD — do not divide it again client-side. This works for 18-decimal collateral too; never hardcode 1e6.
Example:
const markets = await fetch(`${TRADE_API_URL}/markets`) // or `${TRADE_API_URL}/markets?chainId=8453`
const data = await markets.json()

2. POST /trade/quote - Get Trade Quote

Get pricing information for a potential trade. Public — no API key required. Request:
interface TradeQuoteRequest {
  market: string;        // Market contract address
  amount: string;        // Amount in base units (6 decimals for USDC)
  outcome: 0 | 1;       // 0 = NO, 1 = YES
  type: 'Buy' | 'Sell'; // Trade type
  chainId?: number;     // Optional. Auto-resolved from the market when omitted.
}
Response:
interface TradeQuote {
  market: string;
  tokenAddress: string;  // Token to approve (collateral for buy; ERC1155 setApprovalForAll for sell)
  amount: string;
  outcome: 0 | 1;
  type: 'Buy' | 'Sell';
  estimatedReturn: string;
  estimatedPricePerShare: number;
}
Example:
const quote = await fetch(`${TRADE_API_URL}/quote`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    market: "0x...",
    amount: "100000000", // 100 USDC
    outcome: 1, // YES
    type: "Buy"
  })
})
const data = await quote.json()

3. POST /trade - Create Trade Intent

Creates a TransactionIntent and returns the data needed to execute the trade. The response shape depends on the market model:
  • AMM markets return an encoded tx block to sign and send.
  • CLOB markets return an EIP-712 typedData block to sign.
Requires a platform API key. Send it as the X-Platform-API-Key header (or Authorization: Bearer <apiKey>). Without it the request is rejected with 401.
Request:
interface TradeExecuteRequest {
  market: string;        // Market contract address
  amount: string;        // Amount in base units (6 decimals for USDC)
  outcome: 0 | 1;       // 0 = NO, 1 = YES
  type: 'Buy' | 'Sell'; // Trade type
  account: string;       // User wallet address
  chainId?: number;     // Optional. Auto-resolved from the market when omitted.
}
Response:
interface TradeIntentResponse {
  intentId: string;              // ID of the created TransactionIntent
  marketType: 'AMM' | 'CLOB';    // Determines whether `tx` or `typedData` is present
  market: string;
  outcome: 0 | 1;
  amount: string;                // Echoed from the request (base units)
  estimatedReturn: string;       // Shares for buy, USDC for sell (base units)
  price: number;                 // Estimated price per share
  tx?: {                         // AMM only
    to: string;      // Contract address to send transaction to (the market)
    data: string;    // Transaction data (encoded function call)
    value: string;   // ETH value to send (usually "0")
    chainId: number; // Chain the transaction must be submitted on
  };
  typedData?: object;            // CLOB only — EIP-712 typed data to sign
}
The AMM tx block no longer includes an account field. Sign and send the transaction from the user’s own wallet (the account you passed in the request).
Example:
const tradeData = await fetch(`${TRADE_API_URL}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
  },
  body: JSON.stringify({
    market: "0x...",
    amount: "100000000",
    outcome: 1,
    type: "Buy",
    account: "0x..." // user wallet address
  })
})
const data = await tradeData.json()

// AMM markets: send transaction using the user's wallet client
const hash = await walletClient.sendTransaction({
  data: data.tx.data,
  to: data.tx.to,
  value: BigInt(data.tx.value),
  account: "0x...", // the same user wallet address
})

// POST `/trade/save` - Record the broadcast trade for origin attribution.
// Send the transaction hash right after broadcasting; no need to wait for the receipt.
await fetch(`${TRADE_API_URL}/save`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
  },
  body: JSON.stringify({ txHash: hash, chainId: 8453 }),
})

// No /trade/complete call is required — the indexer auto-processes the confirmed transaction.

4. POST /trade/redeem - Execute Redeem

Generate redeem calldata for executing a redeem transaction on resolved markets where the user has winning positions.
Requires a platform API key (X-Platform-API-Key header, or Authorization: Bearer <apiKey>).
Request:
interface RedeemExecuteRequest {
  market: string;  // Market contract address
  account: string; // User wallet address
  chainId?: number; // Optional. Auto-resolved from the market when omitted.
}
Response:
interface RedeemExecuteResponse {
  tx: {
    to: string;      // Contract address to send transaction to
    data: string;    // Transaction data (encoded function call)
    account: string; // User account address
    value: string;   // ETH value to send (usually "0")
  };
  expectedReturn: string; // Expected return amount in base units
  market: string;         // Market address
}
Example:
const redeemData = await fetch(`${TRADE_API_URL}/redeem`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
  },
  body: JSON.stringify({
    market: "0x...", // Market contract address
    account: "0x..." // User wallet address
  })
})
const data = await redeemData.json()

// Send transaction using wallet client
const hash = await walletClient.sendTransaction({
  data: data.tx.data,
  to: data.tx.to,
  value: BigInt(data.tx.value),
  account: data.tx.account,
})

// Redeems are recorded automatically — no save or completion call needed.

5. GET /trade/positions - Get User Positions

Retrieve user positions with pagination support. Supports filtering by active or resolved markets. Query Parameters:
interface PositionsQuery {
  walletAddress: string;           // User wallet address (required)
  type: 'active' | 'resolved';    // Filter by market status (required)
  pg?: number;                    // Page number (default: 1)
  ps?: number;                    // Page size (default: 20, max: 100)
}
Response:
interface UserPositionsResponse {
  positions: Array<{
    market: string;              // Market address
    tokenAddress: string;        // Token address for the market shares
    amountInvested: number;      // Amount invested in collateral token (in base units)
    outcome: 0 | 1;             // Outcome index bought (0 for NO, 1 for YES)
    totalShareAmount: number;    // Total share amount held (in base units)
    question: string;          // Market question
    resolutionOutcome: number | null; // Resolution outcome (0 for NO, 1 for YES, -1 for DRAW/TIE; null if market not resolved)
    marketEndDate: string | null;    // Market end date
  }>;
  page: number;                  // Current page number
  pageSize: number;              // Number of items per page
  hasPrevious: boolean;          // Whether there is a previous page
  hasNext: boolean;              // Whether there is a next page
  totalCount: number;            // Total number of positions for a given user
}
Example:
const positions = await fetch(`${TRADE_API_URL}/positions?walletAddress=0x...&type=resolved&pg=1&ps=20`)
const data = await positions.json()

// Filter for claimable positions (resolved markets where user has winning shares)
const claimablePositions = data.positions.filter(position =>
  position.resolutionOutcome === position.outcome && position.totalShareAmount > 0
)

// Redeem each claimable position
for (const position of claimablePositions) {
  const redeemData = await fetch(`${TRADE_API_URL}/redeem`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Platform-API-Key': process.env.FORESIGHT_API_KEY as string,
    },
    body: JSON.stringify({
      market: position.market,
      account: "0x..." // User wallet address
    })
  })
  // ... execute redeem transaction
}

6. POST /trade/save - Save Trade

After broadcasting a trade transaction, send its transaction hash to this endpoint. Call it right after submitting the transaction — you do not need to wait for the transaction receipt. Headers:
  • X-Platform-API-Key: <your platform API key>
Request:
interface TradeSaveRequest {
  txHash: string;  // 0x-prefixed 32-byte transaction hash from broadcast
  chainId: number; // EVM chain id the transaction was sent on
}
Response:
interface TradeSaveResponse {
  ok: true;
}

Token Approval Process

Before executing trades, users must approve the market maker contract to spend their tokens. This is a standard ERC-20 requirement.

Approval Steps

  1. Check Current Allowance: Query the token contract to see how much the market maker is allowed to spend
  2. Approve if Needed: If allowance is insufficient, call the approve function on the token contract
  3. Wait for Confirmation: Wait for the approval transaction to be confirmed
  4. Execute Trade: Proceed with the trade execution

Example Approval Code

// Check current allowance
const allowance = await publicClient.readContract({
  address: tokenAddress, // USDC token address
  abi: erc20ABI,
  functionName: 'allowance',
  args: [userAddress, marketMakerAddress]
})

// Approve if needed (using max uint256 for unlimited approval)
if (allowance < BigInt(tradeAmount)) {
  const approveHash = await walletClient.writeContract({
    address: tokenAddress,
    abi: erc20ABI,
    functionName: 'approve',
    args: [marketMakerAddress, BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')]
  })

  await publicClient.waitForTransactionReceipt({ hash: approveHash })
}

Important Notes

  • Buy trades require approval: Only buying tokens requires token approval
  • Sell trades require ERC1155 setApprovalForAll: Selling conditional tokens require ERC1155 approval, as we are using ERC1155 to represent market shares
  • Unlimited approval: The example uses max uint256 for unlimited approval to avoid repeated approval transactions
  • Gas costs: Approval transactions require gas fees, so consider this in your UX

Key Notes

  • All amounts are handled in base units (6 decimals for USDC)
  • The execute endpoint returns transaction data that must be signed and sent by the user’s wallet
  • Always simulate transactions before execution to catch potential failures
  • Token approval is required before executing any trades
  • After broadcasting a trade, send its transaction hash to /trade/save. Call it right after submitting — you do not need to wait for the transaction receipt.
  • The API follows RESTful conventions with appropriate HTTP status codes
  • /trade and /trade/redeem require a platform API key (X-Platform-API-Key); /trade/markets, /trade/quote, and /trade/positions are public
  • /trade returns a trade intent — AMM markets include a tx block, CLOB markets include typedData
  • Use /trade/positions to get a user’s positions
  • Use /trade/redeem to get the calldata neccessary to redeem a position
  • No /trade/complete call is needed after a trade or redeem — the indexer auto-processes confirmed transactions
  • /trade/save applies to trades only (origin attribution); redeems need no save call
The API follows a clear pattern where:
  • Markets endpoint provides available trading opportunities
  • Quote endpoint calculates trade details and pricing
  • Execute endpoint prepares blockchain transaction data for the user to sign and send
  • Save endpoint records the broadcast trade’s transaction hash for the backend
  • Positions endpoint retrieves user’s current positions with pagination
  • Redeem endpoint prepares redeem transaction data for winning positions
  • Indexer processes confirmed transactions to ensure positions appear in the frontend