A trustless, automated, Bitcoin-based raffle system that uses Bitcoin's blockchain to provide transparent and verifiable raffle drawings that cannot be manipulated.
- Upload Participants: Upload a CSV file containing participant tickets
- IPFS Storage: The system uploads the CSV file to IPFS and converts the CID to hex format
- External Transaction: Create a Bitcoin transaction (in your own wallet) with the hex CID in an OP_RETURN output, and submit the transaction ID to the app
- Transaction Monitoring: The app monitors the Bitcoin transaction until it's confirmed in a block and retrieves the blockhash
- Drawing Winners: The specified number of winners are determined using BIP32 key derivation. The blockhash serves as the seed, and winners are selected based on calculations involving the derived public keys.
- Verification: Anyone can verify the winners by replicating the BIP32 derivation and calculations using the public blockhash and participant list.
-
Go to the Send tab
-
In the Pay to field, enter the following format:
script(OP_RETURN <CID_hex_format>)
Example:
script(OP_RETURN 6261666b72656966753377663376717a347537756b747a65367465326c78623537656b777267336e67646a6c346b74676f6167356a667775646769)
-
Click Pay...
-
Select an appropriate transaction fee (consider network congestion for timing)
-
Click Sign to authorize the transaction
-
Click Broadcast to send the transaction to the network
-
Copy the Transaction ID (txid) to the app for monitoring confirmation status
Example:
6a37d795e771aa88e9e9302dab8edecc9bacf7c77e18c822c6d249e8559fc002
- Node.js 14+ (16+ recommended)
- npm 6+ (7+ recommended)
- Bitcoin wallet (for creating the transaction)
- Internet connectivity to access the Bitcoin API
- Pinata JWT API key for IPFS storage
- Clone the repository:
git clone https://github.com/yourusername/bitRaffle.git
cd bitRaffle
- Install dependencies:
npm run install-all
- Configure environment variables:
Create a
.env
file in the server directory:
PORT=5000
NODE_ENV=development
PINATA_JWT_KEY=your_pinata_jwt_key_here
- Start the application:
npm start
- Access the application at http://localhost:3000
Alternatively, you can run the application using Docker and Docker Compose.
Prerequisites:
- Docker installed
- Docker Compose installed
Steps:
-
Configure Environment Variables: Ensure you have a
.env
file in theserver/
directory with yourPINATA_JWT_KEY
, as described in the Installation section. The Docker setup mounts the local directory, so this file will be used by the container.# server/.env PORT=5000 NODE_ENV=development PINATA_JWT_KEY=your_pinata_jwt_key_here
-
Build the Docker Image:
docker compose build
-
Run the Application:
docker compose up
This command will start both the client and server services.
-
Access the Application:
- Client UI: http://localhost:3000
- Server API (for client requests): http://localhost:5000
The CSV file should contain participant information with one ticket per line. The first line should be a header row.
Example:
name
John Doe
Jane Smith
Bob Johnson
The raffle's results are verifiable using the confirmed transaction's block hash and the participant list. The process leverages BIP32 Hierarchical Deterministic Wallets, using the block hash as the seed.
-
Obtain Data:
- Get the Block Hash of the block containing the raffle transaction (this can be found on any Bitcoin block explorer using the transaction ID).
- Get the Participant List (CSV file) used for the raffle. Note the total Participant Count.
- Note the Number of Winners drawn.
-
BIP32 Derivation:
- Use the Block Hash as the BIP39 Seed (in hex format).
- Derive keys using the BIP44 path structure:
m/44'/0'/0'/0/i
, wherei
starts at0
and increments for each winner to be drawn. - For each derivation path, obtain the corresponding Public Key.
-
Calculate Winner Index:
- For each derived Public Key, take the last 8 characters of its hexadecimal representation.
- Convert these 8 characters to an integer.
- Calculate the winner index using the formula:
winner_index = int(publicKey[-8:], 16) mod participant_count
- The participant at this
winner_index
(0-based) in the list is the potential winner for this derivation step.
-
Handle Duplicates:
- If a participant is selected more than once, the derivation index
i
is incremented, and a new public key is derived until a unique winner is found. This ensures each participant can only win once per raffle.
- If a participant is selected more than once, the derivation index
-
Verify with Ian Coleman Tool:
- You can cross-reference the derivation process using the Ian Coleman BIP39 tool.
- Paste the Block Hash (hex) into the "BIP39 Seed" field.
- Select the "BIP44" tab for the derivation path.
- Under "Derived Addresses", observe the public keys generated for paths
m/44'/0'/0'/0/0
,m/44'/0'/0'/0/1
, etc. - Compare these public keys and perform the calculation described in step 3 to verify each winner selection.
- Server: Express.js with Node.js
- Client: React with Bootstrap
- Blockchain: Bitcoin Mainnet/Testnet (configurable)
- Storage: IPFS distributed file system via Pinata
- APIs: BlockCypher for blockchain data
- Your Bitcoin private keys stay in your wallet - the app never handles them
- Consider privacy implications when storing participant data
- Ensure CSV files are properly formatted to avoid errors
- The app uses real blockchain data for drawing winners, not simulated values
To deploy bitRaffle to a demo or production environment, consider the following:
-
Environment Variables: Configure the necessary environment variables in your deployment environment. These are typically set through the hosting provider's interface.
server/.env
:NODE_ENV
: Set toproduction
for optimized performance and security.PINATA_JWT_KEY
: Required. Your secret JWT key from Pinata for IPFS uploads.PORT
: Your hosting provider might set this automatically. The server will useprocess.env.PORT
if available, otherwise defaulting to5000
.BLOCKCYPHER_NETWORK
: (Optional) Set tomain
ortestnet
. Defaults tomain
.BLOCKCYPHER_API_BASE_URL
: (Optional) Override the default BlockCypher API endpoint (https://api.blockcypher.com/v1
).PINATA_PUBLIC_GATEWAY_BASE
: (Optional) Set the base URL for public IPFS links generated (e.g.,https://ipfs.io/ipfs
). Defaults tohttps://gateway.pinata.cloud/ipfs
.
- Client Build:
REACT_APP_API_URL
: Required. The full URL to your deployed backend API (e.g.,https://your-demo-site.com/api
). This needs to be available during the client build process.
-
Build Client: The React frontend needs to be built into static assets.
cd client # Set the API URL environment variable for the build REACT_APP_API_URL=https://your-backend-url/api npm run build cd ..
The static files will be in
client/build/
. -
Server Deployment: Deploy the
server/
directory.- Ensure only production dependencies are installed (
npm ci --only=production
inside theserver
directory). - Start the server using
node index.js
. - Make sure the server can access the configured
PORT
.
- Ensure only production dependencies are installed (
-
Serving Client Assets: The static client assets (
client/build/
) need to be served.- Option A: Configure the Node.js server (in
server/index.js
) to serve static files from theclient/build
directory. - Option B: Use a reverse proxy (like Nginx or your hosting platform's equivalent) to serve the static files and route API requests (e.g.,
/api/*
) to the Node.js backend.
- Option A: Configure the Node.js server (in
-
Dockerfile for Production: If using Docker, adapt the
Dockerfile
anddocker-compose.yml
for production.- Use a multi-stage build: Stage 1 builds the client, Stage 2 copies server code, installs production dependencies, copies client build artifacts, and sets the
CMD
tonode index.js
. - Do not mount local volumes for code in production.
- Ensure environment variables are passed correctly to the container.
- Use a multi-stage build: Stage 1 builds the client, Stage 2 copies server code, installs production dependencies, copies client build artifacts, and sets the
MIT