1. Big‑Picture Architecture
| Layer | What it does | Key libs / services |
| Mobile client | Displays the tap game, records scores, captures player Lightning Address, polls for recent winners | React Native + Expo SDK; React Navigation; Reanimated; React‑Native‑Async‑Storage |
| Realtime DB & Auth | Stores rounds, scores, user profiles | Firebase (Firestore + Auth) |
| Payout API (serverless) | Verifies winner, calls Lightning API, logs tx | Cloud Functions (Node 18) |
| Lightning service | Actually sends the sats | ZBD (custodial, plug‑and‑play) OR self‑hosted LNbits (non‑custodial) |
| Analytics & anti‑cheat | Rate‑limits calls, flags anomalies | Firebase App Check, BigQuery |
Why Lightning? Instant, sub‑cent payments; fees ≪ $0.01; trivial to mail 50 sats (~0.02 ¢) per round — perfect for micro‑rewards .
2. Setting Up the Lightning Rail
2.1 ZBD (Fastest to Prototype)
- Create a free dev account → Developer Dashboard .
- Add a new Project, grab its API key.
- In Wallet > Gamertags, enable “Send via API”.
- POST /v0/charges to create invoices or /v0/withdrawal‑requests to push sats (see “Create Charge” doc) .
2.2 LNbits (Self‑Custody)
- Deploy LNbits (Docker/VPS) or use demo.lnbits.com.
- Create Wallet A (Admin) and Wallet B (Players).
- Use the /api/v1/payments/lnurl endpoint to pay any Lightning Address; you’ll need the admin key for sends and invoice key for decodes .
- Hash the LNURL metadata per the sample cURL before sending .
Either path gives you a single HTTPS call that moves real money in under a second. 🎉
3. Game Client – React Native + Expo
Project bootstrap
npx create-expo-app sats-tap –template
cd sats-tap
npx expo install @react-navigation/native react-native-reanimated expo-splash-screen
App.tsx (core loop, trimmed)
import React, {useState, useEffect} from ‘react’;
import {View, Text, Pressable, StyleSheet} from ‘react-native’;
import {getAuth} from ‘firebase/auth’;
import {addDoc, collection, serverTimestamp} from ‘firebase/firestore’;
import {db} from ‘./firebaseConfig’; // init separately
import {Dimensions} from ‘react-native’;
export default function App() {
const [score, setScore] = useState(0);
const [timeLeft, setTimeLeft] = useState(60);
useEffect(() => {
if (!timeLeft) return;
const id = setInterval(() => setTimeLeft(t => t – 1), 1000);
return () => clearInterval(id);
}, [timeLeft]);
const tap = () => setScore(s => s + 1);
const finishRound = async () => {
const user = getAuth().currentUser;
await addDoc(collection(db, ’rounds’), {
uid: user?.uid ?? ‘anon’,
score,
created: serverTimestamp()
});
setScore(0); setTimeLeft(60);
};
return (
<View style={styles.container}>
<Text style={styles.timer}>{timeLeft}s</Text>
<Pressable onPress={tap} style={styles.target}/>
<Text style={styles.score}>{score} taps</Text>
{!timeLeft && <Pressable onPress={finishRound}><Text>Submit!</Text></Pressable>}
</View>
);
}
const size = Dimensions.get(‘window’).width*0.6;
const styles = StyleSheet.create({
container:{flex:1,alignItems:’center’,justifyContent:’center’},
target:{width:size,height:size,borderRadius:size/2,backgroundColor:’#fdd835′},
timer:{fontSize:48,fontWeight:’bold’},
score:{fontSize:32,marginTop:20}
});
This 100‑line prototype was assembled with guidance from Expo’s “NeonCity” tutorial on sprite‑based games .
Collect Lightning Address
Add a simple settings screen where users paste you@wallet.com and save it in Firestore/users/{uid}/lightningAddress. Validate with BOLT‑11 decoders if you like (see StackOverflow thread) .
4. Cloud Function –
payoutBot.ts
import * as functions from ‘firebase-functions/v2’;
import fetch from ‘node-fetch’;
import {firestore} from ‘firebase-admin’;
export const settleRound = functions.pubsub.schedule(‘every 1 minutes’).onRun(async () => {
// 1️⃣ pick winning score in last minute
const since = Date.now() – 60*1000;
const snaps = await firestore()
.collection(’rounds’)
.where(‘created’,’>=’, new Date(since)).get();
if (snaps.empty) return null;
const winner = snaps.docs.sort((a,b)=>b.data().score – a.data().score)[0];
const { lightningAddress, uid } = (await firestore().doc(`users/${winner.data().uid}`).get()).data() || {};
if (!lightningAddress) return null;
// 2️⃣ pay 50 sats
const body = {address: lightningAddress, amount: 50}; // for ZBD
const res = await fetch(‘https://api.zebedee.io/v0/withdrawal-requests’, {
method:’POST’,
headers:{‘Content-Type’:’application/json’,’apikey’: process.env.ZBD_KEY!},
body: JSON.stringify(body)});
const json = await res.json();
// 3️⃣ log tx
return firestore().collection(‘payments’).add({uid, json, ts: Date.now()});
});
Swap the fetch URL/headers for the LNbits call shown earlier when self‑hosting. That’s all it takes to spray sats!
5. Compliance & Store‑Submission Tips
| Platform | Rule to watch | Take‑away |
| Apple | In‑app crypto transfers must not bypass IAP for content sales, but “free‑to‑enter skill contests” with cash prizes are permitted if legally compliant and not gambling (§5.3) | Clearly state that players never pay to enter; sats are prize money. |
| Google Play | “Blockchain‑based content” policy allows NFT or crypto rewards if no gambling and full odds/ratio disclosure ; real‑money contests must publish official rules (“Real‑Money Gambling” policy) | Display prize schedule (e.g., “Top score each round = 50 sats”) in Settings screen + Terms. |
Both stores also require you to block under‑age gambling, so gate 18+ features with a date‑of‑birth prompt.
6. Security, Fair‑Play & Costs
- Anti‑cheat: Cloud Function cross‑checks tap rate ≤ 16 taps/s (human max) and flags tallies beyond 960 taps/min.
- Rate‑limiting payouts: Use Firebase App Check so only your app can hit the REST endpoint.
- Wallet risk: With custodial ZBD you skip node ops but trust a third party; LNbits lets you run your own node (requires channel liquidity).
- Budget: 50 sats × 60 rounds/h ≈ 0.00003 BTC/day if 24h live – under US $1/day at $60k/BTC. Lightning fees are sub‑cent .
7. Level‑Up Ideas
- Unity port: ZBD offers a Unity SDK so you can export to iOS/Android in one go .
- Godot users: Drop‑in insert‑coin(s) asset lets you gate levels behind Lightning payments in Godot 4 .
- On‑chain jackpots: Accumulate 1 sat per ad view and pay the daily top‑10 via LNURL withdraw links.
- Social layer: Let spectators boost players mid‑round by zapping 5 sats; forward 4 to the player, keep 1 as fee.
8. Ship It! 🎉
You now have:
- A cross‑platform tap game that stores scores in Firebase.
- A cron‑driven Cloud Function that pays real Bitcoin to the winner every minute.
- A Lightning backend you can swap between ZBD (quick) and LNbits (sovereign).
- Compliance checkpoints for both major mobile stores.
Clone, customize the UI, crank up the hype, and watch players feel the jolt when sats land in their wallets! The future of fun is instant, global, and programmable – and you just built a piece of it. 🥳
Sources
- ZEBEDEE Developer Dashboard announcement
- LNbits Lightning‑address payment API sample
- LNbits Swagger docs (payments end‑points)
- React Native/Expo game tutorial (“NeonCity”)
- Lightning Address spec site
- React‑Native‑Lightning library (Synonymdev)
- StackOverflow – validating BOLT‑11 invoice in React Native
- Apple App Store Review Guidelines §5.3 (crypto payments)
- Google Play “Blockchain‑based Content” policy
- Google Play “Real‑Money Gambling, Games & Contests” policy