Multiplier
Primitive

Rising multiplier that can crash at any moment — cash out before it does.

A multiplier climbs from 1.00×. A crash point is secretly determined before the round starts. Cash out before the crash to win; wait too long and lose everything. Good for crash, rocket, moon games.

How It Works

You define:

Growth speed, curve shape, min/max crash points

Platform handles:

Crash point generation, multiplier math, 97% RTP

Game Config

1window.GAME_CONFIG = {
2 primitive: 'multiplier',
3 curve: 'exponential', // 'exponential' | 'linear'
4 growthRate: 0.1386, // Controls speed (0.01–10)
5 // For exponential: ln(2)/growthRate = seconds to reach 2x
6 // Default 0.1386 → 2x at ~5 seconds
7 maxMultiplier: 1000000 // Cap (default 1,000,000)
8};

Config Parameters

ParameterTypeDescription
curve'exponential' | 'linear'Curve shape. exponential = classic crash, linear = steady climb
growthRatenumberGrowth speed (0.01–10). Higher = faster climb. Required.
maxMultipliernumberOptional. Cap on multiplier (min 2, max 10,000,000)

Curve Types

exponential

Classic crash curve. Multiplier accelerates over time. Most rounds crash low, rare rounds reach high multipliers. Standard crash game behavior.

linear

Steady climb at a constant rate. More predictable timing. Good for simpler game styles.

PostMessage API

Multiplier games use two messages: start (place bet) and cashout.

1. Start a Round

1parent.postMessage({
2 type: 'multiplierStart',
3 id: Date.now(), // Unique request ID
4 stake: window.GAME_STAKE,
5 currency: window.GAME_CURRENCY
6}, '*');
7
8// Response (matched by id):
9// {
10// sessionId: "abc123",
11// commitHash: "fa3b...", — provably fair commitment
12// serverSeedHash: "ef01...", — hash of server seed
13// curve: "exponential",
14// growthRate: 0.1386,
15// maxMultiplier: 1000000,
16// startTime: 1700000000000, — server timestamp (ms)
17// stake: 0.01,
18// stakeUsd: 10
19// }

2. Cash Out

1// Capture the server-corrected time at the moment the user clicks,
2// then freeze your animation immediately for a snappy feel.
3const clickTimeMs = Date.now() + clockOffset;
4
5parent.postMessage({
6 type: 'multiplierCashout',
7 id: Date.now(),
8 sessionId: state.sessionId,
9 cashoutAtMs: clickTimeMs // optional — locks multiplier at click time
10}, '*');
11
12// Response if BEFORE crash (matched by id):
13// {
14// success: true,
15// multiplier: 2.34,
16// payout: 0.0234,
17// payoutUsd: 23.40,
18// profit: 0.0134,
19// profitUsd: 13.40,
20// stakeUsd: 10,
21// crashPoint: 5.67, — the actual crash point (revealed)
22// serverSeed: "abc...", — for verification
23// commitHash: "fa3b...",
24// gameOver: true
25// }
26
27// Response if AFTER crash:
28// {
29// error: 'Round crashed - too late!',
30// crashed: true,
31// crashPoint: 1.45,
32// payout: 0,
33// payoutUsd: 0,
34// profitUsd: -10,
35// stakeUsd: 10,
36// serverSeed: "abc...",
37// commitHash: "fa3b...",
38// gameOver: true
39// }
cashoutAtMs is optional but recommended. It tells the server what time the player actually clicked, so the multiplier is locked at that moment instead of when the request arrives. The server clamps it safely — it can never be in the future or before the round started. Without it, network latency can cause the player to get a slightly different multiplier than what they saw on screen.

3. Server Events

The platform sends two server-pushed events to your iframe. These are NOT responses to your messages — they arrive automatically:

1// Clock sync — sent periodically to sync animation timing
2// {
3// type: 'serverSync',
4// serverNowMs: 1700000005000 — server's current timestamp
5// }
6
7// Crash — sent when the round crashes
8// {
9// type: 'serverCrash',
10// crashMultiplier: 1.45, — the crash point
11// serverSeed: "abc...", — for verification
12// commitHash: "fa3b..."
13// }
Use serverSync to calibrate your local clock offset, ensuring your multiplier animation matches the server's timeline precisely.

Active Sessions

