{"id":43228897,"url":"https://github.com/danieldunderfelt/btc-hivemind","last_synced_at":"2026-02-01T10:01:58.061Z","repository":{"id":292675450,"uuid":"981452763","full_name":"danieldunderfelt/btc-hivemind","owner":"danieldunderfelt","description":"Guess the price of BTC after one minute.","archived":false,"fork":false,"pushed_at":"2025-05-23T14:18:21.000Z","size":1231,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-23T15:56:50.499Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danieldunderfelt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-11T06:16:30.000Z","updated_at":"2025-05-23T14:18:24.000Z","dependencies_parsed_at":"2025-05-11T14:35:15.350Z","dependency_job_id":"364cf83d-46e0-4633-9ab9-62ea6ac83f0e","html_url":"https://github.com/danieldunderfelt/btc-hivemind","commit_stats":null,"previous_names":["danieldunderfelt/btc-hivemind"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danieldunderfelt/btc-hivemind","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieldunderfelt%2Fbtc-hivemind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieldunderfelt%2Fbtc-hivemind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieldunderfelt%2Fbtc-hivemind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieldunderfelt%2Fbtc-hivemind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danieldunderfelt","download_url":"https://codeload.github.com/danieldunderfelt/btc-hivemind/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieldunderfelt%2Fbtc-hivemind/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28975278,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T09:57:52.632Z","status":"ssl_error","status_checked_at":"2026-02-01T09:57:49.143Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-01T10:01:54.413Z","updated_at":"2026-02-01T10:01:58.054Z","avatar_url":"https://github.com/danieldunderfelt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BitFlip\n\n[Try the app!](https://bitflip.verycool.dev)\n\nBitFlip is an experiment to figure out if the wisdom of crowds can predict the direction of the price of Bitcoin more accurately than a coin flip. Users guess if the price of Bitcoin is higher or lower after one minute. If they are right, they get a point. If they are wrong, they lose a point.\n\n## How it works, technically\n\n1. The user logs in\n2. The user makes a guess whether the price of Bitcoin will go up or down.\n3. The time and BTC price is recorded as a Guess in the guesses table.\n  a. If the user has an unresolved guess, any new guesses are not recorded.\n4. An event is sent to the SQS queue, delayed by one minute.\n5. The queue subscriber receives the event and fetches a new price for BTC.\n  a. In case the price is exactly the same as the guess, it tries to fetch a new prive up to three times. After that the guess will be resolved with a coin flip if the price is still identical.\n6. The guess resolution is recorded in the guess_resolutions table\n7. After a minute has passed since the guess, the client starts polling for a resolution.\n  a. This works very well, a realtime subscription was not necessary.\n8. Once the client receives a new, resolved version of the guess, it shows the \"correct\" or \"incorrect\" status.\n9. The client fetches a list of all resolved guesses and calculates the user points based on the realized guess resolutions.\n  a. A dedicated trpc route for this can easily be added, but it has not been necessary for the current set of requirements.\n  b. Guesses are mainly queried from the resolved_guesses and all_guesses views. These conveniently combine the relevant data from guesses and guess_resolutions.\n\n## Technology\n\nThe application is implemented with the following stack:\n\n### General\n\n- Typescript\n- Better-auth\n- SST infra-as-code\n- tRPC for client\u003c-\u003eserver requests\n- Zod type schemas\n- Bun (production Lambdas still use Node)\n\n### Frontend\n\n- React\n- Vite\n- TailwindCSS (and Shadcn components)\n- React-query\n- Tanstack Router\n- Biome (linting and formatting)\n- Motion (previously known as Framer-Motion)\n- Lucide icons\n\n### Backend\n\n- AWS Lambda (Bun locally) for server-side logic\n- AWS Aurora Serverless Postgres\n- AWS SQS\n- Nodemailer and an SMTP server for auth emails\n- Drizzle ORM\n- Hono HTTP server\n\n### API\n\nThe app uses the [CryptoCompare API](https://developers.coindesk.com/documentation/legacy/Price/SingleSymbolPriceEndpoint) for fetching the Bitcoin price. I tried multiple alternatives, and this one worked the best. Many API's had a minute as the best price update cadence, CryptoCompare has 10 seconds (AFAIK). A realtime API would have been pretty nice, but those seemed to be quite expensive.\n\n## Development\n\nThe project uses SST, and you need to authenticate with an AWS user who is authorized to access the organisation.\n\n### Local prerequisites\n\n- Bun\n- Docker (or Orbstack on Macs)\n- AWS CLI (and SSO configs)\n\n### Start the project locally\n\n1. Start a local Postgres database with Docker:\n\n```\ndocker run -d \\\n  --rm \\\n  -p 5432:5432 \\\n  -v $(pwd)/.sst/storage/postgres:/var/lib/postgresql/data \\\n  -e POSTGRES_USER=postgres \\\n  -e POSTGRES_PASSWORD=password \\\n  -e POSTGRES_DB=local \\\n  postgres:16.4\n```\n\n2. Login with AWS:\n\n```\naws sso login --sso-session=dunderfeltconsulting\n```\n\n3. After that, the local stack should start simply with:\n\n```\nbun sst dev\n```\n\nI have set the necessary secrets in the project already. They _should_ just work 🤞\n\nAfter SST has done it's thing, you can visit the local frontend at [http://localhost:5173](http://localhost:5173).\n\nThe app does not send auth emails when running locally, but it will log the OTP code in the dev_server console when you log in. Use any email address.\n\n### Deployment\n\nTo deploy a new version of the app, run:\n\n```\nbun sst deploy --stage production\n```\n\nThis command should take care of everything. Push-to-deploy can be set up with the SST console, but this has not yet been enabled.\n\n### Database migrations\n\nThe project uses Drizzle ORM for interacting with the database. When the schema (in db/schemas) is updated, a new migration needs to be generated.\n\n1. Start the sst shell, which gives access to environment variables: `bun sst shell`\n2. In the shell, run `bun drizzle-kit generate` to generate a new migration.\n3. Then run `bun drizzle-kit migrate` to apply the changes.\n\nIn production, there is a migration Lambda that applies the migrations to the production database. This runs automatically on `sst deploy`, but needs the migration files present to have any effect.\n\n### Notes\n\n- The resolution time is 10 seconds locally, and 60 seconds in production. This is to make it more ergonomic to test the app locally.\n- CryptoCompare does not require an API key for development. The API key is only used in production.\n\n## Roadmap\n\nThe MVP works as specced, but I would like to explore the following roadmap:\n\n- Better design, more animations, a spinning 3D Bitcoin when the user makes a guess.\n- Ability to enter a guess immediately and log in while it waits for resolution.\n- Investigate the need and benefit of realtime subscriptions for guess states.\n- A realtime BTC price API.\n- Cache BTC price with a KV store (currently in-memory which is of limited benefit in Lambdas).\n- Statistics of how well (or poorly) Bitflip users are able to predict the market. This needs more users to be interesting.\n- Paginated list of resolved guesses. This would also require calculating the score server-side.\n\n## Challenges\n\n- Finding a working set of infra was a challenge, since the SST tool and most AWS services are new to me. I had to start from a clean slate a few times when the infra got into a knot (some times were skill issues, but other times were definitely on AWS). Removing all services and re-deploying takes 15-30 minutes.\n- Initially the server-side URL was not https (using an EC2 cluster), so I needed to completely restructure the server-side infra around Lambdas.\n- I had hoped I would not need a domain, but CORS and cookie issues (and the above https issue) made it the path of least resistance.\n- Finding a good API for the price of Bitcoin was not straight-forward. Many API's only provide price updates by the minute, which is borderline unusable for the app. Some API's just did not return correct data. CryptoCompare seems to work well enough, and they say the data is cached for 10 seconds.\n- The AWS SQS queue did not want to work without providing AWS credentials. This is counter to the SST docs, but I do not see a way around it. It does need the \"session token\" which sounds like it will stop working at some point, but I will monitor the situation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanieldunderfelt%2Fbtc-hivemind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanieldunderfelt%2Fbtc-hivemind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanieldunderfelt%2Fbtc-hivemind/lists"}