Skip to main content

Quickstart Guide

Get Cajas up and running on your local machine in just a few minutes. This guide walks you through cloning the repository, setting up Supabase, and opening your first case.

Prerequisites

Before you begin, make sure you have:
  • Node.js 20.x or later installed
  • npm, yarn, pnpm, or bun package manager
  • A Supabase account (free tier works perfectly)
  • Git for cloning the repository
This guide uses npm commands, but you can substitute with your preferred package manager (yarn, pnpm, or bun).

Installation Steps

1

Clone the repository

Clone the Cajas repository to your local machine:
git clone https://github.com/yourusername/cajas-app.git
cd cajas-app
This creates a local copy of the project in the cajas-app directory.
2

Install dependencies

Install all required npm packages:
npm install
The project includes these key dependencies:
  • next (16.0.6) - React framework
  • @supabase/supabase-js (2.86.0) - Supabase client
  • @supabase/ssr (0.8.0) - Server-side rendering support
  • framer-motion (12.23.25) - Animations
  • react-hook-form (7.68.0) - Form handling
  • zod (4.1.13) - Schema validation
3

Set up Supabase project

Create a new Supabase project:
  1. Go to supabase.com and sign in
  2. Click New Project
  3. Choose a name, database password, and region
  4. Wait for the project to provision (takes ~2 minutes)
Once ready, navigate to Project SettingsAPI and copy:
  • Project URL (starts with https://)
  • anon public key (safe to use in client-side code)
4

Configure environment variables

Create a .env.local file in the project root:
touch .env.local
Add your Supabase credentials:
.env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
Never commit .env.local to version control. The file is already in .gitignore.
These environment variables are used throughout the app:
lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
    const cookieStore = await cookies()

    return createServerClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
        {
            cookies: {
                getAll() {
                    return cookieStore.getAll()
                },
                setAll(cookiesToSet) {
                    try {
                        cookiesToSet.forEach(({ name, value, options }) =>
                            cookieStore.set(name, value, options)
                        )
                    } catch {
                        // The `setAll` method was called from a Server Component.
                    }
                },
            },
        }
    )
}
5

Run database migrations

Apply the database schema to your Supabase project:Option 1: Using Supabase Dashboard (Recommended)
  1. Open your Supabase project dashboard
  2. Navigate to SQL Editor
  3. Run each migration file in order:
Start with the initial schema:
supabase/migrations/20240101000000_init.sql
-- Enable UUID extension
create extension if not exists "uuid-ossp";

