Chameleon

Credit System

How the credit system works in Chameleon

Credit System

Chameleon uses a credit-based system to manage AI generation usage and reward user activities.

Overview

Credits are the internal currency used for:

  • AI Generation - Text, image, and video generation
  • User Rewards - Daily check-ins, referrals
  • Service Access - Premium features

Credit Sources

New User Bonus

Every new user receives 100 credits upon registration.

// Automatically added on account creation
Credits: 100
Expires: 1 year from signup
Transaction Type: new_user

Purchase Credits

Users can buy credit packages through the pricing page.

Example Packages:

  • Starter: 1,000 credits
  • Professional: 5,000 credits
  • Enterprise: 20,000 credits

Payment Methods:

  • One-time purchase (expires in 1 year)
  • Monthly subscription (credits reset monthly)
  • Yearly subscription (credits reset yearly)

Daily Check-In

Users earn 1 credit per day by visiting /api/ping.

// API endpoint
POST /api/ping

// Response
{
  "code": 0,
  "message": "Ping success, 1 credit added"
}

Implementation:

  • Maximum 1 credit per day per user
  • Resets at midnight (UTC)
  • Simple way to engage users

Referral Rewards

When a referred user makes a purchase:

  • Referrer earns commission (5-30% based on order value)
  • Converted to credits automatically

See Referral System for details.

Admin Added Credits

Admins can manually add credits to user accounts for:

  • Customer support
  • Promotions
  • Compensation
// In admin panel or via API
import { increaseCredits } from "@/services/credit";

await increaseCredits({
  user_uuid: "user-uuid",
  trans_type: CreditsTransType.SystemAdd,
  credits: 500,
  expired_at: "2026-12-31T23:59:59Z",
});

Credit Costs

AI Generation Costs

Different AI providers and types have different credit costs:

Video Generation

  • Kling AI: 10 credits per video
  • Seedance: 10 credits per video

Image Generation

  • OpenAI DALL-E 3: 5 credits per image
  • Replicate Flux: 3 credits per image
  • Kling: 4 credits per image

Text Generation (if enabled)

  • OpenAI GPT-4: 1 credit per request
  • DeepSeek: 1 credit per request
  • OpenRouter: 1 credit per request
  • SiliconFlow: 1 credit per request

Configuration: See src/services/constant.ts - AI_GENERATION_CREDITS

Custom Costs

You can adjust credit costs in the code:

// src/services/constant.ts
export const AI_GENERATION_CREDITS = {
  image: { 
    openai: 5,     // DALL-E 3
    replicate: 3,   // Flux
    kling: 4 
  },
  video: { 
    kling: 10, 
    seedance: 10 
  },
};

Credit Deduction

Automatic Deduction

Credits are automatically deducted when:

  • AI generation completes successfully
  • User uses premium features

Deduction Timing

For AI Generation:

  • Credits are NOT deducted on task creation
  • Credits are deducted ONLY on successful completion
  • If generation fails, no credits are deducted
// In src/services/generation.ts
try {
  // Generate AI content
  const result = await generateVideo(...);
  
  // Mark as completed
  await updateGenerationStatus(gen_id, "completed", resultUrls);
  
  // Deduct credits (only on success)
  await decreaseCredits({
    user_uuid,
    trans_type: CreditsTransType.AIGeneration,
    credits: credits_cost,
  });
} catch (error) {
  // Mark as failed
  await updateGenerationStatus(gen_id, "failed", error.message);
  
  // No credits deducted on failure
  throw error;
}

Insufficient Credits

If a user doesn't have enough credits:

const userCredits = await getUserCredits(user_uuid);

if (userCredits.left_credits < credits_cost) {
  return respErr("Insufficient credits. Please recharge.");
}

The UI shows:

  • Warning message: "Insufficient credits"
  • Link to pricing page: "Recharge"
  • Current balance display

Credit Expiration

Expiration Rules

Credit SourceExpiration
New User Bonus1 year from signup
One-Time Purchase1 year from purchase
Monthly SubscriptionEnd of monthly cycle
Yearly SubscriptionEnd of yearly cycle
Referral Rewards1 year from earning
System AddedCustom (set by admin)

Expiration Logic

// Check if credit is expired
const now = new Date();
const expiredAt = new Date(creditTrans.expired_at);

if (expiredAt < now) {
  // Credit has expired, cannot use
  continue;
}

FIFO Deduction

Credits are deducted in First-In-First-Out order:

  1. Oldest credits are used first
  2. Prevents waste of expiring credits
  3. Implemented in decreaseCredits function

Credit Transactions

Transaction Types

All credit changes are recorded in credits_trans table:

export enum CreditsTransType {
  NewUser = "new_user",         // New user bonus
  OrderPay = "order_pay",        // Purchased credits
  SystemAdd = "system_add",      // Admin added
  Ping = "ping",                 // Daily check-in
  AIGeneration = "ai_generation", // AI usage
}

