Cases
by BloxCash
HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cases Fairness Verifier · BloxCash</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="tool">
<div class="cols">
<section class="panel">
<h2 class="eyebrow-section">Round Inputs</h2>
<label class="field">
<span class="eyebrow-field">Server Seed <em>(revealed after rotating)</em></span>
<input id="serverSeed" type="text" spellcheck="false" autocomplete="off" placeholder="64 hex characters" />
</label>
<label class="field">
<span class="eyebrow-field">Client Seed</span>
<input id="clientSeed" type="text" spellcheck="false" autocomplete="off" placeholder="your client seed" />
</label>
<div class="row">
<label class="field">
<span class="eyebrow-field">Nonce</span>
<input id="nonce" type="number" min="0" step="1" inputmode="numeric" placeholder="0" />
</label>
<label class="field">
<span class="eyebrow-field with-badge">
Server Seed Hash <em>(optional)</em>
<span id="hashBadge" class="badge" hidden></span>
</span>
<input
id="serverSeedHash"
type="text"
spellcheck="false"
autocomplete="off"
placeholder="hash shown before the round"
/>
</label>
</div>
<div class="row wide">
<label class="field">
<span class="eyebrow-field">Case</span>
<select id="caseSelect">
<option value="">Paste JSON manually</option>
</select>
</label>
<label class="field">
<span class="eyebrow-field">Round ID <em>(optional)</em></span>
<input
id="roundId"
type="text"
spellcheck="false"
autocomplete="off"
placeholder="pins the odds to that...
CSS
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap');
:root {
--color-bg-primary: #0f0f14;
--color-bg-secondary: #17171e;
--color-bg-tertiary: #25252f;
--color-bg-elevated: #2e2e3a;
--color-accent-yellow: #ffd700;
--color-accent-green: #00e07a;
--color-accent-red-text: #ff6b85;
--color-text-primary: #ffffff;
--color-text-secondary: #c4c4e0;
--color-text-muted: #9e9ec4;
--color-text-faint: #8585ac;
--radius-lg: 0.375rem;
--font-mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Several rules below set an explicit display, which would otherwise beat the [hidden] attribute. */
[hidden] {
display: none !important;
}
body {
/* Matches the bg-bg-secondary card the iframe is embedded in, so the seam disappears. */
background: var(--color-bg-secondary);
color: var(--color-text-primary);
font-family:
'Outfit',
-apple-system,
BlinkMacSystemFont,
sans-serif;
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.tool {
padding: 20px;
}
.cols {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
@media (min-width: 800px) {
.cols {
grid-template-columns: 1fr 1fr;
}
}
.panel {
display: flex;
flex-direction: column;
gap: 14px;
min-width: 0;
}
.eyebrow-section {
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
}
.eyebrow-field {
display: block;
margin-bottom: 6px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
}
.eyebrow-field em {
font-style: normal;
font-weight: 500;
text-transform: none;
letter-spacing: 0;
color: var(--color-text-faint);
}
.field {
min-width: 0;
}
.field.grow {
display: flex;
flex-direction:...
JavaScript
// Serves the case list and pinned round odds; point at a local instance for dev data.
const API_BASE = 'https://bloxcash.com';
/* Probabilities are kept as integers scaled by 10^20: float sums drift and can flip a roll
that lands on a range boundary. */
const SCALE = 10n ** 20n;
const TWO32 = 2n ** 32n;
const FIVE32 = 5n ** 32n;
const encoder = new TextEncoder();
const toHex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
async function hmacSha256(key, message) {
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
return toHex(await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(message)));
}
const sha256 = async (message) => toHex(await crypto.subtle.digest('SHA-256', encoder.encode(message)));
// Exponent form is accepted: the API's Decimals serialize anything below ~1e-6 as "8.8E-7".
function toScaled(raw) {
const match = /^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(String(raw).trim());
const [, whole = '', fraction = '', exponent = '0'] = match ?? [];
if (!whole && !fraction) throw new Error(`"${raw}" is not a non-negative decimal.`);
const decimals = fraction.length - Number(exponent);
if (decimals > 20) throw new Error(`"${raw}" needs more than 20 decimal places.`);
return BigInt(whole + fraction) * 10n ** BigInt(20 - decimals);
}
function formatScaled(scaled, decimals = 8) {
const digits = scaled.toString().padStart(21, '0');
const point = digits.length - 20;
return `${digits.slice(0, point)}.${digits.slice(point, point + decimals)}`;
}
function parseItems(text) {
const data = JSON.parse(text);
const list = [data, data?.items, data?.case?.items].find(Array.isArray);
if (!list) throw new Error('Expected an items array, or an object containing one.');
if (!list.length) throw new Error('The item list is empty.');
return...