Alchemy Community NFT Raffle is a full-stack dApp that lets Alchemy University communities run transparent monthly giveaways. A Chainlink Functions + VRF powered smart contract proves fairness on-chain, while a Next.js dashboard handles admin tooling, entry audits, and winner announcements.
- How It Works
- Project Layout
- Prerequisites
- Quick Start
- Smart Contract Suite (
contracts/) - Frontend (
frontend/) - Chainlink Integrations
- API Utilities
- Verification & Auditing
- Troubleshooting & Notes
- Acknowledgements
- Admin launches a raffle for a specific month with the qualifying NFT token IDs.
- Chainlink Functions fetches live owner data from the Alchemy NFT API, returning total entries plus a commitment hash of all participants.
- Chainlink VRF v2.5 draws a provably fair winner index from those entries.
- Chainlink Functions is called again to resolve the shuffled winner wallet and store it on-chain.
- The Next.js site exposes public pages for the active raffle, past winners, and an admin-only control panel.
contracts/— Foundry project with theAlchemyRafflecontract, deployment script, and Chainlink integrations.frontend/— Next.js 16 app (App Router) with RainbowKit, Wagmi, and server routes for Alchemy/Chainlink interop.
- Node.js 20+ and npm
- Foundry toolchain (
forge,cast,anvil) — install via https://book.getfoundry.sh/getting-started/installation - An Alchemy (or compatible) RPC endpoint and NFT API key for your target network
- Chainlink Functions and VRF subscriptions funded on your target network
- WalletConnect Cloud project ID for RainbowKit (WalletConnect v2)
- Clone the repo
git clone https://github.com/leetebbs/Alchemy-Community-NFT-Raffle.git cd Alchemy-Community-NFT-Raffle - Configure environments
- Copy
contracts/.env.exampletocontracts/.envand fill in Chainlink + RPC details. - Create
frontend/.env.localwith the variables listed in Frontend.
- Copy
- Build contracts & launch frontend
cd contracts forge install forge build cd ../frontend npm install npm run dev
- Visit
http://localhost:3000to interact with the UI.
- Source:
src/AlchemyRaffle.solextendsFunctionsClientandVRFConsumerBaseV2Plusto coordinate entry retrieval and random selection. - Key features
- Tracks raffle rounds, entry counts, commitment hashes, and winner metadata.
- Calls Chainlink Functions twice: first to fetch entries + commitment, second to resolve the winner address.
- Uses Chainlink VRF v2.5 for randomness; winner index is stored before requesting the second Functions call.
- Deploy
- Populate
contracts/.env. - Run
forge script script/DeployAlchemyRaffle.s.sol:DeployAlchemyRaffle --rpc-url <network> --broadcast(seecontracts/README.mdfor network aliases and post-deploy checklist). - Authorize the deployed address on both the Chainlink Functions and VRF subscriptions.
- Populate
- Stack: Next.js 16 (App Router), Tailwind CSS (v4), RainbowKit, Wagmi + viem, React Query.
- Pages
/— Public landing page with live winner/raffle info pulled from the contract via API routes./admin— Wallet-gated controls to fetch eligible entries and triggerstartRafflefrom the connected admin wallet./pastWinners— Historical winners hydrated from the contractgetWinnerByRaffleIdview.
- Environment variables (create
frontend/.env.local)Optional extras:NEXT_PUBLIC_RAFFLE_CONTRACT_ADDRESS=0xYourDeployedContract NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your_walletconnect_project_id ALCHEMY_RAFFLE_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your_key ALCHEMY_API_KEY=your_polygon_nft_api_key CONTRACT_ADDRESS=0x8728F6f66ceAb2f092BBde42DaB380b97b349d19_alchemy_comunity_call_nft_contract_address
RAFFLE_CONTRACT_NETWORKif you expose multiple environments. - Development
npm run dev— start local Next.js server.npm run build/npm start— production build + serve.
- Wallet flow —
App.tsxwires RainbowKit + Wagmi (Sepolia chain). The admin actionstartRaffleencodes a transaction with viem’sencodeFunctionDataand sends it through the connected wallet.
- Functions
- Inline JavaScript requests live entry data from
https://alchemy-community-nft-raffle.vercel.app/api/FetchNumberOfEntries. - Response must include total entry count and a commitment hash to seal the participant set.
- Secondary request returns the shuffled winner address for the previously drawn index.
- Inline JavaScript requests live entry data from
- VRF v2.5
- Uses network-specific
keyHash,coordinator, and subscription IDs configured in the deployment script. fulfillRandomWordsstores a single random word, derives the winner index, and dispatches the follow-up Functions call.
- Uses network-specific
app/api/FetchNumberOfEntries— Server action that hits the Polygon NFT API, aggregates ownership counts, shuffles entries (optional), and mirrors the commitment logic used on-chain.app/api/FetchWinnerAddress— Reads the latest raffle struct from the contract and formats winner metadata for the landing page.app/api/FetchPastWinners— Iterates raffles via viem to build the public winners list.app/api/VerifyCommitmentHash— Recomputes commitment hashes from NFT data or provided entry arrays for transparency/audits.app/api/GetDiscordName— Server route that looks up Discord names from the community CSV file or Vercel Blob Storage by Ethereum address. Protected by API key in Authorization header.app/api/UploadCSV— Admin endpoint to upload updated Discord data CSV files to Vercel Blob Storage. Creates timestamped backups and maintains adiscord_data_latest.csvpointer for lookups.
The raffle system includes a Discord name lookup feature for admins to identify community members by their Ethereum address, with support for dynamic CSV uploads via Vercel Blob Storage.
CSV Data Sources
- Static (fallback):
frontend/public/data/Alchemy-Community-Call-Nov-26-2025_2025-11-26T19_50_06.csv- Default bundled CSV with community registration data
- Used if no dynamic upload exists
- Vercel Blob Storage (primary): Dynamic uploads stored in
discord_data_latest.csvpointer- Admin can upload new CSV files via the admin dashboard
- Each upload creates a timestamped backup (e.g.,
discord_data_1766001501572.csv) - Latest pointer always references the most recent upload
- Persistent across deployments
CSV Format Requirements
- Must be a
.csvfile - Required columns:
What is your Discord handle? - Discord Display Name— Discord usernameAt which Ethereum address would you like to receive the NFT?— Ethereum address
- Important: Remove leading/trailing whitespace from addresses to ensure reliable lookups
- Example format:
Submission Timestamp,What is your Discord handle? - Discord Display Name,At which Ethereum address would you like to receive the NFT? 2025-11-26T17:54:56.022Z,user123,0xb7e9ec6add9cbf3c0571df5746e54d0aa23932f5 2025-11-26T17:55:01.000Z,testuser,0x84Cb5f2F6bCBE2bF3fBc7F9D6B983432b4171ABB
Vercel Blob Storage Setup (Required for CSV Uploads)
- Enable Blob Storage in Vercel
- Go to your Vercel project dashboard → Storage → Create Database → choose Blob
- Name it (e.g.,
alchemy-community-nft-raffle-blob)
- Add Environment Variable to Vercel
- In Vercel project Settings → Environment Variables
- Add:
BLOB_READ_WRITE_TOKEN - Copy the token from Vercel Blob Storage dashboard
- Apply to: Production, Preview, and Development
- Re-deploy or trigger a new build for changes to take effect
- Local Development
- For local testing, Blob operations are skipped gracefully (falls back to static CSV)
- Full Blob functionality only active when deployed to Vercel
API Endpoint: GET /api/GetDiscordName
- Query Parameters:
address(Ethereum address to look up) - Authentication: Bearer token in
Authorizationheader- Security: API key authentication prevents unauthorized access to community Discord data
- Header format:
Authorization: Bearer <DISCORD_API_KEY> - Server validation: Compares against
DISCORD_API_KEYenvironment variable - Frontend integration: Uses
NEXT_PUBLIC_DISCORD_API_KEYto authenticate requests - Protection scope: Guards all Discord name lookups, including manual queries and automated last winner lookups
- Response:
{ "discordName": "username", "ethAddress": "0x..." } - Error codes:
400- Missing address parameter401- Unauthorized (invalid API key or missing Authorization header)404- Address not found in CSV500- Server error or no CSV data available
- Lookup Logic:
- Validates Authorization header against server-side
DISCORD_API_KEY - Attempts to fetch
discord_data_latest.csvfrom Vercel Blob Storage - Falls back to static bundled CSV if Blob fetch fails
- Trims whitespace from addresses before comparison (handles CSV formatting issues)
- Validates Authorization header against server-side
API Endpoint: POST /api/UploadCSV
- Authentication: Bearer token in
Authorizationheader- Security: API key authentication prevents unauthorized CSV uploads
- Header format:
Authorization: Bearer <DISCORD_API_KEY> - Admin-only access: Protects community data integrity by restricting uploads to authorized admins
- Form Data:
file(multipart form,.csvfile only) - Response:
{ "success": true, "message": "CSV uploaded successfully", "filename": "discord_data_1766001501572.csv", "timestamp": 1766001501572, "url": "https://...", "latestUrl": "https://..." } - Error codes:
400- No file or invalid file type (must be.csv)401- Unauthorized (invalid API key or missing Authorization header)500- Upload failed
- Upload Behavior:
- Creates timestamped blob file:
discord_data_<timestamp>.csv - Deletes existing
discord_data_latest.csv(prevents conflicts) - Creates new
discord_data_latest.csvpointer to latest upload - Subsequent lookups automatically use the new data
- Old timestamped files retained as backups
- Creates timestamped blob file:
Environment Variables
NEXT_PUBLIC_DISCORD_API_KEY=your_secret_key # Used by frontend to authenticate API requests
DISCORD_API_KEY=your_secret_key # Server-side validation (must match NEXT_PUBLIC_DISCORD_API_KEY)
BLOB_READ_WRITE_TOKEN=<vercel_blob_token> # Vercel Blob Storage token (Vercel deployment only)Security Best Practices
- API Key Protection: The
DISCORD_API_KEYacts as a Bearer token to prevent unauthorized access to Discord lookup and CSV upload endpoints - Dual Environment Variables:
NEXT_PUBLIC_DISCORD_API_KEY— Client-side key for frontend requests (exposed to browser)DISCORD_API_KEY— Server-side validation key (private, not exposed to client)- Both must match for authentication to succeed
- Key Rotation: Change both keys periodically and update in Vercel environment variables
- Rate Limiting: Consider implementing rate limiting on
/api/GetDiscordNamefor production deployments - HTTPS Only: Always use HTTPS in production to protect API keys in transit
Admin Page Integration
- Lookup Discord Name:
- Enter an Ethereum address in the "Lookup Discord Name" section
- Click "Lookup Discord Name" to retrieve the associated Discord handle
- Results display both the Discord name and verified Ethereum address
- Requests are secured with API key authentication
- Upload Discord Data CSV:
- Click "Choose File" in the "Upload Discord Data CSV" section
- Select an updated CSV file with community data
- Click "Upload CSV" to upload to Blob Storage
- Success confirmation shows upload URL and timestamp
- Latest upload automatically becomes the active dataset for lookups
- Last Winner:
- Click "Last Winner" to fetch the most recent raffle winner's Discord name
- Combines smart contract winner data with Discord lookup
- Commitment hash proofs — Compare the hash emitted during
startRafflewithVerifyCommitmentHashresponses to ensure the entry list was unchanged. - Admin safeguards — The admin page double-checks wallet ownership before exposing controls.
- Ensure the NFT collection lives on the same network as the Alchemy NFT API you query (defaults to Polygon mainnet).
- Winner resolution is asynchronous; expect ~3 minutes between raffle start and final on-chain winner data depending on oracle fulfilment.
- Blob Storage & CSV Uploads:
- CSV uploads only work when deployed to Vercel (requires
BLOB_READ_WRITE_TOKEN) - Local development uses static fallback CSV
- Ensure CSV addresses have no leading/trailing whitespace for reliable lookups
- Upload creates timestamped backups; old files are retained in Blob Storage for audit purposes
- If upload fails on Vercel, verify
BLOB_READ_WRITE_TOKENis set in all environments (Production, Preview, Development)
- CSV uploads only work when deployed to Vercel (requires
- Built with the Alchemy University community in mind.
- Chainlink Functions & VRF teams for oracle infrastructure.
- RainbowKit, Wagmi, and viem for wallet & chain tooling.