Skip to content

Commit 9a4db75

Browse files
committed
Initial commit
0 parents  commit 9a4db75

23 files changed

+837
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Deploy to Fly.io
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Flyctl
16+
uses: superfly/flyctl-actions/setup-flyctl@v1
17+
18+
- name: Set Fly secrets
19+
run: |
20+
flyctl secrets set \
21+
GITHUB_APP_ID=${{ secrets.GITHUB_APP_ID }} \
22+
GITHUB_WEBHOOK_SECRET=${{ secrets.GITHUB_WEBHOOK_SECRET }} \
23+
SNAPSHOT_SIGNING_KEY=${{ secrets.SNAPSHOT_SIGNING_KEY }} \
24+
GITHUB_APP_PRIVATE_KEY="${{ secrets.GITHUB_APP_PRIVATE_KEY }}"
25+
env:
26+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
27+
28+
- name: Deploy
29+
run: flyctl deploy --remote-only
30+
env:
31+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Node
2+
node_modules/
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
pnpm-debug.log*
7+
8+
# Build output
9+
dist/
10+
coverage/
11+
12+
# TypeScript
13+
*.tsbuildinfo
14+
15+
# Env and secrets (never commit)
16+
.env
17+
.env.*
18+
!.env.example
19+
20+
# OS / editor cruft
21+
.DS_Store
22+
Thumbs.db
23+
*.swp
24+
*.swo
25+
26+
# Logs and temp
27+
logs/
28+
*.log
29+
tmp/
30+
temp/
31+
32+
# Fly.io / Docker
33+
.fly/
34+
.fly.*
35+
*.tar
36+
37+
# IDE
38+
.vscode/
39+
.idea/
40+
41+
# Test artifacts
42+
jest-test-results.json
43+
44+
# Repomix
45+
*repomix-output*

.repomixignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Add patterns to ignore here, one per line
2+
# Example:
3+
# *.log
4+
# tmp/

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
FROM node:20-alpine
3+
WORKDIR /app
4+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
5+
RUN npm ci || yarn || pnpm i
6+
COPY tsconfig.json ./tsconfig.json
7+
COPY src ./src
8+
RUN npm run build
9+
ENV NODE_ENV=production
10+
EXPOSE 8080
11+
CMD ["node", "dist/app.js"]

