The costs page states both deductions and, for each game, the maths return alongside what players actually receive. It is rendered from /api/fees, which the server generates from the same schedule it charges, so the published terms cannot drift from the behaviour. Backups every five minutes with a rolling 24 hours. Each dump is checked for size and format before replacing the previous one — a backup script that reports success on a truncated file is worse than none, because it turns a recoverable outage into silent loss found only when needed. A nightly job restores the newest snapshot and asserts the ledger balances. The standby continuously restores into a shadow database and swaps only after verifying the books, so it is never mid-restore when needed and never promotes a corrupt copy. Promotion does not contact the dead machine, and it refuses to start if the ledger does not balance. Kernel tuning is tied to measured limits, not copied defaults. Alby Hub is explicitly excluded from snapshot restore: publishing a stale channel state can lose the channel balance outright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
1.9 KiB
JavaScript
63 lines
1.9 KiB
JavaScript
/* The costs page.
|
|
*
|
|
* Every figure is fetched from /api/fees, which the server renders from the
|
|
* same schedule it charges. Nothing here is written by hand, so the published
|
|
* terms cannot drift from the behaviour — if the operator changes the rake,
|
|
* this page changes with it. */
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
function el(tag, attrs, ...children) {
|
|
const node = document.createElement(tag);
|
|
for (const [k, v] of Object.entries(attrs || {})) {
|
|
if (k === 'class') node.className = v;
|
|
else if (k === 'text') node.textContent = v;
|
|
else node.setAttribute(k, v);
|
|
}
|
|
for (const c of children) {
|
|
if (c == null) continue;
|
|
node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
async function load() {
|
|
let d;
|
|
try {
|
|
d = await (await fetch('/api/fees')).json();
|
|
} catch {
|
|
$('schedule').textContent = 'Could not load the fee schedule.';
|
|
return;
|
|
}
|
|
|
|
const s = d.schedule || {};
|
|
const grid = $('schedule');
|
|
const rows = [
|
|
['taken from winnings', s.rake_percent],
|
|
['payouts rounded to', s.rounding_unit],
|
|
['most rounding can cost', s.worst_case_rounding_per_payout],
|
|
];
|
|
for (const [k, v] of rows) {
|
|
grid.appendChild(el('div', {},
|
|
el('span', { class: 'k', text: k }),
|
|
el('span', { class: 'v', text: v || '—' })));
|
|
}
|
|
$('worst').textContent = s.worst_case_rounding_per_payout || '—';
|
|
|
|
const table = $('rtp-table');
|
|
const crash = d.crash_games || {};
|
|
table.appendChild(el('tr', {},
|
|
el('td', { text: 'Crash games' }),
|
|
el('td', { text: crash.game_rtp_percent || '—' }),
|
|
el('td', { class: 'rtp', text: crash.effective_percent || '—' })));
|
|
|
|
for (const t of d.scratch_tickets || []) {
|
|
table.appendChild(el('tr', {},
|
|
el('td', { text: t.ticket }),
|
|
el('td', { text: t.game_rtp_percent }),
|
|
el('td', { class: 'rtp', text: t.effective_percent })));
|
|
}
|
|
}
|
|
|
|
load();
|