Files
Swarm/README_LOTTERY.md
2025-10-24 21:40:50 +08:00

11 KiB
Raw Blame History

:bug# Lottery System API Documentation

Overview

The DysonNetwork Lottery System provides a daily lottery where users can purchase tickets with custom number selections. Each day features a new draw with random winning numbers. Users purchase tickets using ISP (Dyson Network Points), with results announced each morning.

The API is handled by the DysonNetwork.Pass service. Which means if you use it with the Gateway the /api should be replaced with /pass

Key Features

  • Daily Draws: Automated draws at midnight UTC
  • Custom Number Selection: Users choose 5 unique numbers (0-99) + 1 special number (0-99)
  • Flexible Pricing: Base cost 10 ISP + extra ISP per multiplier (e.g., multiplier=2 costs 20 ISP)
  • Daily Limits: One ticket purchase per user per day
  • Prize System: Multiple prize tiers based on matches
  • Instant Payment: Tickets purchased using in-app points
  • Historical Records: Complete draw history and statistics

Data Models

LotteryDrawStatus Enum

public enum LotteryDrawStatus
{
    Pending = 0,    // Ticket awaiting draw
    Drawn = 1       // Ticket has been processed in draw
}

SnLottery Model

public class SnLottery : ModelBase
{
    public Guid Id { get; set; }
    public SnAccount Account { get; set; } = null!;
    public Guid AccountId { get; set; }
    public List<int> RegionOneNumbers { get; set; } = new(); // 5 numbers (0-99)
    public int RegionTwoNumber { get; set; }                    // Special number (0-99)
    public int Multiplier { get; set; } = 1;                     // Prize multiplier (≥1)
    public LotteryDrawStatus DrawStatus { get; set; }
    public DateTime? DrawDate { get; set; }                      // Date when drawn
}

SnLotteryRecord Model

public class SnLotteryRecord : ModelBase
{
    public Guid Id { get; set; }
    public DateTime DrawDate { get; set; }
    public List<int> WinningRegionOneNumbers { get; set; } = new(); // 5 winning numbers
    public int WinningRegionTwoNumber { get; set; }                   // Winning special number
    public int TotalTickets { get; set; }                             // Total tickets processed
    public int TotalPrizesAwarded { get; set; }                       // Number of winning tickets
    public long TotalPrizeAmount { get; set; }                        // Total ISP prize amount
}

Prize Structure

Region 1 Matches Base Prize (ISP) Notes
0 0 No prize
1 10 Minimum win
2 20 Double minimum
3 50 Five times minimum
4 100 Ten times minimum
5 1000 Maximum prize

Special Number Bonus: If Region 2 number matches, multiply any prize by 10x.

API Endpoints

All endpoints require authentication via Bearer token.

Purchase Ticket

POST /api/lotteries

Creates a lottery order and deducts ISP from user's wallet.

Request Body:

{
  "RegionOneNumbers": [5, 23, 47, 68, 89],
  "RegionTwoNumber": 42,
  "Multiplier": 1
}

Response:

{
  "id": "guid",
  "accountId": "guid",
  "createdAt": "2025-10-24T00:00:00Z",
  "status": "Paid",
  "currency": "isp",
  "amount": 10,
  "productIdentifier": "lottery"
}

Validation Rules:

  • RegionOneNumbers: Exactly 5 unique integers between 0-99
  • RegionTwoNumber: Single integer between 0-99
  • Multiplier: Integer ≥ 1
  • User can only purchase 1 ticket per day

Pricing:

  • Base cost: 10 ISP
  • Additional cost: (Multiplier - 1) × 10 ISP
  • Total cost = (Multiplier × 10) ISP

Get User Tickets

GET /api/lotteries

Retrieves user's lottery tickets with pagination.

Query Parameters:

  • offset (optional, default 0): Page offset
  • limit (optional, default 20, max 100): Items per page

Response:

[
  {
    "id": "guid",
    "regionOneNumbers": [5, 23, 47, 68, 89],
    "regionTwoNumber": 42,
    "multiplier": 1,
    "drawStatus": "Pending",
    "drawDate": null,
    "createdAt": "2025-10-24T10:30:00Z"
  }
]

Response Headers:

X-Total: 42  // Total number of user's tickets

Get Specific Ticket

GET /api/lotteries/{id}

Retrieves a specific lottery ticket by ID.

Response: Same structure as individual items from Get User Tickets.

Error Responses:

  • 404 Not Found: Ticket doesn't exist or user doesn't own it

Get Lottery Records

GET /api/lotteries/records

Retrieves historical lottery draw results.

Query Parameters:

  • startDate (optional): Filter by draw date (YYYY-MM-DD)
  • endDate (optional): Filter by draw date (YYYY-MM-DD)
  • offset (optional, default 0): Page offset
  • limit (optional, default 20): Items per page

Response:

[
  {
    "id": "guid",
    "drawDate": "2025-10-24T00:00:00Z",
    "winningRegionOneNumbers": [7, 15, 23, 46, 82],
    "winningRegionTwoNumber": 19,
    "totalTickets": 245,
    "totalPrizesAwarded": 23,
    "totalPrizeAmount": 4820
  }
]

Integration Examples

Frontend Integration (JavaScript/React)

