DEV Community

Alex Rivers
Alex Rivers

Posted on

Building a Prediction Bot with the Trepa Building a Prediction Bot with the Trepa SDK: A Complete GuideSDK: A Complete Guide

Introduction

Trepa is a Solana-based precision forecasting platform backed by Colosseum and Balaji. Unlike binary yes/no prediction markets, Trepa ranks you by how close your estimate is to the actual price. Each round, everyone pays the same fixed entry fee ($1 USDC). After the outcome is revealed, the median-error rule splits the field: players whose error is strictly below the median win; the rest lose. Winners get their fee back plus a share of the prize pool (funded by losers' fees), weighted by accuracy. Losers forfeit their entry.

This guide walks you through building a working prediction bot using the official @trepa/sdk TypeScript package — from setup to advanced multi-account swarms.


Prerequisites

  • Node.js 22.12 or newer
  • A Trepa account (sign up at trepa.app)
  • USDC on Solana deposited into your Trepa wallet
  • Basic TypeScript/JavaScript knowledge

Step 1: Get Your Credentials

You need two secrets from your Trepa account: an API key and your wallet private key.

API Key

Your API key tells Trepa which user your bot represents. Keys start with trp_ and are shown in full only once.

  1. Open Settings → API keys in the Trepa app
  2. Click + Create key and give it a label (e.g. "My bot")
  3. Copy and store the key immediately — it won't be shown again

You can have up to 5 active keys. Use a separate key per bot so revoking one doesn't affect others.

Wallet Private Key

Your Trepa embedded wallet signs all on-chain prediction transactions.

  1. Go to Settings → Wallet → Export Private Key in the Trepa app
  2. Enter your PIN or 2FA
  3. Copy the base58-encoded key

Store both in a .env file — never hardcode credentials:

# .env
TREPA_API_KEY=trp_your_api_key_here
TREPA_PRIVATE_KEY=your_base58_private_key_here
Enter fullscreen mode Exit fullscreen mode

Add it to .gitignore:

echo ".env" >> .gitignore
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Project

mkdir my-trepa-bot && cd my-trepa-bot
npm init -y
npm pkg set type=module
npm install @trepa/sdk
Enter fullscreen mode Exit fullscreen mode

Step 3: Your First Prediction Bot

The SDK exposes trepa.bots.run(), which handles the entire round lifecycle. Each time a pool opens for predictions, your predict callback fires with the current pool data. You return { value, stake } — your price forecast and USDC to stake — or null to skip the round.

import { credentialsFromEnv, Trepa } from '@trepa/sdk'

const trepa = new Trepa({ credentials: credentialsFromEnv() })

// Simple strategy: predict the current BTC spot price from Binance
async function btcSpot(): Promise<number> {
  const res = await fetch(
    'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
  )
  const { price } = (await res.json()) as { price: string }
  return Number(price)
}

await trepa.bots.run({
  predict: async (pool) => ({
    value: await btcSpot(),
    stake: pool.min_stake,
  }),
})
Enter fullscreen mode Exit fullscreen mode

Run it:

node --env-file=.env bot.ts
Enter fullscreen mode Exit fullscreen mode

Press Ctrl+C to stop. The SDK signs your bot out cleanly before exit.


Step 4: Understanding the Prediction Loop

Every round, trepa.bots.run does the following automatically:

  1. Waits for an open pool — finds a pool currently accepting predictions
  2. Calls your predict function — passes the pool object (reference price, min stake, timing)
  3. Builds, signs, and submits — creates the Solana transaction and submits it on-chain
  4. Handles errors gracefully — if predict throws, the error is logged and the loop continues
  5. Loops — waits for the next open pool and repeats

You only need to implement predict. Everything else — authentication, transaction signing, session refresh, clean shutdown — is handled by the SDK.


Step 5: Using Context and Hooks

The ctx Parameter

Your predict function can accept a second argument, ctx, which gives you access to the signed-in user and a scoped Trepa client:

await trepa.bots.run({
  predict: async (pool, ctx) => {
    // ctx.me — the Trepa user for this bot
    // ctx.trepa — scoped Trepa client for API calls
    const stats = await ctx.trepa.users.statistics(ctx.me.id)
    console.log(`My precision stats:`, stats)

    return {
      value: await btcSpot(),
      stake: pool.min_stake,
    }
  },
})
Enter fullscreen mode Exit fullscreen mode

Lifecycle Hooks

Pass optional callbacks alongside predict for logging and side effects:

Hook When it runs
onStart Right after the bot signs in
onPredicted After a forecast is submitted successfully
onPoolSkipped When the bot returns null (skips a round)
onError When the loop recovers from a problem
await trepa.bots.run({
  predict: async (pool) => ({
    value: await btcSpot(),
    stake: pool.min_stake,
  }),

  onStart: () => {
    console.log('🤖 Bot signed in and ready')
  },

  onPredicted: ({ pool, value }) => {
    console.log(`📊 Submitted prediction: $${value} for ${pool.title}`)
  },

  onError: (err) => {
    console.error('⚠️ Error (will retry):', err.message)
  },
})
Enter fullscreen mode Exit fullscreen mode

Step 6: Advanced Strategies

Momentum Strategy

Use the SDK's users.predictions method to look at your recent results and adjust:

await trepa.bots.run({
  predict: async (pool, ctx) => {
    // Fetch your last few predictions
    const recent = await ctx.trepa.users.predictions(ctx.me.id, {
      limit: 5,
      sort_by: 'CREATION_DATE',
    })

    if (recent.length < 2) {
      // Not enough history — just predict spot
      return { value: await btcSpot(), stake: pool.min_stake }
    }

    // Calculate average prediction error direction
    const spot = await btcSpot()
    return { value: spot, stake: pool.min_stake }
  },
})
Enter fullscreen mode Exit fullscreen mode

Skipping Rounds

Return null from predict to skip a round when conditions aren't right:

await trepa.bots.run({
  predict: async (pool) => {
    const spot = await btcSpot()

    // Skip if price is moving too fast (high volatility)
    // Your own logic here
    const shouldSkip = false // replace with your volatility check

    if (shouldSkip) return null

    return { value: spot, stake: pool.min_stake }
  },
  onPoolSkipped: () => console.log('⏭️ Skipped this round'),
})
Enter fullscreen mode Exit fullscreen mode

Step 7: Running Multiple Accounts (Swarms)

A swarm runs several bots in one process — each with its own Trepa account and wallet. Use numbered environment variables:

# .env
TREPA_API_KEY_1=trp_first_account_key
TREPA_PRIVATE_KEY_1=first_account_private_key
TREPA_API_KEY_2=trp_second_account_key
TREPA_PRIVATE_KEY_2=second_account_private_key
Enter fullscreen mode Exit fullscreen mode

credentialsFromEnv() loads _1, _2, _3… until it hits a gap. A half pair (key without matching private key) throws at startup.

⚠️ Important: Trepa allows one prediction per account per pool. Each bot needs its own Trepa account. If multiple API keys belong to the same account, only the first bot's prediction lands.

Same Strategy, Multiple Accounts

import { credentialsFromEnv, Trepa } from '@trepa/sdk'

const trepa = new Trepa({ credentials: credentialsFromEnv() })

// All bots predict the same spot price
await trepa.bots.run({
  predict: async (pool) => ({
    value: await btcSpot(),
    stake: pool.min_stake,
  }),
})
Enter fullscreen mode Exit fullscreen mode

Different Strategy Per Bot (Ladder)

Pass a function to bots.run instead of a plain object. It receives index (which bot) and count (total bots):

await trepa.bots.run(({ index, count }) => ({
  predict: (pool) => {
    const fair = 96_000     // your fair-value estimate
    const spacing = 400     // price gap between bots
    return {
      value: fair + (index - (count - 1) / 2) * spacing,
      stake: pool.min_stake,
    }
  },
}))
Enter fullscreen mode Exit fullscreen mode

Each bot shifts its forecast along a ladder around your fair value, hedging your position across the median-error cutoff.


Step 8: Checking Your Performance

Use the SDK's helper methods to inspect your account programmatically:

import { credentialsFromEnv, Trepa } from '@trepa/sdk'

const trepa = new Trepa({ credentials: credentialsFromEnv() })
const me = await trepa.auth.me()

// Lifetime statistics
const stats = await trepa.users.statistics(me.id)
console.log('Statistics:', stats)

// Portfolio (balances, locked stake)
const portfolio = await trepa.users.portfolio(me.id)
console.log('Portfolio:', portfolio)

// Recent predictions with reward info
const predictions = await trepa.users.predictions(me.id, {
  limit: 10,
  includes: ['reward'],
})
console.log('Recent predictions:', predictions)

// Streak details
const streakDetails = await trepa.users.streakDetails(me.id, 'bitcoin')
console.log('Streak:', streakDetails)
Enter fullscreen mode Exit fullscreen mode

Step 9: Rate Limits and Best Practices

Trepa enforces per-IP rate limits. Key limits:

Area Limit Window
Read-only endpoints (pools, users, stats) 120 requests 1 minute
Auth endpoints 15 requests per endpoint 1 minute
Transaction routes (predictions, stake updates, claims) 10 requests per endpoint 1 minute
Withdrawals 20 requests per endpoint 24 hours

Withdrawals are a hard daily quota, not a per-minute burst. If you hit a 429, back off with exponential retry:

import { isTrepaError } from '@trepa/sdk'

async function submitWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (err) {
      if (isTrepaError(err) && err.status === 429 && i < maxRetries - 1) {
        const delay = Math.pow(2, i) * 1000
        console.log(`Rate limited. Retrying in ${delay}ms...`)
        await new Promise(resolve => setTimeout(resolve, delay))
      } else {
        throw err
      }
    }
  }
  throw new Error('Max retries exceeded')
}
Enter fullscreen mode Exit fullscreen mode