README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# ganttmarket
2+
3+
GitHub-native prediction market MVP that stores state in GitHub Issues. Deployable to Fly.io with a one-click GitHub Actions workflow.
4+
5+
## Features
6+
- Markets are Issues with a signed snapshot block (qYes, qNo, price, deadline, seq)
7+
- Create markets via comments: `/market create issue 123 deadline 2025-11-15`
8+
- Trade via comments: `/trade yes 10` or `/trade no 5`
9+
- Ledger Issue tracks balances and positions (signed, with seq)
10+
- Auto-resolution and payouts ($1 per winning share)
11+
- Simple in-memory rate limit: max 5 trades per user per minute
12+
- Minimal dependencies, no external DB
13+
- Dockerfile + fly.toml + Deploy-to-Fly GitHub Actions workflow
14+
15+
## Architecture at a glance
16+
- Storage: GitHub Issues (market issue + single “Forecast Ledger” issue per repo)
17+
- Identity: GitHub username from webhook events
18+
- Math: LMSR AMM with tunable liquidity `b`
19+
- Safety:
20+
- Signed JSON blocks prevent tampering
21+
- Optimistic concurrency with `seq` for both market snapshots and ledger
22+
- In-memory rate limiter for trade spam protection
23+
24+
## Commands
25+
- Create market (comment on any issue or market issue):
26+
- `/market create issue <number> deadline YYYY-MM-DD`
27+
- Trade (comment on market issue):
28+
- `/trade yes <shares>`
29+
- `/trade no <shares>`
30+
- Check balance and position in the current market:
31+
- `/balance`
32+
33+
## GitHub App setup
34+
1. Create a GitHub App:
35+
- Permissions:
36+
- Issues: Read & write
37+
- Metadata: Read
38+
- Webhooks: subscribe to `issues`, `issue_comment`
39+
2. Install the App on the target organization/repository.
40+
3. After deploying, set the webhook URL to:
41+
- `https://<your-fly-app>.fly.dev/webhooks`
42+
43+
## Fly.io deployment (via GitHub Actions)
44+
This repo includes `.github/workflows/deploy.yml` to build and deploy on push to `main` or manually via “Run workflow”.
45+
46+
### Prerequisites
47+
- Install Fly CLI locally and create the app:
48+
```bash
49+
flyctl apps create ganttmarket
50+
flyctl auth token
51+
```
52+
- In your GitHub repo, add the following Actions secrets:
53+
- `FLY_API_TOKEN`: from `flyctl auth token`
54+
- `GITHUB_APP_ID`: numeric App ID
55+
- `GITHUB_WEBHOOK_SECRET`: your webhook secret
56+
- `SNAPSHOT_SIGNING_KEY`: a strong random string (256-bit)
57+
- `GITHUB_APP_PRIVATE_KEY`: paste the PEM content of your app private key
58+
59+
### Workflow file
60+
`.github/workflows/deploy.yml`:
61+
```yaml
62+
name: Deploy to Fly.io
63+
64+
on:
65+
push:
66+
branches: [ main ]
67+
workflow_dispatch:
68+
69+
jobs:
70+
deploy:
71+
runs-on: ubuntu-latest
72+
steps:
73+
- name: Checkout
74+
uses: actions/checkout@v4
75+
76+
- name: Setup Flyctl
77+
uses: superfly/flyctl-actions/setup-flyctl@v1
78+
79+
- name: Set Fly secrets
80+
run: |
81+
flyctl secrets set \
82+
GITHUB_APP_ID=${{ secrets.GITHUB_APP_ID }} \
83+
GITHUB_WEBHOOK_SECRET=${{ secrets.GITHUB_WEBHOOK_SECRET }} \
84+
SNAPSHOT_SIGNING_KEY=${{ secrets.SNAPSHOT_SIGNING_KEY }} \
85+
GITHUB_APP_PRIVATE_KEY="${{ secrets.GITHUB_APP_PRIVATE_KEY }}"
86+
env:
87+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
88+
89+
- name: Deploy
90+
run: flyctl deploy --remote-only
91+
env:
92+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
93+
```
94+
95+
### Deploy
96+
- Push to `main` or go to Actions → “Deploy to Fly.io” → “Run workflow”.
97+
- After the first deploy, update your GitHub App webhook URL to the Fly app URL:
98+
- `https://<your-app>.fly.dev/webhooks`
99+
100+
## Local development
101+
```bash
102+
npm install
103+
npm run dev
104+
```
105+
Environment variables (use Fly secrets in production):
106+
```
107+
PORT=8080
108+
GITHUB_APP_ID=123456
109+
GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
110+
GITHUB_WEBHOOK_SECRET=supersecret
111+
SNAPSHOT_SIGNING_KEY=some-long-random-256-bit-string
112+
MARKET_B=100
113+
```
114+
115+
## Files
116+
- `src/` core TypeScript:
117+
- `app.ts` Express server
118+
- `webhooks.ts` GitHub webhook handlers (create, trade, balance, resolve)
119+
- `lmsr.ts` AMM functions
120+
- `snapshot.ts` signed market snapshot (with `seq`)
121+
- `storage.ts` read/write snapshot into issue body
122+
- `market.ts` market creation and trade application
123+
- `ledger.ts` signed ledger snapshot (balances, positions, `seq`)
124+
- `resolution.ts` outcome detection and payouts
125+
- `Dockerfile` container build
126+
- `fly.toml` Fly app config
127+
- `.github/workflows/deploy.yml` GitHub Actions deployment
128+
129+
## Notes and limits
130+
- No selling/shorting; positions are buy-only for now. Selling can be approximated by buying the opposite side in binary markets, but proper shorting needs more accounting.
131+
- Concurrency: `seq` is incremented on each write; for high-traffic repos, consider adding a retry loop that re-reads the latest snapshot if `seq` changed between read and write.
132+
- Rate limits are in-memory and reset on process restart.
133+
134+
## Troubleshooting
135+
- Webhook 401: ensure GitHub App webhook secret matches `GITHUB_WEBHOOK_SECRET`.
136+
- “Missing env” on startup: set all required secrets in Fly and/or local `.env`.
137+
- Actions deployment failing: verify `FLY_API_TOKEN` and that `fly.toml` has `app = "ganttmarket"` or your chosen app name.

