Skip to content

openpredictionmarkets/gantt

 
 

Repository files navigation

ganttmarket

GitHub-native prediction market MVP that stores state directly in GitHub Issues. No external database. Deployable to Fly.io with a clean GitHub Actions workflow.

Why

  • Transparent state: market and ledger snapshots are signed JSON blocks inside Issues.
  • Lightweight ops: minimal dependencies, containerized, deploy with one workflow.
  • GitHub-native: identity = GitHub username, governance via repo permissions.

Concept: markets on issues

A “market” is a question about your repo’s work, backed by a prediction price that updates as people trade. The default question is:

  • “Will Issue # be closed by ?”

Collaborators buy YES or NO shares to express their beliefs. The price (0–1) reflects the current crowd forecast (e.g., 0.72 ≈ 72% chance). When the deadline hits:

  • If the issue closed before the deadline, YES wins; otherwise, NO wins.
  • Winners receive $1 per winning share; balances and positions are tracked in a signed ledger issue.

Why trade on issues?

  • Planning: quantify the likelihood that a task lands by a date.
  • Prioritization: discover which work is at risk through market prices rather than gut feel.
  • Alignment: make implicit expectations visible (prices) and auditable (signed snapshots).
  • Fun-but-useful: add a lightweight “skin in the game” signal without real money.

Example markets

  • “Will Issue #456 be closed by 2025-12-01?”
  • “Will PR #789 be merged by Friday?”
  • “Will the v1.2 release ship by end of month?”

Workflow

  • Create: comment /market create issue 123 deadline 2025-11-15
  • Trade: comment /trade yes 10 or /trade no 5 on the market issue
  • Check: comment /balance to see your credits and position
  • Resolve: automatically resolves YES if the target closes before deadline; else NO after deadline

Notes

  • Markets are collaborator-only to reduce spam and ensure skin-in-repo.
  • Shares can be fractional to 2 decimals; max 10,000 shares per trade; 5 trades/min rate limit.
  • All state (market snapshots, ledger) is signed JSON in issue bodies; no external DB.

Features

  • Markets are Issues with a signed snapshot block (qYes, qNo, priceYes, deadline, seq)
  • Create markets via comments: /market create issue 123 deadline 2025-11-15
  • Trade via comments: /trade yes 10 or /trade no 5 (fractional shares up to 2 decimals)
  • Ledger Issue tracks balances and positions (signed, with seq)
  • Auto-resolution and payouts ($1 per winning share)
  • Rate limiting: max 5 trades per user per minute (in-memory)
  • Health check endpoint at /healthz
  • Minimal dependencies, no external DB
  • Dockerfile + fly.toml + GitHub Actions deploy

How it works

  1. A comment (e.g., /trade yes 10) triggers a GitHub webhook to your app.
  2. The app verifies the webhook signature and maps the repo/issue from the payload.
  3. It reads the market snapshot from the issue body:
    • Extracts the <!-- forecast-snapshot:start --> ... <!-- forecast-snapshot:end --> block
    • Verifies the snapshot signature; invalid/tampered data is rejected
  4. It reads the ledger snapshot from the “Forecast Ledger” issue and verifies its signature.
  5. Applies LMSR math to compute cost and new price; checks user balance and rate limit.
  6. Writes the updated ledger and market snapshot (signed), with seq increments and optimistic concurrency. On write conflicts, it retries with backoff.
  7. Posts a trade receipt comment with cost, slippage, new probability, and remaining balance.

Commands

  • Create market (comment on any issue or market issue):
    • /market create issue <number> deadline YYYY-MM-DD
  • Trade (comment on a market issue):
    • /trade yes <shares>
    • /trade no <shares>
  • Check balance and position in current market:
    • /balance
  • Help:
    • /help

Notes:

  • Shares can be fractional to 2 decimal places (e.g., 7.25).
  • Maximum 10,000 shares per trade.
  • Rate limit: 5 trades per minute per user.
  • Starting balance: 1000 credits.
  • Winners receive $1 per winning share.

Permissions: who can trade

Trading is restricted to repository collaborators (permissions: read, write, admin). Non-collaborators will receive a comment indicating they cannot trade.

Security model

  • Snapshots and the ledger are signed with HMAC-SHA256 using SNAPSHOT_SIGNING_KEY.
  • On read:
    • Market snapshot: extracted and verified. Invalid or missing snapshots are treated as not open/not available.
    • Ledger: extracted and verified. Invalid signatures raise an error.
  • On write:
    • The block is re-rendered with a signed payload and seq increment.
    • A single snapshot/ledger block is kept and replaced at the top.

Health check

GET /healthz returns JSON with validation of required configuration:

  • App ID present
  • Private key shape (BEGIN PRIVATE KEY)
  • Webhook secret and snapshot signing key minimum lengths
  • Positive b parameter
  • Valid port