Tip: The @trepa/sdk bot loop already spaces work sensibly. You mainly need retry logic for custom scripts outside bots.run.


Full Working Example

A complete, production-ready bot with logging, error handling, and graceful shutdown:

import { credentialsFromEnv, Trepa, isTrepaError } from '@trepa/sdk'

const trepa = new Trepa({ credentials: credentialsFromEnv() })

async function btcSpot(): Promise<number> {
  const res = await fetch(
    'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
  )
  const { price } = (await res.json()) as { price: string }
  return Number(price)
}

let rounds = 0

await trepa.bots.run({
  predict: async (pool) => {
    const spot = await btcSpot()
    console.log(`📊 Round ${++rounds} | BTC spot: $${spot.toFixed(2)}`)
    return { value: spot, stake: pool.min_stake }
  },

  onStart: async () => {
    const me = await trepa.auth.me()
    const portfolio = await trepa.users.portfolio(me.id)
    console.log(`🤖 Bot started for user ${me.id}`)
    console.log(`💰 Portfolio:`, portfolio)
  },

  onPredicted: ({ pool, value }) => {
    console.log(`✅ Predicted $${value} for ${pool.title}`)
  },

  onPoolSkipped: () => {
    console.log('⏭️ Skipped this round')
  },

  onError: (err) => {
    console.error('⚠️ Error (continuing):', err)
  },
})
Enter fullscreen mode Exit fullscreen mode

