Skip to content

TianhangZhuzth/CandyDrop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🍭 Candy Drop

Candy Drop

Cut the rope. Feed the monster. Earn real Solana.
A skill-based cut-the-rope puzzle with a fair, on-chain play-to-earn layer on Solana mainnet.

Website X Play Now

Solana Mainnet Node 18+ Postgres Phaser 3 Railway

Candy Drop turns a beautifully simple physics puzzle into a fair, on-chain economy. Slice ropes, time the swing, and drop the candy into a hungry little monster — then watch pure skill become real SOL. Earn $CANDY as you play, climb the ranks, knock out daily quests, and claim your slice of a daily Solana prize pool straight to your Phantom wallet. No wagering. No pay-to-win. Just skill, holdings, and payouts you can verify on-chain — all wrapped in hand-drawn art and buttery-smooth Phaser physics.


📑 Table of contents


✨ Why it's special

🎯 Skill, not luck Rewards come from how well you play and how much you hold — never from wagering or randomness.
Real on-chain payouts Claims settle on Solana mainnet, signed by a server-side treasury, verifiable on Solscan.
🔑 Non-custodial You connect your own Phantom wallet. We never hold your keys or your funds.
🛡️ Server-authoritative The browser can't grant itself points. Every reward is decided and capped by the server.
🧱 Durable & crash-safe Postgres-backed, single-flight persistence, and a chain-reconciled claim flow that can't double-pay.
🎨 100% original art Hand-drawn mascot, candy, and themed backgrounds — generated/illustrated for this project.
Zero-asset engine extras Procedural textures, WebAudio SFX, and responsive layout — loads instantly.

🎮 How to play

  1. Cut the rope — swipe across a rope to slice it; the candy is released and physics takes over.
  2. Time the swing — the candy swings side-to-side. Wait for the right moment, then cut to fling it. You can also drag it to build momentum.
  3. Feed the monster — land the candy on the little green monster to clear the level. Grab all 3 stars for a perfect run.

Bubbles float the candy up; air pumps blow it sideways. Ten hand-built levels across five themed “boxes” mix these mechanics into escalating puzzles.


🪙 Play-to-earn: $CANDY → SOL

There are two things in play, and they're deliberately different:

What it is
🍬 $CANDY The in-game coin you earn by playing. It is the metric that decides your reward.
SOL Real Solana — what you actually claim to your wallet.

The model: every day there is a fixed SOL prize pool, shared across players. Your slice is decided by how much of the $CANDY coin you hold, as a percentage of supply — the more you hold, the bigger your share, up to a 3.5% cap.

your SOL today  =  daily pool  ×  min(your % of $CANDY supply, 3.5%)  ÷  Σ everyone's capped %
  • 🐋 Whale-proof — holding more than 3.5% of supply earns no extra, so a few big wallets can't drain the pool.
  • 🎮 You must play to earn — only wallets active that day share the pool. Holding alone isn't enough; holdings set the amount, playing sets eligibility.
  • 🔄 Pre-launch fallback — until the $CANDY token is minted, the pool is split by skill points instead, so the game already pays out. The moment a TOKEN_MINT is set, rewards switch to holdings automatically.

Settlement & claiming:

flowchart LR
  A["Play and earn $CANDY<br/>(all day)"] --> B["UTC midnight:<br/>day settles"]
  B --> C["Your share moves to<br/>Available to claim"]
  C --> D["Click Claim →<br/>real SOL to your wallet"]
  D --> E["Verifiable on<br/>Solscan"]
Loading

🧩 Quests & ranks

Daily quests (reset every UTC day) award bonus $CANDY and show on both the game screen and the dashboard:

Quest Goal Reward
Warm Up Clear 1 level +5
Sweet Tooth Clear 3 levels +15
On a Roll Clear 5 levels +25
Marathon Clear all 10 levels +40
Star Collector Collect 10 stars +20
Star Hoarder Collect 20 stars +35
Star Master Collect all 30 stars +50
Flawless Get a 3-star clear +25
Triple Threat Three 3-star clears +40

Ranks are a badge of lifetime $CANDY and never reset:

🥉 Bronze → 🥈 Silver → 🥇 Gold → 💠 Platinum → 💎 Diamond → 👑 Master


🛡️ Fairness & fund safety

Because real money is at stake, the backend is built to be hard to cheat and impossible to drain by accident:

Anti-cheat (server-authoritative):

  • The client never sets its own points — the server awards them.
  • Each level pays once per day; replays earn nothing.
  • A solve needs a single-use server session older than a minimum solve time → blocks instant bots & replays.
  • A hard per-wallet daily cap bounds how much anyone can earn.
  • The holdings 3.5% cap stops whales dominating the pool.

Fund safety (chain-reconciled claims):

  • The payout tx is signed locally first (signature known up-front), the balance is reserved and persisted before broadcasting, and on a confirmation timeout the chain is queried — a transfer that actually landed is never refunded (no double-pay), and only a provably failed/expired tx is refunded.
  • On boot, any claim left mid-flight by a crash is reconciled against the chain, so funds are never stuck.
  • Persistence is single-flight with retry; the process flushes on SIGTERM (redeploys), and settlement is durable so a crash can't double-credit.

These properties were adversarially reviewed (multi-agent audit) and the confirmed findings were fixed and re-tested.


🏗️ Architecture

flowchart TD
  subgraph Browser
    G["🎮 Phaser game<br/>(play.html)"]
    L["🏠 Landing + Docs"]
    D["📊 Dashboard"]
  end
  subgraph "Node server (one process)"
    S["Static host"]
    API["/api/* REST"]
    R["Rules: server-authoritative<br/>points, quests, ranks"]
    LED["Ledger: settlement<br/>+ chain-reconciled claims"]
  end
  W["👻 Phantom wallet"] -->|sign-in signature| API
  G & L & D --> S
  G & D -->|fetch| API
  API --> R --> store["💾 store.js"]
  API --> LED --> store
  store --> PG[("🐘 Postgres / JSON file")]
  LED --> TRE["🔐 Treasury wallet"]
  TRE -->|SOL payout| W