TODO.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
2+
# TODO
3+
4+
## 1) Security and Integrity
5+
- Enforce signature verification on every read of snapshots and ledger
6+
- storage.readIssueSnapshot: verify with verifySnapshot; reject if invalid
7+
- ledger.readLedger: already verifies; keep consistent
8+
- Add optimistic concurrency retries (seq-based)
9+
- On trade: read -> compute -> write (seq+1); re-read and compare seq; retry N times (e.g., 3) on mismatch
10+
- Apply same pattern for ledger writes
11+
- Add write receipts for partial failures
12+
- If ledger updated but market snapshot write fails, post a bot comment noting partial update and suggesting retry/reconcile
13+
- Harden snapshot block parsing
14+
- Ensure only one snapshot block exists; if multiple, prefer the first and remove extras; always re-render at top of body
15+
16+
## 2) Permissions and Governance
17+
- Trading permissions
18+
- Restrict to repo collaborators or org members (octokit.repos.getCollaboratorPermissionLevel)
19+
- Optional: team-based allowlist via config
20+
- Conflict-of-interest flags
21+
- If commenter can close the target issue, flag their trades in comments and (optionally) cap trade size
22+
- Max exposure per user per market
23+
- Cap shares per side (e.g., 1,000) to limit manipulation
24+
25+
## 3) Market Discovery and Metadata
26+
- Label market issues on creation
27+
- Labels: `forecast-market`, `target-issue-<n>`
28+
- Store target linkage in snapshot (already present)
29+
- Fast lookup by label
30+
- In resolution and helpers, query by labels instead of scanning titles
31+
- Support multiple markets per target
32+
- Encode `marketId` in a label (e.g., `market-id:<id>`) and in the title to disambiguate
33+
34+
## 4) UX: Commands and Feedback
35+
- Add `/help` command listing supported commands and examples
36+
- Add `/sell yes|no <shares>` (optional)
37+
- MVP: model sell as buying the opposite side; display cost/refund at current price
38+
- Improve trade feedback
39+
- Show pre-trade and post-trade price, slippage, and remaining balance
40+
- Balance/portfolio
41+
- `/portfolio` to list all open market positions and total exposure
42+
- Leaderboard
43+
- Scheduled (GitHub Actions cron) job posts top balances to the Ledger Issue
44+
45+
## 5) PR Integration
46+
- For PR-targeted markets, update a "Forecast" check on relevant events
47+
- On trade and on PR sync, call checks.upsertForecastCheck with latest probability
48+
- Optionally display a badge in PR description via a bot comment that updates on trades
49+
50+
## 6) Resolution Improvements
51+
- Deadline handling
52+
- Use explicit timezone (UTC) and display local time hints
53+
- Manual override command
54+
- `/market resolve <yes|no> reason:"..."` with audit log in the market issue
55+
- Auto-resolve scheduler
56+
- GitHub Actions workflow that hits a maintenance endpoint or uses the API to scan and resolve past-deadline markets periodically
57+
58+
## 7) Reliability and Performance
59+
- Add retry with backoff on GitHub API 5xx / rate limits
60+
- Add minimal request queueing to avoid burst writes on hot markets
61+
- Cache repo metadata (collaborator permission levels) in-memory with TTL
62+
- Paginate issue listing calls (currently per_page=100) and/or filter via labels
63+
64+
## 8) Data Model Extensions
65+
- Record trade receipts
66+
- Append a small JSON receipt inside the market issue as a separate block or dedicated comments with a `trade-receipt` marker
67+
- Include: user, side, shares, cost, pre/post price, seq, timestamp
68+
- Transaction IDs
69+
- Include a monotonic `txId` field in comments to assist audit/reconciliation
70+
71+
## 9) Configuration and Policy
72+
- Repo-level config via a `.ganttmarket.yml`
73+
- startingCredits, b parameter, rate limits, allowed roles/teams, max shares
74+
- Environment variables validation with human-friendly errors
75+
- Toggle features via labels (e.g., `forecast-enabled`)
76+
77+
## 10) Testing and Tooling
78+
- Add unit tests for lmsr, parsing, and snapshot/ledger signing
79+
- Add integration tests using nock to stub GitHub API
80+
- Prettier/ESLint minimal setup for consistency
81+
82+
## 11) Observability
83+
- Structured logging (JSON) with request IDs and event types
84+
- Optional webhook delivery logging for debugging signature/headers
85+
- Health endpoint to verify env and GitHub App configuration (without secrets)
86+
87+
## 12) Documentation
88+
- Expand README with:
89+
- Resolution criteria and edge cases (reopen after deadline, edited titles)
90+
- Governance policies (who can trade, caps)
91+
- Security model (signed blocks, seq)
92+
- Troubleshooting guide (common webhook errors)
93+
94+
## 13) Enterprise/Privacy Options
95+
- Redact PII in logs (only show GitHub login when necessary)
96+
- Data residency note (all state is in GitHub; no external DB)
97+
- Optional export command `/export` to produce CSV summary of markets, trades, balances
98+
99+
## 14) Future Features
100+
- Conditional/scenario markets ("If +2 engineers, probability?")
101+
- Milestone and release markets with auto-deadlines from GitHub metadata
102+
- Slack/Teams notifications on big price moves
103+
- Visualization: tiny sparkline image rendered and posted as a comment (optional)

fly.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
app = "ganttmarket"
3+
primary_region = "iad"
4+
5+
[build]
6+
dockerfile = "Dockerfile"
7+
8+
[env]
9+
PORT = "8080"
10+
11+
[[services]]
12+
internal_port = 8080
13+
protocol = "tcp"
14+
[[services.ports]]
15+
handlers = ["http"]
16+
port = 80
17+
[[services.tcp_checks]]
18+
grace_period = "10s"
19+
interval = "15s"
20+
restart_limit = 0
21+
timeout = "2s"

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
{
3+
"name": "github-prediction-market",
4+
"version": "0.1.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "ts-node-esm src/app.ts",
8+
"start": "node dist/app.js",
9+
"build": "tsc"
10+
},
11+
"dependencies": {
12+
"@octokit/rest": "^20.0.0",
13+
"@octokit/webhooks": "^12.0.0",
14+
"@octokit/auth-app": "^6.0.0",
15+
"express": "^4.19.2",
16+
"dotenv": "^16.4.5"
17+
},
18+
"devDependencies": {
19+
"typescript": "^5.4.0",
20+
"ts-node": "^10.9.2"
21+
}
22+
}

0 commit comments

Comments
 (0)