-- USERS (Public Profiles)
create table public.users (
  id uuid references auth.users not null primary key,
  username text unique,
  avatar_url text,
  balance numeric default 0,
  client_seed text,
  nonce integer default 0,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- Enable RLS
alter table public.users enable row level security;

-- Policies for users
create policy "Public profiles are viewable by everyone."
  on public.users for select
  using ( true );

create policy "Users can insert their own profile."
  on public.users for insert
  with check ( auth.uid() = id );

create policy "Users can update own profile."
  on public.users for update
  using ( auth.uid() = id );
Then run the cases system migration:
supabase/migrations/0000_create_cases_system.sql
-- Create cases table
CREATE TABLE IF NOT EXISTS cases (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  description text,
  price numeric NOT NULL CHECK (price >= 0),
  image_url text NOT NULL,
  created_at timestamptz DEFAULT now()
);

-- Create case_items table
CREATE TABLE IF NOT EXISTS case_items (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  case_id uuid REFERENCES cases(id) ON DELETE CASCADE,
  name text NOT NULL,
  value numeric NOT NULL CHECK (value >= 0),
  image_url text NOT NULL,
  probability numeric NOT NULL CHECK (probability > 0 AND probability <= 100),
  created_at timestamptz DEFAULT now()
);

-- Enable RLS
ALTER TABLE cases ENABLE ROW LEVEL SECURITY;
ALTER TABLE case_items ENABLE ROW LEVEL SECURITY;

-- RLS Policies
CREATE POLICY "Public read cases" ON cases FOR SELECT USING (true);
CREATE POLICY "Public read case_items" ON case_items FOR SELECT USING (true);
Finally, add the provably fair system:
supabase/migrations/20251209000000_create_provably_fair.sql
-- Create user_seeds table
create table public.user_seeds (
  user_id uuid references auth.users not null primary key,
  server_seed text not null,
  client_seed text not null,
  nonce bigint not null default 0,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
  updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- Enable RLS
alter table public.user_seeds enable row level security;

-- Policies for user_seeds
create policy "Users can view their own seeds"
  on public.user_seeds for select
  using (auth.uid() = user_id);

create policy "Users can update their own seeds"
  on public.user_seeds for update
  using (auth.uid() = user_id);

-- Create game_rolls table (audit log)
create table public.game_rolls (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references auth.users not null,
  case_id uuid references public.cases not null,
  server_seed text not null,
  client_seed text not null,
  nonce bigint not null,
  roll_result bigint not null,
  item_won_id uuid not null,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- Enable RLS
alter table public.game_rolls enable row level security;

-- Policies for game_rolls
create policy "Users can view their own rolls"
  on public.game_rolls for select
  using (auth.uid() = user_id);
Run migrations in numerical order. Each migration builds upon the previous one.
Option 2: Using Supabase CLIIf you have the Supabase CLI installed:
supabase link --project-ref your-project-ref
supabase db push
6

Start the development server

Start the Next.js development server:
npm run dev
The server starts at http://localhost:3000.You should see output like:
▲ Next.js 16.0.6
- Local:        http://localhost:3000
- Environment:  development

✓ Ready in 2.3s
7

Create a test user

Open your browser to http://localhost:3000 and:
  1. Click Sign Up or Login in the navigation bar
  2. Create a new account with your email and password
  3. Check your email for a confirmation link (in development, check Supabase dashboard → Authentication → Users)
  4. Confirm your email to activate the account
In development mode, Supabase sends confirmation emails. Check the AuthEmail Templates in your Supabase dashboard to customize these.
Upon successful signup, a user profile is automatically created via this database trigger:
-- Function to handle new user creation
create or replace function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = public
as $$
begin
  insert into public.users (id, username, avatar_url)
  values (new.id, new.raw_user_meta_data ->> 'full_name', new.raw_user_meta_data ->> 'avatar_url');
  return new;
end;
$$;

-- Trigger for new user
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
8

Create and open your first case

Now let’s create a test case:Create an Admin UserFirst, promote your user to admin in the Supabase dashboard:
  1. Go to SQL Editor
  2. Run this query (replace with your user ID):
-- Add role column if it doesn't exist
ALTER TABLE users ADD COLUMN IF NOT EXISTS role text DEFAULT 'user';

-- Promote user to admin
UPDATE users SET role = 'admin' WHERE id = 'your-user-id-here';
Create a CaseNavigate to http://localhost:3000/admin/create-case and create a case with:
  • Name: Starter Box
  • Price: 1000
  • Description: Your first case
  • Image URL: Any image URL or upload
Add items to the case with different probabilities (must total 100%):
  • Common Item (50% probability, value: 500)
  • Rare Item (30% probability, value: 1500)
  • Epic Item (15% probability, value: 3000)
  • Legendary Item (5% probability, value: 10000)
Open the Case
  1. Navigate back to the homepage
  2. You’ll see your “Starter Box” case
  3. Click on it to open the case page
  4. Click Open Case to run your first opening!
Congratulations! You’ve successfully set up Cajas and opened your first case.
The case opening animation uses a provably fair algorithm. Here’s how it works:
app/api/cases/open/route.ts
export async function POST(request: Request) {
    const supabase = await createClient()
    const { data: { user } } = await supabase.auth.getUser()

    if (!user) {
        return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    const { caseId, clientSeed } = await request.json()

    // Fetch case items
    const { data: caseItems } = await supabase
        .from('case_items')
        .select('*')
        .eq('case_id', caseId)

    // Get user seeds
    let { data: seeds } = await supabase
        .from('user_seeds')
        .select('*')
        .eq('user_id', user.id)
        .single()

    // Calculate winner using HMAC-SHA256
    const nonce = seeds.nonce + 1
    const rollValue = calculateRollResult(seeds.server_seed, seeds.client_seed, nonce)
    const winnerItem = getWinningItem(caseItems, rollValue)

    // Record the roll for verification
    await supabase.from('game_rolls').insert({
        user_id: user.id,
        case_id: caseId,
        server_seed: seeds.server_seed,
        client_seed: seeds.client_seed,
        nonce: nonce,
        roll_result: Math.floor(rollValue * 1000000),
        item_won_id: winnerItem.id
    })

    return NextResponse.json({ winner: winnerItem })
}

Verify Your Setup

To ensure everything is working correctly:
In your Supabase dashboard, navigate to Table Editor and verify these tables exist:
  • users
  • cases
  • case_items
  • user_seeds
  • game_rolls
  • transactions
All tables should have Row Level Security (RLS) enabled.
Try logging out and logging back in. Check that:
  • You can sign up with a new email
  • You receive confirmation emails
  • Sessions persist across page refreshes
  • Protected routes redirect to login
Open multiple cases and verify:
  • Animations play smoothly
  • Different items are won (based on probability)
  • No console errors appear
  • Items appear in your inventory
Navigate to /provably-fair and verify:
  • Your server seed hash is displayed
  • You can update your client seed
  • Roll history is accessible
  • Verification tools work correctly

Common Issues

Environment variables not loadingIf you see errors like process.env.NEXT_PUBLIC_SUPABASE_URL is undefined:
  1. Ensure .env.local exists in the project root
  2. Restart the Next.js development server
  3. Check that variable names match exactly (including NEXT_PUBLIC_ prefix)
Database connection errorsIf Supabase queries fail:
  1. Verify your project URL and anon key are correct
  2. Check that your Supabase project is active (not paused)
  3. Ensure RLS policies are properly configured
  4. Check the browser console for detailed error messages
Migration errorsIf migrations fail to run:
  1. Run migrations in the correct order (by timestamp)
  2. Check for existing tables that might conflict
  3. Verify you have permission to create tables
  4. Look for SQL syntax errors in the Supabase SQL Editor

Next Steps

Now that you have Cajas running locally:

Explore Features

Learn about all the features available in Cajas, from provably fair mechanics to wallet management.

Deploy to Production

Deploy your Cajas instance to Vercel or another hosting platform.

Customize the UI

Modify colors, fonts, and layouts to match your brand.

Add Payment Methods

Integrate real payment processors like Stripe or Mercado Pago.

Development Tips

Hot Reload: Next.js automatically reloads when you save files. No need to restart the server for code changes.
Type Safety: The project uses TypeScript. Run npm run build to check for type errors before committing.
Database Types: Generate TypeScript types from your Supabase schema using the Supabase CLI:
supabase gen types typescript --project-id your-project-ref > types/database.ts

Need Help?

If you run into issues:
  1. Check the GitHub Issues for known problems
  2. Review the Features Documentation for detailed explanations
  3. Join the community discussions
  4. Open a new issue with reproduction steps
You’re all set! Start building your case opening platform.

Build docs developers (and LLMs) love