API Reference
Complete reference for the QTUM Dice backend API. All endpoints return JSON.
The base URL depends on your deployment (e.g., https://dice.ziesto.ca).
Base URL & Authentication
The API has no authentication — it's designed for public access. Rate limiting is applied at the server level:
- General: 500 requests per 15 minutes per IP
- House seed generation: 20 requests per minute per IP
All responses follow this format:
{
"success": true | false,
"data": { ... }, // present on success
"error": "message" // present on failure
}
General
GET /health
Health check endpoint. Returns server status.
Response:
{
"status": "ok",
"uptime": 12345,
"qtumConnected": true
}
GET /api/info
Game metadata and configuration.
Response:
{
"success": true,
"data": {
"name": "QTUM Dice",
"version": "1.0.0",
"network": "testnet",
"contractAddress": "9aac22498a..."
}
}
Dice Game Endpoints
GET /api/game/status
Get the current game status including contract balance, pending bets, and recent history.
Response:
{
"success": true,
"data": {
"contractBalance": "10000000000", // in satoshis (100 QTUM)
"pendingBets": 2,
"recentBets": [
{
"betId": 42,
"player": "7926223070...",
"amount": "10000000",
"target": 50,
"isOver": true,
"roll": 73,
"won": true,
"payout": "19800000",
"resolvedAt": 1706123456789
}
]
}
}
GET /api/game/house-seed
Generate a new house seed and return its keccak256 hash. The actual seed is stored on the backend for later reveal. Seeds expire after 2 hours.
Rate limit: 20 requests per minute per IP
Response:
{
"success": true,
"data": {
"houseSeedHash": "0x3a7f8c...", // bytes32 keccak256 hash
"expiresAt": 1706130656789 // Unix timestamp (ms)
}
}
Call this endpoint before placing a bet. Pass the houseSeedHash to the
smart contract's placeBet() function. The backend will later reveal the
original seed during revealAndResolve().
POST /api/game/submit-player-seed
Submit the player's revealed seed after the bet is confirmed on-chain.
This triggers the backend to call revealAndResolve() on the contract.
Request Body:
| Parameter | Type | Description |
|---|---|---|
| betId | number | The on-chain bet ID |
| playerSeed | string | The original seed (bytes32, 0x + 64 hex chars) |
Request:
POST /api/game/submit-player-seed
Content-Type: application/json
{
"betId": 42,
"playerSeed": "0xabcdef0123456789..."
}
Response:
{
"success": true,
"data": {
"betId": 42,
"playerSeedReceived": true,
"revealTxid": "abc123..." // or null if failed
}
}
Errors:
| Code | Error | Cause |
|---|---|---|
| 400 | Missing required fields | betId or playerSeed not provided |
| 400 | Invalid player seed format | Seed not in bytes32 format |
| 404 | Bet not found or not pending | betId doesn't match a pending bet |
GET /api/game/bet/:betId
Get details of a specific bet by ID. Works for both pending and resolved bets.
URL Parameter:
| Parameter | Type | Description |
|---|---|---|
| betId | number | The on-chain bet ID |
Response (resolved):
{
"success": true,
"data": {
"betId": 42,
"player": "7926223070...",
"amount": "10000000", // satoshis
"target": 50,
"isOver": true,
"roll": 73,
"won": true,
"payout": "19800000", // satoshis
"status": "resolved",
"resolvedAt": 1706123456789
}
}
Response (pending):
{
"success": true,
"data": {
"betId": 43,
"player": "7926223070...",
"amount": "10000000",
"target": 75,
"isOver": false,
"hasPlayerSeed": false,
"status": "pending"
}
}
GET /api/game/find-bet?houseSeedHash=0x...
Find a pending bet by its house seed hash. Used by the frontend to discover the on-chain bet ID after placing a transaction.
| Parameter | Type | Description |
|---|---|---|
| houseSeedHash | string (query) | The house seed hash used when placing the bet |
Response:
{
"success": true,
"data": {
"betId": 42,
"player": "7926223070...",
"amount": "10000000",
"target": 50,
"isOver": true,
"hasPlayerSeed": false
}
}
GET /api/game/multiplier?target=50&isOver=true
Calculate the payout multiplier for given bet parameters.
| Parameter | Type | Description |
|---|---|---|
| target | number (query) | Target number (2-98) |
| isOver | string (query) | true or false |
Response:
{
"success": true,
"data": {
"target": 50,
"isOver": true,
"multiplier": "1.9600",
"winChance": "50%"
}
}
GET /api/game/history?limit=20
Get recent resolved bets in reverse chronological order.
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | number (query) | 20 | Max number of bets to return |
Response:
{
"success": true,
"data": [
{
"betId": 42,
"player": "7926223...",
"amount": "10000000",
"target": 50,
"isOver": true,
"roll": 73,
"won": true,
"payout": "19800000",
"resolvedAt": 1706123456789
}
]
}
WebSocket Events
Connect to the WebSocket server at ws://<host>:3002 (or
wss:// for SSL). The server broadcasts game events in real-time.
betPlaced
Emitted when a new bet is detected on-chain.
{
"type": "betPlaced",
"data": {
"betId": 42,
"player": "7926223070...",
"amount": "10000000",
"target": 50,
"isOver": true
}
}
betResolved
Emitted when a bet is resolved on-chain.
{
"type": "betResolved",
"data": {
"betId": 42,
"player": "7926223070...",
"amount": "10000000",
"target": 50,
"isOver": true,
"roll": 73,
"won": true,
"payout": "19800000",
"resolvedAt": 1706123456789
}
}
WebSocket Client Example
const ws = new WebSocket('wss://dice.ziesto.ca/ws');
ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'betPlaced':
console.log(`New bet #${msg.data.betId}`);
break;
case 'betResolved':
console.log(`Bet #${msg.data.betId}: roll=${msg.data.roll}`);
break;
}
});
Smart Contract Interface
These are the contract functions your dApp interacts with. All functions are called
via QTUM RPC (sendtocontract or callcontract) or through
the MetaMask Snap.
Write Functions
| Function | Parameters | Description |
|---|---|---|
placeBet |
(bytes32 playerSeedHash, uint8 target, bool isOver, bytes32 houseSeedHash) |
Place a bet with QTUM value. Payable. |
revealAndResolve |
(uint256 betId, bytes32 houseSeed, bytes32 playerSeed) |
Reveal both seeds and resolve the bet. |
claimRefund |
(uint256 betId) |
Claim refund if bet not resolved after 1 hour. |
deposit |
(none) | Fund the contract bankroll. Payable. |
withdraw |
(uint256 amount) |
Owner-only: withdraw from bankroll. |
Read Functions
| Function | Parameters | Returns |
|---|---|---|
getBet |
(uint256 betId) |
Full bet details (player, amount, target, result, etc.) |
calculateMultiplier |
(uint8 target, bool isOver) |
Payout multiplier in basis points |
calculatePayout |
(uint256 amount, uint8 target, bool isOver) |
Potential payout for a bet |
getBalance |
(none) | Contract balance in satoshis |
Events
| Event | Parameters | When Emitted |
|---|---|---|
BetPlaced |
(uint256 indexed betId, address indexed player, uint256 amount, uint8 target, bool isOver, bytes32 playerSeedHash, bytes32 houseSeedHash) |
When placeBet() succeeds |
BetResolved |
(uint256 indexed betId, address indexed player, uint8 roll, bool won, uint256 payout) |
When revealAndResolve() or claimRefund() succeeds |
Function Selectors
Pre-computed keccak256 selectors for ABI encoding:
| Function | Selector |
|---|---|
placeBet(bytes32,uint8,bool,bytes32) | keccak256(...).slice(0,10) |
revealAndResolve(uint256,bytes32,bytes32) | keccak256(...).slice(0,10) |
claimRefund(uint256) | keccak256(...).slice(0,10) |
getBalance() | 0x12065fe0 |
getBet(uint256) | keccak256(...).slice(0,10) |
You can compute function selectors using ethers.js:
import { keccak256, toUtf8Bytes } from 'ethers';
const sel = keccak256(
toUtf8Bytes('placeBet(bytes32,uint8,bool,bytes32)')
).slice(0, 10);
// e.g., "0x1a2b3c4d"
Error Handling
All error responses follow the standard format:
{
"success": false,
"error": "Human-readable error message"
}
| HTTP Code | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Missing or invalid parameters |
| 404 | Not Found | Bet ID doesn't exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | QTUM node unreachable, contract call failed |