GitHub App setup

  1. Create a GitHub App:
    • Permissions:
      • Issues: Read & write
      • Metadata: Read
    • Webhooks: subscribe to issues, issue_comment
  2. Install the App on the target organization/repository.
  3. After deploying, set the webhook URL:
    • https://<your-fly-app>.fly.dev/webhooks

Fly.io deployment (via GitHub Actions)

This repo includes .github/workflows/deploy.yml to build and deploy on push to main or manually via “Run workflow”.

Prerequisites

  • Install Fly CLI locally and create the app:
    flyctl apps create ganttmarket
    flyctl auth token
  • In your GitHub repo, add Actions secrets:
    • FLY_API_TOKEN: from flyctl auth token
    • GITHUB_APP_ID: numeric App ID
    • GITHUB_WEBHOOK_SECRET: your webhook secret
    • SNAPSHOT_SIGNING_KEY: a strong random string (256-bit)
    • GITHUB_APP_PRIVATE_KEY: paste the PEM content of your app private key

Workflow file

.github/workflows/deploy.yml:

name: Deploy to Fly.io

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v1

      - name: Install dependencies
        run: bun install

      - name: Check formatting
        run: bun run format:check

      - name: Lint code
        run: bun run lint:check

      - name: Typecheck
        run: bun run typecheck

      - name: Setup Flyctl
        uses: superfly/flyctl-actions/setup-flyctl@v1

      - name: Set Fly secrets
        run: |
          flyctl secrets set \
            GITHUB_APP_ID=${{ secrets.GITHUB_APP_ID }} \
            GITHUB_WEBHOOK_SECRET=${{ secrets.GITHUB_WEBHOOK_SECRET }} \
            SNAPSHOT_SIGNING_KEY=${{ secrets.SNAPSHOT_SIGNING_KEY }} \
            GITHUB_APP_PRIVATE_KEY="${{ secrets.GITHUB_APP_PRIVATE_KEY }}"
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

      - name: Deploy
        run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Deploy

  • Push to main or go to Actions → “Deploy to Fly.io” → “Run workflow”.
  • After the first deploy, update your GitHub App webhook URL to the Fly app URL:
    • https://<your-app>.fly.dev/webhooks
  • Check health:
    • curl https://<your-app>.fly.dev/healthz

Local development

bun install
bun run dev

Environment variables (use Fly secrets in production):

PORT=8080
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
GITHUB_WEBHOOK_SECRET=supersecret
SNAPSHOT_SIGNING_KEY=some-long-random-256-bit-string
MARKET_B=100

End-to-end example

  1. Comment on issue #123:
    /market create issue 123 deadline 2025-11-15
    
    The bot replies with the created market issue number.
  2. On the market issue, trade:
    /trade yes 10
    
    The bot replies with cost, slippage, new probability, remaining balance, and a transaction ID.
  3. Check your balance:
    /balance
    
    The bot replies with your current balance and positions.
  4. Resolution and payouts:
    • If #123 closes before the deadline, the market auto-resolves YES; winners get $1 per winning share.
    • If the deadline passes without closure, it resolves NO.

Files

  • src/ core TypeScript:
    • app.ts Bun server and /healthz
    • webhooks.ts GitHub webhook handlers (create, trade, balance, resolve)
    • lmsr.ts AMM functions
    • snapshot.ts signed market snapshot (with seq)
    • storage.ts read/write snapshot into issue body
    • market.ts market creation
    • ledger.ts signed ledger snapshot (balances, positions, seq)
    • resolution.ts outcome detection and payouts
  • Dockerfile container build
  • fly.toml Fly app config
  • .github/workflows/deploy.yml GitHub Actions deployment

Notes and limits

  • No selling/shorting; for binary markets, selling can be approximated by buying the opposite side. Proper shorting needs more accounting.
  • Concurrency: optimistic retries with backoff on snapshot write conflicts.
  • Rate limits are in-memory and reset on process restart.
  • Labels used for discovery and metadata: forecast-market, target-issue-<n>, deadline-YYYY-MM-DD. Discovery uses per_page=100; high-volume repos may need pagination tweaks.

Troubleshooting

  • Webhook 401: ensure GitHub App webhook secret matches GITHUB_WEBHOOK_SECRET.
  • Startup “unhealthy”: check /healthz for failed checks (e.g., private key format, missing env vars).
  • Actions deployment failing: verify FLY_API_TOKEN and that fly.toml has your app name.
  • “Market not open or missing snapshot”: the snapshot may be invalid or missing; check the market issue body for the signed block and verify your SNAPSHOT_SIGNING_KEY.

Roadmap

See TODO.md for planned features:

  • Governance (caps per user, conflict-of-interest flags)
  • Portfolio/leaderboard
  • Enhanced resolution policies
  • Observability/logging (request IDs)
  • Unit tests for LMSR and parsing

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 98.7%
  • Dockerfile 1.3%