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. Execute the trade
const executeResponse = await fetch(`${TRADE_API_URL}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    market: marketAddress,
    amount: amount,
    outcome: outcome,
    type: tradeType,
    account: account
  })
})
const tradeData = await executeResponse.json()
console.log('Trade execution data:', tradeData)

// If the trade status is successful
if (tradeData.tx) {
  // Simulate the transaction first
  const callResult = await publicClient.call({
    account: tradeData.tx.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. Completion step (Deprecated): No longer needed — indexer auto-processes confirmed transactions
// Wait for on-chain confirmation; the indexer will pick it up automatically
await publicClient.waitForTransactionReceipt({ hash })
// No call to /trade/complete is required
}

// 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 {
      const redeemResponse = await fetch(`${TRADE_API_URL}/redeem`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        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 and complete
        await publicClient.waitForTransactionReceipt({ hash: redeemHash })

        const completeRedeemResponse = await fetch(`${TRADE_API_URL}/complete`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ txHash: redeemHash })
        })
        const redeemCompletion = await completeRedeemResponse.json()
        console.log('Redeem completion:', redeemCompletion)
      }
    } 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. Response:
interface MarketInfo {
  address: string;
  question: string;
  outcome1Price: number;
  outcome0Price: number;
  endDate: string;
  conditionDisplayName?: string;
}
Example:
const markets = await fetch(`${TRADE_API_URL}/markets`)
const data = await markets.json()

2. POST /trade/quote - Get Trade Quote

Get pricing information for a potential trade. 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
}
Response:
interface TradeQuote {
  market: string;
  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 - Execute Trade

Execute a trade and get transaction data for blockchain submission. 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
}
Response:
interface TradeExecuteResponse extends TradeQuote {
  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")
  };
}
Example:
const tradeData = await fetch(`${TRADE_API_URL}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    market: "0x...",
    amount: "100000000",
    outcome: 1,
    type: "Buy",
    account: "0x..." // user wallet address
  })
})
const data = await tradeData.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,
})

// 4. POST `/trade/complete` - Complete Trade
// After the transaction is confirmed on-chain, notify the backend to process and persist it
await publicClient.waitForTransactionReceipt({ hash })

const completeRes = await fetch(`${TRADE_API_URL}/complete`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ txHash: hash })
})
const completion = await completeRes.json()
// completion: { success: boolean; message: string; data?: { ... } }

4. POST /trade/redeem - Execute Redeem

Generate redeem calldata for executing a redeem transaction on resolved markets where the user has winning positions. Request:
interface RedeemExecuteRequest {
  market: string;  // Market contract address
  account: string; // User wallet address
}
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' },
  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,
})

// Completion step (Deprecated): No longer needed — indexer auto-processes confirmed transactions
await publicClient.waitForTransactionReceipt({ hash })
// No call to /trade/complete is required

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 (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' },
    body: JSON.stringify({
      market: position.market,
      account: "0x..." // User wallet address
    })
  })
  // ... execute redeem transaction
}

6. POST /trade/complete - Complete Trade (Deprecated)

Deprecated: An indexer now automatically processes confirmed on-chain transactions and persists them. Calling this endpoint is no longer required. Request:
interface TradeCompleteRequest {
  txHash: string; // 0x-prefixed 32-byte hash
}
Response:
interface TradeCompleteResponse {
  success: boolean;
  message: string;
  data?: {
    txHash: string;
    type: 'BUY' | 'SELL';
    tokenAmount: string;
    usdtAmount: string;
    price: string;
    positionId: string;
    userAddress: string;
    createdAt: string; // ISO date-time
  };
}
Notes:
  • Deprecated; retained for backward compatibility only.
  • If used, behavior remains unchanged but is unnecessary in normal flows.

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 sending the transaction, no separate completion call is needed. The indexer automatically detects and persists confirmed transactions.
  • The API follows RESTful conventions with appropriate HTTP status codes
  • Use /trade/positions to get a user’s positions
  • Use /trade/redeem to get the calldata neccessary to redeem a position
  • Redeem transactions also require calling /trade/complete after confirmation
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
  • 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 (replacing the need for the Complete endpoint)