Multiplier games are stateful. If a player refreshes mid-round, the platform restores their session:

1window.addEventListener('message', (e) => {
2 if (e.data.type === 'sessionResume' && e.data.primitive === 'multiplier') {
3 const s = e.data.session;
4 // s.sessionId, s.stake, s.stakeUsd
5 // s.curve, s.growthRate, s.maxMultiplier
6 // s.startTime, s.commitHash
7 // Resume the multiplier animation from where they left off
8 }
9});

Provably Fair

The crash point is determined before the round starts:

WhenWhat Happens
Round startServer generates serverSeed + crashPoint, sends commitHash
Round endServer reveals serverSeed and crashPoint
VerificationSHA256(crashPoint:serverSeed) === commitHash

Animating the Multiplier

Your game HTML is responsible for animating the multiplier visually. The platform tells you the growthRate and curve — you render the animation:

1// Example: exponential curve animation
2const startTime = Date.now();
3const growthRate = 0.1386; // from start response
4
5function animate() {
6 const elapsed = (Date.now() - startTime) / 1000;
7 // Exponential: multiplier = e^(growthRate * t)
8 const multiplier = Math.exp(growthRate * elapsed);
9 // Linear alternative: multiplier = 1 + growthRate * elapsed;
10 updateDisplay(multiplier.toFixed(2) + 'x');
11 if (!gameOver) requestAnimationFrame(animate);
12}
13animate();
The server determines the actual crash point — your animation is just visual. Always listen for the serverCrash event to know when the round actually ends. Don't rely on your local multiplier value for game logic.

Handling Lag & Desync

Your client clock and the server clock will always drift slightly. Use serverSyncmessages to calculate an offset, then apply it to your animation so the displayed multiplier stays accurate:

1let clockOffset = 0; // ms difference: server - client
2
3window.addEventListener('message', (e) => {
4 if (e.data.type === 'serverSync') {
5 // serverNowMs = what the server thinks "now" is
6 clockOffset = e.data.serverNowMs - Date.now();
7 }
8});
9
10// In your animation loop, use the corrected time:
11function animate(startTime, growthRate) {
12 const serverNow = Date.now() + clockOffset;
13 const elapsed = (serverNow - startTime) / 1000;
14 const multiplier = Math.exp(growthRate * elapsed);
15 updateDisplay(multiplier.toFixed(2) + 'x');
16 if (!gameOver) requestAnimationFrame(() =>
17 animate(startTime, growthRate));
18}

Common Pitfalls

Using local multiplier for logicThe displayed multiplier is a client-side estimate. Always use the server's crashPoint / multiplier from cashout or serverCrash responses for any game logic (win/loss detection, payout display).
Ignoring serverCrashIf the player hasn't cashed out and the round crashes, the serverCrash event is the only way to know. Not listening for it means the UI can show a rising multiplier after the round is already over.
Skipping clock syncWithout the serverSync offset, your animation can drift 100–500ms from reality — enough for a player to think they cashed out in time when they actually didn't.
Allowing double cashoutAlways disable the cashout button immediately after clicking. The server will reject duplicates, but it confuses the UI if you don't guard against it locally.

Latency Best Practices

Network round-trips add 50-200ms between a player clicking "Cash Out" and the server processing it. During that delay the multiplier keeps climbing, which feels unfair. Follow these three patterns to make cashouts feel instant:

Optimistic UIFreeze the displayed multiplier and stop animating the moment the player clicks. Show a "Cashing out..." state while the request is in flight. If the server returns a crash error, revert to the crash animation.
Send cashoutAtMsInclude cashoutAtMs (Date.now() + clockOffset) in your multiplierCashout message. The server uses this timestamp to calculate the multiplier at the exact click moment, eliminating the network-delay penalty.
Use serverSync for clock offsetApply the offset from serverSync messages to both your animation loop and your cashoutAtMs calculation. This keeps the client-side multiplier aligned with the server and ensures cashoutAtMs is accurate.

Balance Reveal Timing

The player's visible balance updates as soon as multiplierCashout completes on the server. To show a cashout celebration animation before the balance updates, send balanceReveal when the animation finishes.