Transaction History

Users can view their credit history at /my-credits:

  • Transaction type
  • Amount (+ or -)
  • Remaining balance
  • Expiration date
  • Timestamp

Query Transactions

import { getCreditsByUserUuid } from "@/models/credit";

const transactions = await getCreditsByUserUuid(user_uuid, page, pageSize);

// Returns array of transactions with:
// - trans_type
// - credits (positive or negative)
// - expired_at
// - created_at

Credit Balance

Checking Balance

API Endpoint:

POST /api/get-user-credits

// Response
{
  "code": 0,
  "data": {
    "left_credits": 850,
    "total_credits": 1100
  }
}

In Code:

import { getUserCredits } from "@/services/credit";

const credits = await getUserCredits(user_uuid);
console.log(credits.left_credits);  // Current balance
console.log(credits.total_credits); // Lifetime earned

Balance Display

The credit balance is displayed:

  • Header - Top right of user center
  • AI Generator - Shows cost and remaining credits
  • My Credits page - Detailed breakdown

Managing Credits

Increase Credits

import { increaseCredits, CreditsTransType } from "@/services/credit";

await increaseCredits({
  user_uuid: "user-uuid",
  trans_type: CreditsTransType.OrderPay,
  credits: 1000,
  expired_at: "2026-01-01T00:00:00Z",
});

Decrease Credits

import { decreaseCredits, CreditsTransType } from "@/services/credit";

await decreaseCredits({
  user_uuid: "user-uuid",
  trans_type: CreditsTransType.AIGeneration,
  credits: 10,
});

decreaseCredits automatically handles expired credits and FIFO deduction.

Credit System Architecture

Database Tables

credits Table:

  • Stores current balance (left_credits)
  • Stores lifetime total (total_credits)
  • Updated on every transaction

credits_trans Table:

  • Stores all credit transactions
  • Immutable audit log
  • Used for history and analytics

Service Layer

File: src/services/credit.ts

Key Functions:

  • getUserCredits(user_uuid) - Get current balance
  • increaseCredits(...) - Add credits
  • decreaseCredits(...) - Deduct credits
  • getCreditsByUserUuid(...) - Get transaction history

Constants

File: src/services/constant.ts

export const CreditsAmount = {
  NewUserGet: 100,           // New user bonus
  OneDayPingGet: 1,          // Daily check-in
};

export const AI_GENERATION_CREDITS = {
  image: { openai: 5, replicate: 3, kling: 4 },
  video: { kling: 10, seedance: 10 },
};

Best Practices

For Users

  1. Check balance before generating - Avoid failed requests
  2. Use daily check-ins - Free credit every day
  3. Choose providers wisely - Different costs for different quality
  4. Subscribe for better value - More credits per dollar

For Developers

  1. Always check balance before allowing actions
  2. Deduct only on success - Don't charge for failures
  3. Log all transactions - Important for debugging
  4. Set appropriate costs - Balance user value and server costs
  5. Monitor usage - Detect abuse or unusual patterns

Customization

Adjust Credit Costs

Edit src/services/constant.ts:

export const AI_GENERATION_CREDITS = {
  image: { 
    openai: 10,    // Increase DALL-E cost to 10
    replicate: 5,   // Increase Replicate to 5
  },
  video: { 
    seedance: 15    // Increase Seedance to 15
  },
};

Adjust New User Bonus

export const CreditsAmount = {
  NewUserGet: 500,  // Give 500 credits instead of 100
};

Custom Expiration

// Give credits that expire in 30 days
const expiry = new Date();
expiry.setDate(expiry.getDate() + 30);

await increaseCredits({
  user_uuid,
  trans_type: CreditsTransType.SystemAdd,
  credits: 1000,
  expired_at: expiry.toISOString(),
});

Analytics

Credit Usage Reports

Query credit usage:

-- Total credits consumed by AI generation
SELECT SUM(ABS(credits)) as total_used
FROM credits_trans
WHERE trans_type = 'ai_generation'
AND created_at > NOW() - INTERVAL '30 days';

-- Credits by AI type
SELECT ai_type, provider, SUM(credits_cost) as total_cost
FROM generations
WHERE status = 'completed'
GROUP BY ai_type, provider;

User Spending Analysis

// Get top credit spenders
const topSpenders = await db()
  .select({
    user_uuid: credits.user_uuid,
    total_spent: sql`SUM(ABS(credits))`,
  })
  .from(credits_trans)
  .where(eq(credits_trans.trans_type, "ai_generation"))
  .groupBy(credits.user_uuid)
  .orderBy(desc(sql`SUM(ABS(credits))`))
  .limit(10);

Next Steps