A correction that reads 'granted by admin' explains nothing to the member looking at
their ledger. The note now travels with the grant, so a credit-consumption correction
names itself where they will actually see it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marty approves specific people for multiple accounts (partners, staff, a spouse on a
shared machine) and needed a way to say so without the guard fighting him.
Admin > Members > Duplicate signals now carries an "Approved exceptions" list: add an
email with a note, see who is on it and when, remove one. It sits directly under the
signals so the two are read together.
An exception can be added against the address they ALREADY have, not just the new one.
That matters because the usual case is approving a person before their second address
exists, and at sign-up time the new address is unknown to us. checkSignup now tracks
every account the sign-up collided with, and clears the block if either side is approved.
Clearing the HARD flags matters as much as clearing the block. Those flags are what
silently drop an account off the leaderboard and bar it from adopting out of the holding
tank, so an approved person would have been "allowed" in name only. They now keep both.
The account is tagged 'allowlisted' instead, so the admin sees why it went through, and
the server logs the exception by name.
Suspension still wins. An exception is permission to hold several accounts, not immunity
from being suspended for something else.
qa/fraud-allow.mjs (12 assertions) boots its own server and walks the real flow: first
account created, second blocked with the Qualified Start redirect, exception added,
second account now created, no hard flag left on it, exception visible in the admin
report, removing it blocks again, malformed address refused, endpoint admin-only.
qa/sponsor-note.mjs 5, qa/run.sh member 0 bugs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marty signed in and was told "You're joining the line of @bliss". He is member #1.
Cause: the last-touch sponsor cookie lives 30 days, and /api/sponsor set invited
purely from that cookie. So any member who had ever clicked a teammate's invite link
was greeted on the sign-in screen as though logging in would place them under that
person. Untrue, and alarming in exactly the wrong place: their sponsor locked at their
first purchase and nothing on that screen can move it. Anyone seeing that would
reasonably worry their line was about to change.
The greeting now shows when someone actually arrived through a link (?ref= in the URL),
or when the cookie is present AND this browser has never had an account, which is the
genuine "came back later to finish joining" case. A browser that already has an account,
or a signed-in session, never sees it.
Attribution is deliberately untouched: the cookie still resolves, the sponsor id is
still returned, and placement still works exactly as before. Only the greeting changed.
fraud.hasAccountOnDevice(req) is the new signal, reusing the device cookie the
one-account-per-person checks already set.
qa/sponsor-note.mjs (5 assertions) boots its own throwaway server and creates a REAL
account so the case is proven rather than assumed: still greeted with ?ref=, still
greeted from the cookie on a browser with no account, NOT greeted on the browser that
has one, and attribution still resolving. qa/run.sh member: 0 bugs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marty, 2026-09-16, after @megamol created megamol2/megamol3 under his own link and bought $20 on each
to fake his two qualifying buyers. fraud.js records sign-up IP/UA/browser id (iap.dev cookie set with
the code request) and last-seen on sign-in. New accounts: dup-device (browser already has an account)
and sponsor-device are refused, ip-burst (> fraudMaxSignupsPerIpDay, default 2, per 24h) is refused;
sponsor-ip and shared-ip are flagged only. Flagged/suspended accounts never count on the leaderboard
and cannot adopt from the tank; suspended accounts are signed out everywhere (auth.fromRequest
wrapper) and refused at sign-in. Admin > Members: Duplicate signals card (shared browser / IP,
flagged, suspended), flags badge, Suspend/Unsuspend; GET /api/admin/fraud; PATCH members {suspend,
reason, flags}. Telegram admin alert on every block/flag. Privacy page + chatbot prompt updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Campaign #134 (NAS ad 2806) had the member's join-page link in the image field and served a broken
banner 141 times on the network; #102 did the same with an imgbb page link. imageCheck() accepts
/uploads/ and instantadpay.com/banners files and image extensions outright, otherwise HEADs the URL
(one redirect) and requires image/*. Wired into member and house campaign creation for banners and
login-ad creatives. Form placeholder + hint point at Promo tools > Banners > Copy image URL.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Marty, 2026-09-16: make missing a payout visible. On a TierPaid with hops>0 the PassedUp events of the
same tx name the skipped positions, with the reason and how many qualifying buyers they had; the post
says the amount and who received it instead. On a Purchase whose buyer was routed past an unactivated
sponsor (movedByTx set by sponsorSyncOnEvent), the post names the sponsor who missed the whole sale
and the upline who now owns that buyer for good. Both go to the main feed and the shared payments
topic unless the events mode is 'none'.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Marty, 2026-09-16, after the third "why didn't my sponsor get paid" thread of the day (livedreams / gracie25,
Morten / Terry's linked wallets). GET /api/my/trace?who=<#|username> (yourself, anyone up to 3 levels
below you, or your own 3 uplines) and GET /api/admin/trace?who= list every purchase the member was
part of, newest first: buyer, sponsor, package, then levels 1-3 with the recipient, the skipped
positions and the reason ("not qualified: had N of 2 qualifying buyers then"), the platform share, and
a verify link. Linked extra wallets are named after their owner. Earnings tab card "Trace a payment";
admin member card gets a "Payment trace" section; chatbot canned answer + AI prompt route the
question there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Marty, 2026-09-16: a purchase must never wait on a sponsor who has not linked a wallet / switched on
payouts. /api/sponsor and /me now walk the site's sponsor line upward to the first upline that is
activated with payouts on (not on the no-payout list) and the buy proceeds under them; nobody
activated -> the catch position (#1). The buyer sees a one-line notice and carries on; admin gets a
heads-up. When the position is created on-chain (MemberActivated), sponsorSyncOnEvent re-points the
buyer's stored sponsor to whoever was paid (#1 included), so the referral leaves the skipped
sponsor's line for good, then sends the pointed notices: the skipped sponsor(s) are told they lost
this referral permanently and how to switch on payouts; the sponsor who was paid is told the
referral is theirs for good and to coach the one who missed it (teach-forward). Admin lookup:
GET /api/admin/sponsor-resolve?ref=. Only a transient chain error still pauses a buy.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hugh's Earnings page showed no payouts, referrals or purchases: /api/my/activity read the last 600
events and the window had moved past his Sept 9-10 activity. Ten other readers (dashboard earned
total, leaderboard, holding-tank own-buy check, admin member view, growth snapshot, P&L, burner
match) treated the same list as complete. KEEP_EVENTS 600 -> 250000; chain.rescan() rebuilds from
deployBlock without re-firing onEvent (no repeat Telegram/email), keeps real ts for known events and
estimates ts by block height for backfilled ones; runs once at boot when a filled window is found;
admin routes POST /api/admin/chain/rescan and GET /api/admin/chain/status. State file only rewritten
when events changed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hugh earned a 10-credit sign-in bonus, then bought a 280-credit extension; earned credits spend
first, so Earned read 0 and the bonus looked lost. There was no record a member could check.
Now every real credit movement writes a credit_log row (JSON store in file mode): welcome, sign-in
bonus with streak, daily claim, inbox reads, video watches, verified visits, milestone/leaderboard
bonuses, partner codes, admin/team grants, AI Copy Engine charges + refunds, flat buys (featured
run, extension, visit pack, more visits), login-ad days, and metered delivery rolled up per
campaign per day; on-chain purchased-credit spends are marked. GET /api/my/credits/activity;
dashboard shows the last 40 under Campaigns (also when there are no campaigns) with a link from
Earn credits. Chatbot canned answer + AI prompt updated; my.js tag bumped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hugh and Michael both added credits to a featured run expecting more days. Featured is a flat
daily buy, so the credits sat unspent until the run ended and their balance read 0 meanwhile.
Now: /api/my/campaigns/:id/extend books extra days after the current run (or from today if it
ended) and charges them at once; top-up refuses featured; a visits top-up buys more visits at the
pack rate, charged now; rows show paid up front + day count + runs through <date> / run ended;
explainer under the campaign table; chatbot canned answer + AI prompt updated; my.js v tag bumped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Spark: one-tap banner/text campaign templates aimed at the member's link (POST /api/my/toolkit/template); printable QR handout at /handout/<username>
- Circuit: videomaker.js renders every promo video/short with the member's end card + QR via ffmpeg (Dockerfile adds ffmpeg + ttf-dejavu), hosted in Spaces; split tester compares join angles
- Nexus: Leader Ops (three-level triage, stalled flags, one-click nudges), AI team broadcast kind + Send to my team, credit grants from the leader's earned pool, co-branded join page (sponsor bio for level-3 sponsors), member-funded partner code (promo_codes.funder; redemption charges the funder) + /partners?ref=<username>
- toolkit ladder all live; chatbot updated; my.js v20260914e, site.css v20260914c, join.js/partners.js v20260914a
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>