Run it:

node --env-file=.env bot.ts
Enter fullscreen mode Exit fullscreen mode

How Winning Works: The Median-Error Rule

Understanding the payout mechanism is key to building a profitable strategy:

  1. Everyone pays the same entry fee (currently $1 USDC)
  2. You submit a price estimate — your prediction of the BTC price at settlement
  3. Error = |your estimate − outcome| — the absolute distance matters
  4. The median error is the cutoff — all errors sorted, the middle value is the threshold
  5. Win if your error is strictly below the median — roughly ~50% of players win each round
  6. Winners split the prize pool — funded by losers' forfeited fees (minus platform take), distributed by accuracy weight (closer = bigger share)
  7. Losers forfeit their entry fee

The key insight: you don't need to predict the exact price. You need to be more accurate than the median player. A strategy that's consistently slightly better than average will be profitable over time.


Key Concepts Summary

Concept Description
Estimate Your predicted BTC price for the round
Outcome The actual BTC price when the round settles
Error Absolute distance: |estimate − outcome|
Median Error The cutoff between winners and losers
Entry Fee Fixed stake per round ($1 USDC), same for everyone
Prize Pool Funded by losers' fees (after take), shared among winners by accuracy weight
Precision Score 100–1000 score per round for leaderboards and streaks

SDK Quick Reference

Namespace Key Methods
trepa.bots run({ predict, onStart?, onPredicted?, onPoolSkipped?, onError? })
trepa.auth me(), refresh(), logout()
trepa.users get(id), predictions(id, opts), statistics(id), portfolio(id), streakDetails(id, streakId)
trepa.pools list(opts), get(poolId)
trepa.streaks bitcoin(), pools(streakId, opts), poolDetails(streakId), claimReward({ streakRewardId })
trepa.predictions create({ poolId, value, stake }), update({ predictionId, value }), updateStake({ predictionId, stake })
trepa.rewards claim({ poolId, rewardId })
trepa.withdrawals create({ toAddress, amount, mintAddress })
trepa.raw Typed HTTP escape hatch: GET, POST, etc.

Resources


Written by Alex Rivers (@Swampy) — Full-stack developer and Solana builder

Top comments (0)