// Purchase a lottery ticket
async function purchaseLottery(numbers, specialNumber, multiplier = 1) {
  try {
    const response = await fetch('/api/lotteries', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${userToken}`
      },
      body: JSON.stringify({
        RegionOneNumbers: numbers,      // Array of 5 unique numbers 0-99
        RegionTwoNumber: specialNumber, // Number 0-99
        Multiplier: multiplier          // Optional, defaults to 1
      })
    });

    const order = await response.json();

    if (response.ok) {
      console.log('Ticket purchased successfully!', order);
      // Refresh user ISP balance
      updateWalletBalance();
    } else {
      console.error('Purchase failed:', order);
    }
  } catch (error) {
    console.error('Network error:', error);
  }
}

// Get user's tickets
async function getUserTickets() {
  try {
    const response = await fetch('/api/lotteries?limit=20', {
      headers: {
        'Authorization': `Bearer ${userToken}`
      }
    });

    const tickets = await response.json();
    const totalTickets = response.headers.get('X-Total');

    return { tickets, total: parseInt(totalTickets) };
  } catch (error) {
    console.error('Error fetching tickets:', error);
  }
}

// Get draw history
async function getDrawHistory() {
  try {
    const response = await fetch('/api/lotteries/records', {
      headers: {
        'Authorization': `Bearer ${userToken}`
      }
    });

    return await response.json();
  } catch (error) {
    console.error('Error fetching history:', error);
  }
}

Mobile Integration (React Native/TypeScript)

interface LotteryTicket {
  id: string;
  regionOneNumbers: number[];
  regionTwoNumber: number;
  multiplier: number;
  drawStatus: 'Pending' | 'Drawn';
  drawDate?: string;
  createdAt: string;
}

interface PurchaseRequest {
  RegionOneNumbers: number[];
  RegionTwoNumber: number;
  Multiplier: number;
}

class LotteryService {
  private apiUrl = 'https://your-api-domain.com/api/lotteries';

  async purchaseTicket(
    ticket: Omit<PurchaseRequest, 'RegionOneNumbers'> & { numbers: number[] },
    token: string
  ): Promise<any> {
    const request: PurchaseRequest = {
      RegionOneNumbers: ticket.numbers,
      RegionTwoNumber: ticket.RegionTwoNumber,
      Multiplier: ticket.Multiplier
    };

    const response = await fetch(this.apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`
      },
      body: JSON.stringify(request)
    });

    return response.json();
  }

  async getTickets(token: string, offset = 0, limit = 20): Promise<LotteryTicket[]> {
    const response = await fetch(`${this.apiUrl}?offset=${offset}&limit=${limit}`, {
      headers: { 'Authorization': `Bearer ${token}` }
    });

    return response.json();
  }

  async getDrawRecords(token: string): Promise<any[]> {
    const response = await fetch(`${this.apiUrl}/records`, {
      headers: { 'Authorization': `Bearer ${token}` }
    });

    return response.json();
  }
}

Number Validation

function validateLotteryNumbers(numbers, specialNumber, multiplier = 1) {
  // Validate region one numbers
  if (!Array.isArray(numbers) || numbers.length !== 5) {
    return { valid: false, error: 'Must select exactly 5 numbers' };
  }

  const uniqueNumbers = new Set(numbers);
  if (uniqueNumbers.size !== 5) {
    return { valid: false, error: 'Numbers must be unique' };
  }

  // Check range 0-99
  for (const num of numbers) {
    if (!Number.isInteger(num) || num < 0 || num > 99) {
      return { valid: false, error: 'Numbers must be integers between 0-99' };
    }
  }

  // Validate special number
  if (!Number.isInteger(specialNumber) || specialNumber < 0 || specialNumber > 99) {
    return { valid: false, error: 'Special number must be between 0-99' };
  }

  // Validate multiplier
  if (!Number.isInteger(multiplier) || multiplier < 1) {
    return { valid: false, error: 'Multiplier must be 1 or greater' };
  }

  return { valid: true };
}

// Example usage
const validation = validateLotteryNumbers([5, 12, 23, 47, 89], 42, 2);
if (!validation.valid) {
  console.error(validation.error);
}

Daily Draw Schedule

  • Draw Time: Every midnight UTC (00:00 UTC)
  • Processing: Only tickets from the previous day are included
  • Prize Distribution: Winners automatically receive ISP credits
  • History: Draws are preserved indefinitely

Error Handling

Common Error Codes

  • 400 Bad Request: Invalid request data (bad numbers, duplicate purchase, etc.)
  • 401 Unauthorized: Missing or invalid authentication token
  • 404 Not Found: Ticket doesn't exist or access denied
  • 403 Forbidden: Insufficient permissions (admin endpoints)

Error Response Format

{
  "message": "You can only purchase one lottery per day.",
  "type": "ArgumentException",
  "statusCode": 400
}

Testing Guidelines

Test Cases

  1. Valid Purchase: Select valid numbers, verify wallet deduction
  2. Invalid Numbers: Try duplicate region one numbers, out-of-range values
  3. Daily Limit: Attempt second purchase in same day
  4. Insufficient Funds: Try purchase without enough ISP
  5. Draw Processing: Verify winning tickets receive correct prizes
  6. Historical Data: Check draw records match processed tickets

Test Data Examples

// Valid ticket
{ numbers: [1, 15, 23, 67, 89], special: 42, multiplier: 1 }

// Invalid - duplicate numbers
{ numbers: [1, 15, 23, 15, 89], special: 42, multiplier: 1 }

// Invalid - out of range
{ numbers: [1, 15, 23, 67, 150], special: 42, multiplier: 1 }

Support

For API integration questions or support:

  • Check network documentation for authentication details
  • Contact Dyson Network development team for assistance
  • Monitor API response headers for pagination metadata