PolHunter v0.2: the hunt engine

Hand-off sign-in from InstantAdPay (lib/sso.js: HMAC token, five minutes, single use; no sign-up,
no mailer), missions with per-member per-visit proof codes (lib/missions.js: the embed is
answered only from the mission's own origin, only after the dwell; hiding place rotates by day
and member), weighted-low rewards with a daily cap that queues rather than refuses
(lib/rewards.js), the faucet sender on ethers with a low-balance alert (lib/faucet.js), the
one-line embed for the sites (public/embed.js), the hunter board, the public ledger, an
admin page, and the site's own look. Telegram is behind the OUTBOUND gate like everything else.
13-check end-to-end test in test/run.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 14:17:44 -05:00
parent 1a6166abc3
commit 523d57624e
15 changed files with 1001 additions and 75 deletions
+28
View File
@@ -0,0 +1,28 @@
// Tiny JSON store on the volume. Every collection is one file; writes are atomic (tmp + rename).
// PolHunter's data is small (missions, completions, payouts) and a member's balance never lives
// here, so a database would be ceremony. If it ever grows, this is the one file to swap.
'use strict';
const fs = require('fs');
const path = require('path');
let DIR = null;
function init(dir) { DIR = dir; fs.mkdirSync(DIR, { recursive: true }); }
const file = name => path.join(DIR, name + '.json');
function read(name, fallback) {
try { return JSON.parse(fs.readFileSync(file(name), 'utf8')); } catch (e) { return fallback; }
}
function write(name, data) {
const f = file(name), tmp = f + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(data));
fs.renameSync(tmp, f);
}
// read-modify-write in one place so callers never race themselves
function update(name, fallback, fn) {
const cur = read(name, fallback);
const out = fn(cur) || cur;
write(name, out);
return out;
}
module.exports = { init, read, write, update };