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.
- Open Settings → API keys in the Trepa app
- Click + Create key and give it a label (e.g. "My bot")
- 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.
- Go to Settings → Wallet → Export Private Key in the Trepa app
- Enter your PIN or 2FA
- 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
Add it to .gitignore:
echo ".env" >> .gitignore
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
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,
}),
})
Run it:
node --env-file=.env bot.ts
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:
- Waits for an open pool — finds a pool currently accepting predictions
-
Calls your
predictfunction — passes the pool object (reference price, min stake, timing) - Builds, signs, and submits — creates the Solana transaction and submits it on-chain
-
Handles errors gracefully — if
predictthrows, the error is logged and the loop continues - 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,
}
},
})
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)
},
})
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 }
},
})
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'),
})
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
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,
}),
})
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,
}
},
}))
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)
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')
}
Tip: The
@trepa/sdkbot loop already spaces work sensibly. You mainly need retry logic for custom scripts outsidebots.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)
},
})
Run it:
node --env-file=.env bot.ts
How Winning Works: The Median-Error Rule
Understanding the payout mechanism is key to building a profitable strategy:
- Everyone pays the same entry fee (currently $1 USDC)
- You submit a price estimate — your prediction of the BTC price at settlement
- Error = |your estimate − outcome| — the absolute distance matters
- The median error is the cutoff — all errors sorted, the middle value is the threshold
- Win if your error is strictly below the median — roughly ~50% of players win each round
- Winners split the prize pool — funded by losers' forfeited fees (minus platform take), distributed by accuracy weight (closer = bigger share)
- 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
- Trepa SDK on npm
- GitHub: TrepaOrg/trepa-sdk
- Developer Docs
- Writing Bots
- Swarms
- SDK Reference
- Rate Limits
- Game Rules: Winning & Losing
- Discord
Written by Alex Rivers (@Swampy) — Full-stack developer and Solana builder
Top comments (0)