1// In your cashout response handler, after playing the celebration:
2parent.postMessage({ type: 'balanceReveal', id: betId }, '*');
3// Pass the original request id so the platform can match it to the correct payout.
4// If omitted, the platform falls back to the oldest pending payout (FIFO).
A 5-second fallback fires automatically if you never send balanceReveal. If the player gets crashed out (no cashout), no update is queued since there is no payout.

Complete Template

1<!DOCTYPE html>
2<html>
3<head><title>My Crash Game</title></head>
4<body>
5 <button onclick="startRound()">START</button>
6 <div id="multiplier">1.00x</div>
7 <button onclick="cashout()">CASH OUT</button>
8 <div id="status"></div>
9
10 <script>
11 window.GAME_CONFIG = {
12 primitive: 'multiplier',
13 curve: 'exponential',
14 growthRate: 0.1386
15 };
16
17 let sessionId = null;
18 let gameOver = false;
19 let clockOffset = 0;
20 const pendingRequests = new Map();
21 let requestId = 0;
22
23 // Request/response helper (id-based matching)
24 function sendToParent(data) {
25 return new Promise((resolve, reject) => {
26 const id = ++requestId;
27 pendingRequests.set(id, { resolve, reject });
28 var payload = Object.assign({}, data, { id: id });
29 // In preview mode, include game config for validation
30 if (window.TEST_MODE) {
31 payload.gameConfig = window.GAME_CONFIG_FOR_API;
32 }
33 parent.postMessage(payload, '*');
34 setTimeout(() => {
35 if (pendingRequests.has(id)) {
36 pendingRequests.delete(id);
37 reject(new Error('Timeout'));
38 }
39 }, 30000);
40 });
41 }
42
43 async function startRound() {
44 gameOver = false;
45 try {
46 const d = await sendToParent({
47 type: 'multiplierStart',
48 stake: window.GAME_STAKE || 1,
49 currency: window.GAME_CURRENCY || 'USDT'
50 });
51 if (d.error) return alert(d.error);
52 sessionId = d.sessionId;
53 startAnimation(d.growthRate, d.startTime);
54 } catch (err) { alert(err.message); }
55 }
56
57 async function cashout() {
58 if (!sessionId || gameOver) return;
59 gameOver = true; // freeze animation immediately
60 const clickTime = Date.now() + clockOffset;
61 document.getElementById('multiplier').textContent += ' CASHING OUT...';
62 try {
63 const d = await sendToParent({
64 type: 'multiplierCashout',
65 sessionId: sessionId,
66 cashoutAtMs: clickTime
67 });
68 showWin(d.multiplier, d.payout);
69 sessionId = null;
70 } catch (err) {
71 // "Round crashed - too late!" or other errors
72 gameOver = true;
73 sessionId = null;
74 alert(err.message);
75 }
76 }
77
78 function startAnimation(growthRate, startTime) {
79 function tick() {
80 if (gameOver) return;
81 const t = (Date.now() - startTime) / 1000;
82 const m = Math.exp(growthRate * t);
83 document.getElementById('multiplier').textContent =
84 m.toFixed(2) + 'x';
85 requestAnimationFrame(tick);
86 }
87 tick();
88 }
89
90 window.addEventListener('message', (e) => {
91 const d = e.data;
92
93 // Resolve pending requests by id
94 if (d.id && pendingRequests.has(d.id)) {
95 const h = pendingRequests.get(d.id);
96 pendingRequests.delete(d.id);
97 if (d.error) h.reject(new Error(d.error));
98 else h.resolve(d);
99 return;
100 }
101
102 // Server says round crashed
103 if (d.type === 'serverCrash') {
104 gameOver = true;
105 document.getElementById('multiplier').textContent =
106 d.crashMultiplier.toFixed(2) + 'x CRASHED';
107 sessionId = null;
108 }
109
110 // Clock sync (calibrate animation + cashoutAtMs accuracy)
111 if (d.type === 'serverSync') {
112 clockOffset = d.serverNowMs - Date.now();
113 }
114
115 // Restored session after refresh
116 if (d.type === 'sessionResume' && d.primitive === 'multiplier') {
117 sessionId = d.session.sessionId;
118 startAnimation(d.session.growthRate, d.session.startTime);
119 }
120
121 if (d.type === 'stakeUpdate') {
122 // Update local stake variables
123 }
124 });
125
126 parent.postMessage({ type: 'gameReady' }, '*');
127 </script>
128</body>
129</html>