Loading

One Node process serves the frontend and the API, so it deploys as a single service. State is held in memory and persisted by store.js to Postgres (or a JSON file locally / on a volume).


🧰 Tech stack

Layer Tech
Game Phaser 3 + Matter.js physics, procedural textures, WebAudio SFX
Frontend Vanilla JS/CSS, responsive, no build step
Backend Node.js (no framework — a tight http server)
Chain @solana/web3.js, tweetnacl + bs58 for signature auth
Data PostgreSQL (JSONB) with a JSON-file fallback
Wallet Phantom (sign-in message + payouts)
Hosting Railway (Railpack, auto-deploy on push)

📁 Project structure

.
├── index.html            # landing page
├── play.html             # the game
├── dashboard.html        # earnings dashboard (rank, quests, holdings, claim)
├── docs.html             # in-app documentation
├── package.json          # root manifest (Railway build entry)
├── assets/               # original art: spritesheet, backgrounds, logos, icons
├── src/
│   ├── game.js           # Phaser scene: ropes, physics, win/lose, juice
│   ├── draw.js           # procedural textures + themed backgrounds + sprite wiring
│   ├── levels.js         # 10 levels in normalized coords, per-level themes
│   ├── wallet.js         # connect, sign-in, report solves, claim
│   ├── quests.js         # in-game daily-quest panel
│   ├── dashboard.js      # dashboard UI
│   └── landing.{js,css}  # marketing site
└── server/
    ├── server.js         # static host + /api router
    ├── config.js         # env loader (.env → process.env)
    ├── auth.js           # nonce + ed25519 signature verify + session tokens
    ├── rules.js          # points, quests, ranks, holdings tiers (anti-farm)
    ├── ledger.js         # daily settlement + chain-reconciled claims
    ├── solana.js         # treasury, payouts, holdings, signature status
    └── store.js          # durable Postgres/file store (single-flight, crash-safe)

💻 Run it locally

git clone https://github.com/ctrlshifthash/candydrop.git
cd candydrop

# install backend deps
cd server && npm install && cd ..

# configure secrets
cp server/.env.example server/.env      # then fill in the values

# run (serves the game AND the API on http://localhost:8080)
node server/server.js

Open http://localhost:8080. Without a TREASURY_SECRET the whole app still runs (play, points, dashboard) — only the claim button is disabled, so you can develop safely before funding a wallet.

Generate the session/admin secrets:

node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"

☁️ Deploy (Railway)

  1. New Project → Deploy from GitHub repoctrlshifthash/candydrop.
  2. Add a PostgreSQL plugin (Railway injects DATABASE_URL).
  3. Set the environment variables below.
  4. Railpack auto-detects Node from the root package.json → runs npm installnpm start (node server/server.js). No build command or root directory needed.
  5. Keep 1 replica (the store is single-writer) and enable Teardown so redeploys don't overlap.

✅ Live deployment: https://candydrop-production.up.railway.app


🔐 Environment variables

Variable Required Description
TREASURY_SECRET for payouts Treasury wallet secret key (base58). Lives only on the server.
SESSION_SECRET Long random string signing player session tokens.
ADMIN_TOKEN Gate for /api/admin/*.
RPC_URL Solana mainnet RPC (use a paid provider in production).
DATABASE_URL prod Postgres connection string (${{Postgres.DATABASE_URL}} on Railway).
REQUIRE_DB rec. true → refuse to boot on non-durable file storage.
DAILY_POOL_SOL Size of the daily pool, in SOL.
MIN_CLAIM_SOL Minimum claimable balance (keeps fees sane).
HOLDING_CAP_PCT Effective holding cap (default 3.5).
TOKEN_MINT optional $CANDY SPL mint. Empty = points fallback; set = holdings rewards.
MIN_WALLET_SOL optional Anti-sybil: minimum wallet balance to earn (0 = off).
PORT auto Injected by Railway — do not set.

🔒 Secrets live only in server/.env (git-ignored) or the host's env. server/.env.example documents every variable.


🔌 API reference

Method Endpoint Auth Purpose
GET /api/config Public config (network, treasury, quests, ranks, tiers)
GET /api/stats Live player count + SOL paid
GET /api/nonce?wallet= Login challenge to sign
POST /api/login Verify signature → session token
POST /api/level/start 🔑 Open a server-authoritative level session
POST /api/level/complete 🔑 Report a solve → server awards $CANDY
GET /api/me 🔑 Dashboard payload (rank, holdings, claimable, quests)
POST /api/claim 🔑 Claim settled SOL to your wallet
GET /api/leaderboard Top players by lifetime $CANDY
GET /api/admin/stats 🛡️ Treasury balance + aggregate stats

🗺️ Roadmap

  • Playable game + Phantom connect
  • Server-authoritative points, quests, ranks
  • Daily settlement + real mainnet claims
  • Durable Postgres store + crash-safe claims
  • Holdings-tier rewards (3.5% cap)
  • $CANDY token launch (flip on holdings mode)
  • On-chain leaderboard + seasons
  • Mobile apps & community level editor

🌐 playCandydrop.com  ·  𝕏 @playCandyDrop  ·  ▶ Play now

About

Cut the rope. Feed the monster. Earn real Solana. A skill-based play-to-earn puzzle on @Solana — play, stack $CANDY, claim SOL. No wagers. Just skill.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages