Back It (Onchain) is a multi-chain social prediction market platform built on Base and Stellar. It allows users to create "calls" (predictions), back them with onchain stakes, and build a reputation based on accuracy.
- Create Calls: Make bold predictions about crypto, culture, or tech.
- Back & Counter: Stake on "YES" or "NO" outcomes.
- Social Feed:
- For You: Algorithmic feed of trending calls.
- Following: See calls from users you follow.
- User Profiles: Track your reputation, follower counts, and betting history.
- Onchain Accountability: All stakes and outcomes are recorded onchain.
- Multi-Chain Support: Deploy and interact on Base (EVM) or Stellar (Soroban).
| Chain | Status | Token | Wallet |
|---|---|---|---|
| Base (Ethereum L2) | ✅ Production | USDC (ERC-20) | Coinbase Wallet, MetaMask |
| Stellar (Soroban) | 🚧 In Development | USDC (Stellar Native) | Freighter, Lobstr |
- Framework: Next.js (App Router)
- Styling: Tailwind CSS
- Base Integration: OnchainKit, Wagmi, viem
- Stellar Integration: @stellar/stellar-sdk, @stellar/freighter-api
- Framework: NestJS
- Database: PostgreSQL + TypeORM
- Indexing: Multi-chain event indexer (ethers.js + Stellar Horizon)
| Chain | Language | Framework |
|---|---|---|
| Base | Solidity | Foundry |
| Stellar | Rust | Soroban SDK |
- Base: EIP-712 typed data signatures (secp256k1)
- Stellar: ed25519 signatures
back-it-onchain/
├── packages/
│ ├── frontend/ # Next.js web application
│ ├── backend/ # NestJS API server
│ ├── contracts/ # Solidity contracts (Base)
│ └── contracts-stellar/ # Soroban contracts (Stellar)
├── ARCHITECTURE.md # Detailed system design
├── APP-CONCEPT.md # Product vision
└── README.md
| Package | Description |
|---|---|
packages/frontend |
Next.js app with multi-chain wallet support |
packages/backend |
Unified API server, multi-chain indexer, oracle service |
packages/contracts |
Solidity smart contracts for Base (Foundry) |
packages/contracts-stellar |
Rust smart contracts for Stellar (Soroban) |
- Node.js (v18+)
- pnpm (v8+)
- Docker (for PostgreSQL)
- Foundry (for Base contracts)
- Rust + soroban-cli (for Stellar contracts)
-
Clone the repo
git clone https://github.com/yourusername/back-it-onchain.git cd back-it-onchain -
Install dependencies
pnpm install
-
Setup Environment Variables
- Copy
.env.exampleto.envinpackages/backendandpackages/contracts. - Copy
.env.local.exampleto.env.localinpackages/frontend.
- Copy
-
Start Development
pnpm dev
This starts both frontend and backend concurrently using Turborepo:
- Frontend: http://localhost:3000
- Backend: http://localhost:3001
cd packages/contracts
forge build
forge testcd packages/contracts-stellar
soroban contract build
soroban contract test┌─────────────────────────────────────────────────────────────┐
│ Frontend │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Base Wallet │ │ Stellar Wallet │ │
│ │ (Wagmi/OCK) │ │ (Freighter) │ │
│ └────────┬────────┘ └────────┬────────┘ │
└───────────┼────────────────────────────┼────────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Base Contracts │ │ Stellar Contracts │
│ (Solidity) │ │ (Soroban/Rust) │
│ - CallRegistry │ │ - call_registry │
│ - OutcomeManager │ │ - outcome_manager │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Unified Backend │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Indexer │ │ Oracle │ │ Feed │ │
│ │ (Multi-Chain) │ │ Service │ │ Service │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ PostgreSQL │ │
│ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
$ pnpm run test:cov
# WebSocket Events Gateway
Location: `packages/backend/src/gateways/`
## Architecture
Client (browser / mobile) │ Socket.io ws://api/events │ ▼ EventsGateway (/events namespace) │ ├─ handleConnection() → auto-joins user: room if JWT present ├─ subscribeMarket → joins market: room ├─ unsubscribeMarket → leaves market: room └─ authenticate → mid-session login → joins user: room │ └─ @OnEvent() listeners ←── EventEmitter2 (from service layer / Issue 9) stake.created → broadcast → market: price.updated → broadcast → market: outcome.proposed → broadcast → market: dispute.raised → broadcast → market: + user: dispute.resolved → broadcast → market: + user: user.notification → send → user:
## Installation
```bash
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io @nestjs/event-emitter
See app.module.snippet.ts. The two required additions are:
EventEmitterModule.forRoot({ wildcard: true, delimiter: '.' })GatewaysModule
import { io } from 'socket.io-client';
const socket = io('wss://api.example.com/events', {
transports: ['websocket'],
});
// Subscribe to a market
socket.emit('subscribeMarket', { marketId: 'mkt-abc' });
// Receive live events
socket.on('stakeCreated', (data) => console.log('new stake', data));
socket.on('priceUpdated', (data) => console.log('price', data));
socket.on('outcomeProposed', (data) => console.log('outcome', data));
socket.on('disputeRaised', (data) => console.log('dispute', data));
socket.on('disputeResolved', (data) => console.log('resolved', data));Option A — JWT in Authorization header at connection time:
const socket = io('wss://api.example.com/events', {
transports: ['websocket'],
extraHeaders: { Authorization: `Bearer ${token}` },
});Option B — Authenticate after connecting (SPA login flow):
socket.emit('authenticate', { token: jwtToken });
socket.on('authenticated', ({ userId }) => console.log('logged in as', userId));Private notifications arrive on the notification event:
socket.on('notification', ({ type, payload, timestamp }) => {
console.log(`[${type}]`, payload);
});Inject EventEmitter2 and emit with the dot-delimited keys the gateway listens to:
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { StakeCreatedEvent } from '../gateways/events.types';
@Injectable()
export class StakesService {
constructor(private readonly emitter: EventEmitter2) {}
async createStake(...) {
// ... business logic ...
this.emitter.emit('stake.created', {
marketId: stake.marketId,
staker: stake.stakerAddress,
amount: stake.amount.toString(),
outcomeIndex: stake.outcomeIndex,
timestamp: Date.now(),
txHash: tx.hash,
} satisfies StakeCreatedEvent);
}
}| EventEmitter2 key | Socket.io client event | Room(s) notified |
|---|---|---|
stake.created |
stakeCreated |
market:<id> |
price.updated |
priceUpdated |
market:<id> |
outcome.proposed |
outcomeProposed |
market:<id> |
dispute.raised |
disputeRaised |
market:<id> + user:<staker> |
dispute.resolved |
disputeResolved |
market:<id> + user:<staker> |
user.notification |
notification |
user:<id> |
constructor(private readonly eventsGateway: EventsGateway) {}
// Broadcast to all clients watching a market
this.eventsGateway.broadcastToMarket(marketId, 'customEvent', payload);
// Push to a single user
this.eventsGateway.sendToUser(userId, 'notification', payload);The oracle service supports both chains with different signature schemes:
| Chain | Signature Scheme | Verification |
|---|---|---|
| Base | EIP-712 (secp256k1) | ecrecover in Solidity |
| Stellar | ed25519 | env.crypto().ed25519_verify() in Soroban |
- Architecture - Detailed system design
- App Concept - Product vision and principles
- Base deployment (MVP)
- Social graph and feed
- Stellar Soroban contracts
- Multi-chain wallet selector
- Cross-chain reputation aggregation
- Mainnet deployments
Contributions are welcome! Please read the contributing guidelines before submitting a PR.
MIT