Commit Graph

158 Commits

Author SHA1 Message Date
martbost c91a530b7e Recruiting Telegram feed: add inline "Get Started" button to rmcircle.team
sendTelegram() takes an optional reply_markup; the recruiting posts carry an
inline URL button (🚀 Get Started — rmcircle.team) under every event, so the
busy new-members feed converts attention into clicks to the home page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 07:40:02 -05:00
martbost 0fff3c92e0 Add recruiting-framed payout feed to a second Telegram topic
sendTelegram() now takes an optional topicId; the same team events that post to
the internal team-build topic also fan out a recruiting-framed version (social
proof + CTA to rmcircle.team, no internal/contact detail) to the new-members
topic. Gated on config.telegramRecruitTopicId — dormant until set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 07:34:45 -05:00
martbost a3e9d05f5d Dashboard 'My Next Step' panel + funded badge
Adds a synthesized next-step panel to /my: the level ladder (you-are-here →
upgrade-next), the single clearest action (qualify vs upgrade), and a funded
badge that checks the member's wallet against their next upgrade cost.

Privacy: the wallet balance is checked server-side (chain.balanceOf via
eth_getBalance) and ONLY a funded true/false is returned — the raw balance is
never exposed on the ID-addressable dashboard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 06:33:59 -05:00
martbost f0ea4469a4 Self-rotating personal links: qualified member's join routes to next-to-qualify
Fixes the spillover leak where a qualified member promoting their own link had
joins land back on themselves (bonus reward) instead of qualifying their team.

- /api/public/member now returns joinTarget: unqualified member -> self;
  qualified -> next-to-qualify in their leg (reuses rotation-aligned nextInLine);
  whole leg qualified -> global rotation sponsor; else -> spillover (prior behavior)
- /join page: the Join button, "join under" header, sponsor-ID copy, and the
  submitted sponsorId all follow joinTarget; a note explains "invited by #X,
  joining to help #Y qualify" so the new member knows their real sponsor
- Member's SHARE link is unchanged (/join/<id>); resolution happens when a
  visitor opens it, so it self-corrects even for links already in the wild

Dry-run verified: /join/27 -> #34, /join/34 -> #34 (self), /join/21 -> #34.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 13:02:34 -05:00
martbost 3a80a50648 Rebrand public site to RM Circle Team Build (remove Crypto Team Build Network)
Public promo is now RM-Circle-only; the Crypto Team Build Network is learned
after joining, not surfaced to prospects.
- nav brand "Crypto Team Build" -> "RM Circle" (subtitle "Premium Team Build")
- all <title>, og:site_name, twitter meta, footers, disclaimers, 404, chatbot
  system prompt + canned answers, config siteName/supportLabel swept
- removed "a project of the Crypto Team Build Network" framing from prose
- banners regenerated: eyebrow now "The RM Circle · Polygon" (was Crypto Team Build)
- og-card.jpg regenerated (was "CRYPTO TEAM BUILD NETWORK PRESENTS")
- roadmap.webp already RM-Circle branded (no change needed)
- social posts/swipes: #CryptoTeamBuild -> #RMCircle, "crypto team build" -> "RM Circle team build"

Videos still reference old branding (VO + embedded site screenshot) — rebuilt next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 06:43:23 -05:00
martbost 21bd77c07c Add banner kit + social posts + short/long swipes + Branded Voice CTA to /tools
- 7-size banner kit (1200x630, 1080x1080, 1080x1920, 300x250, 728x90,
  468x60, 125x125) rendered on-brand, exact dimensions, served from /banners
- Promo Tools page: 5 copy-paste social posts (X/Facebook), short + standard
  + long email swipes, banner grid (download / copy-URL), invite-link builder
- Branded Voice CTA (mybrandedvoice.com) for writing promos in your own voice
- Sync chatbot: chat.js canned answer + server.js AI system prompt updated

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 06:08:35 -05:00
martbost 4dbbc6780c Add payments video to training + new Promo Tools page (/tools)
Training: new "Video 5 · Why you're not getting paid yet" card with the
payments teaching video (+ poster). Promo Tools page at /tools (noindex,
unlinked from public nav — a team resource): share-ready video clips
with copy-link buttons, a personal invite-link builder, and three
compliant email/message swipes; banners noted as generate-on-request.
Chatbot gains a promo-tools answer. Old scroll-only clip is superseded
by the combined cut (retire it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 05:47:01 -05:00
martbost 76c55fe4c2 Sweep all self-references to rmcircle.team (SEO/OG/canonical/chatbot)
Domain migration go-forward: replaced every hardcoded rmcircle.saasy.top
across public/*.html (canonical, og:url, og:image, twitter tags),
chat.js (chatbot answer URLs + linkify self-link matcher), my.js, and
server.js (member-page OG injection, unsubUrl, chat system prompt) with
rmcircle.team. Old domain still serves everything; canonical now points
search engines + social previews at the new primary domain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 05:32:13 -05:00
martbost d0b1eddda2 Show which generation caught each pass-up + finish domain go-forward
Feature (Marty): live payment proof + notifications now show the
generation for upgrade pass-ups (matrix hop distance payer→recipient),
removing the "why did they get it" question. chain.genBetween() adds a
`gen` field to payout events + /api/public/payouts; rendered as a gold
"Gen N" badge in the feed, "Gen N pass-up" in toasts, and "Gen N …" in
Telegram team alerts.

Domain: rmcircle.team is now live (added to Coolify alongside
rmcircle.saasy.top, SSL issued, both serve, no redirect). Switched the
auto-tweet CTA to https://rmcircle.team.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 05:22:47 -05:00
martbost 9717ddbd85 Admin: payment-routing / leak map for the org
New chain.getOrgRouting(rootId, ownerIds) simulates the contract's
_payUpline routing for every member below the org root on their NEXT
upgrade, classifying where the POL lands: your positions, a teammate
in-org (healthy), an outsider above the org (true LEAK), or fees.
Surfaced on /api/admin/income and rendered as a panel in "My Positions
— Income": 4-stat summary + expandable list of payments escaping the
org. Distinguishes real leaks (money leaving the team) from teammates
earning — which the old upgrade-needs alert conflated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 18:35:48 -05:00
martbost c418aa516a Add auto-tweet of on-chain payout proof to @cryptoteambuild (Blotato)
New tweet.js posts a tweet to X via Blotato each time the indexer emits
a NEW team payout. Org-gated to the #21 organization; fires on all
payout types. Ships DISABLED (config.tweetEnabled default off / env
TWEET_ON_PAYOUT), so nothing posts until explicitly turned on.

Safety: dedupes by tx key (persisted to DATA_DIR/tweeted-payouts.json)
so restarts never re-post; a min-gap queue keeps bursts under Blotato's
30/min limit; API key read from env BLOTATO_API_KEY or DATA_DIR key
file, never committed or logged. Varied emoji/hashtag templates with a
CTA (+UTM) to the main page; deterministic template pick avoids X
duplicate-content rejection. Admin config allowlist gains tweetEnabled,
tweetCtaUrl, tweetHashtags, blotatoTwitterId.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 11:35:05 -05:00
martbost d36a405581 Paginate the live payment proof feed (Show more / Show fewer)
The feed showed only the latest 12 of 136 recorded payouts. Now:
- chain.getPayoutsPublic(offset,limit) returns a page of the full
  reversed history plus total/offset/limit/hasMore (limit capped 100).
- /api/public/payouts accepts ?offset & ?limit (defaults 0/40, so the
  live poll + toast detection are unchanged).
- payouts.js keeps a deduped store keyed by payout key; the live poll
  refreshes the recent page while "Show more" pulls older pages 12 at a
  time (with slight overlap so a newly-arrived payout can't open a gap),
  and "Show fewer" collapses back. Verified in-browser: 12 → 48 → 136
  rows, button flips to "Show fewer", collapses to 12, 0 console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 11:16:12 -05:00
martbost 086e64b3f6 Add home-page "Still have doubts?" trust band + fix CSP blocking inline styles
Home page: new reassurance band between the live payment feed and the
final CTA — surfaces the three doubts (rules can't change, money never
sits in the contract, keeps running if creators vanish) and routes
skeptics to /contract.

CSP fix (the real find): style-src was 'self' with no 'unsafe-inline',
so the browser was silently dropping EVERY inline style="" attribute
site-wide — the attribute stayed in the DOM but never applied. This is
why the earlier margin fix only worked once moved to a class, and why
the new card rendered left-aligned with a teal eyebrow. Added
'unsafe-inline' to style-src only (script-src stays locked to 'self').
Verified via computed styles + full-page screenshots: home, /contract,
/how-pay-works now render as authored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 10:52:43 -05:00
martbost 56890b025a Add "What if the creators disappear?" reassurance (verified on-chain)
Answers the durability question: the contract is autonomous and
immutable (no admin heartbeat, no pause, no expiry), and the four
admin wallets are plain EOAs — read live from contract storage and
confirmed via eth_getCode — so a lost/abandoned admin wallet can
never reject or jam a member payment; at worst the project's own fee
sits uncollected. No stored balance means nothing can be stranded.

- contract.html: new section 6 card
- chat.js: matching canned answer (creators-disappear keywords)
- server.js: RESILIENCE fact added to the AI system prompt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 10:26:58 -05:00
martbost 279bcb5f25 Teach the chatbot everything we added (canned + AI prompt)
chat.js: new canned answers for how-you-get-paid (two streams + depth-
matched levels), Premium vs Standard tier, and the member dashboard/
alerts. server.js AI system prompt: added PAY FLOW, TIERS, and MEMBER
DASHBOARD facts, and refreshed the site-pages list (/how-pay-works,
/my, /disclaimer, spillover article). Both chat modes now answer the
questions this session surfaced (skips, standard tier, dashboards).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 10:15:42 -05:00
martbost 1929a928d8 Add /how-pay-works pay-flow diagram page
Visual explainer of the two income streams: entry rewards you keep, and
the depth-matched upgrade ladder (each layer pays you once, at the one
level matching its depth) as an SVG flow diagram. Route + CSS added;
linked from training body and all footers. Educational, on-chain-factual
framing with disclaimer link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 10:06:36 -05:00
martbost 7b33e28d0c Personalized social previews for shared referral links
/my/:id and /join/:id are now served with server-injected, per-member
Open Graph + Twitter Card tags (social scrapers don't run JS, so the
per-ID meta must be in the static HTML). Strips any baked-in og/twitter
tags and injects a fresh ID-specific set — title/description reference
the member #, branded og-card.jpg image, canonical per-ID og:url. Bare
/my and /join paths unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 08:30:52 -05:00
martbost d805a33608 Add home "team by the numbers" showcase + why-a-winning-team-pays
Public /api/public/org-stats (from config.orgRootId, default 21) feeds a
prominent home-page band: share of the whole network, team size,
generations deep, and on-chain POL to the team — populated by bridge.js,
section hides itself if stats aren't ready. Paired with three mechanics
cards (spillover fills your matrix, depth pays as it climbs, the rotation
qualifies you) that make the "winning team" case honestly, plus the risk
caveat ("most participants may not profit") and a disclaimer link.
Live: 51.1% of members, 48 in the org, 10 generations, 25,423 POL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:14:23 -05:00
martbost 7b6f629780 Add "Your Organization vs. the Network" live stat to admin
New chain.getOrgShare(rootId) + /api/admin/org-share endpoint compute an
org's share of the whole contract by both member count and total POL
paid, from in-memory state. Admin card (default root 21, saved to
config.orgRootId, auto-loads) shows the two headline percentages, a bar,
and the member/POL breakdown. Verified: #21 org = 51.1% of members,
49.5% of POL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:07:27 -05:00
martbost f5de98c214 Classify rotation joins by worked-sponsor status, not just active
A rotation joiner submits their ID after the rotation may have advanced
(esp. with auto-advance: the direct who completes a sponsor's 2/2 joined
under it, but the queue has already moved on). Keying "rotation" off the
currently-active sponsor mislabeled them "leg" (hit live: Harvey #92
under #36 after #36 qualified). Now: rotation if the on-chain referrer
is a queue sponsor that is active OR qualified (was/is worked); a
still-waiting or non-queue referrer stays a leg join, preserving the
original #49/#56-under-#46 fix. Auto-enqueue now parents the new
position to the actual on-chain referrer, not the current active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 05:43:03 -05:00
martbost e1d6b782a2 Auto-advance rotation when a sponsor hits 2/2
Reverses the earlier manual-Qualify design (Marty 2026-08-15): when the
auto-count brings a queue sponsor to 2/2 directs, the sponsor is now
auto-qualified and normalizeStatuses activates the next waiting position
— same as clicking Qualify, but automatic. Previously it stopped at 2/2
"active" and waited for a manual click, which left the rotation looking
stuck (hit live with #36 Mad Dog). #36 qualified manually to catch up;
rotation advanced to #34 Janie.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 05:36:14 -05:00
martbost 0f5630e6d0 Disclaimer page, self-service member alerts, and admin login lockout
- /disclaimer: independent-resource, affiliate, earnings, risk, and
  not-advice disclosures; linked from all footers.
- Self-service alerts: members opt in with their email on /my/:id to get
  "you've been paid" + "upgrade needed" emails for their own position
  (same watcher as the owner alerts, extended). Signed unsubscribe link
  (/unsubscribe?id=&t=HMAC), on-chain-registration check, rate-limited,
  masked-email status, confirmation email.
- Admin login: timing-safe compare + per-IP lockout (8 fails -> 15 min,
  escalating). Previously unlimited/brute-forceable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:45:57 -05:00
martbost 8b3e82d960 Alert when an owned position needs upgrading to catch incoming pay
New owner upgrade watcher: every 5 min it checks config.ownerIds against
the chain state and, when a leg member is ONE upgrade from paying a
position that isn't eligible yet (below the required level, or not
qualified), emails config.ownerAlertEmail + pings Telegram — "upgrade
#24 to Fabrica, #61 is one upgrade from paying you 2,486 POL." Deduped
per (position, level) in owner-alerts.json, re-fires if the situation
recurs. Same warning renders inline in the admin "My Positions" panel
(upgradeNeeds in the income endpoint). New ownerAlertEmail settings
field. Detection logic unit-tested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:27:50 -05:00
martbost 81d90fbe8e Add admin "My Positions — Income" panel
Aggregates every on-chain payment received by a set of owned IDs
(defaults to 21,24,25, saved to config.ownerIds and prefilled/auto-loaded
next time). Shows grand total + per-position totals and a merged,
newest-first ledger (to / from / entry-or-upgrade level / amount) via a
focused chain.getIncome read and an admin-gated /api/admin/income
endpoint. Verified: 5,064.43 POL across 21/24/25, 11 payments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:08:21 -05:00
martbost d1aef1e3ff Add /contract — plain-language security review of the smart contract
Shareable page from the 2026-08-13 source review: code immutability
(no proxy/delegatecall/selfdestruct, Sourcify exact match), same-tx
distribution (no pooled funds), locked prices and fee constants, the
honest list of owner powers with the 50% entry-fee cap, where unmatched
pass-ups go, and verify-yourself links. Linked from all public footers,
the payment-proof note, and the chatbot (canned + AI prompt).

Also: passed-over lists now exclude structurally-skipped uplines — a
level-N payment never checks the first N-1 uplines, so listing them as
"not eligible" was misleading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 10:51:34 -05:00
martbost 8d766161e8 Auto-sync queue levels from on-chain upgrades
When a queue member's MemberUpgraded event lands, their Level in the
rotation queue updates automatically (same op as the admin dropdown),
with a Telegram note. Current queue trued up against the chain: four
entries corrected (30, 35, 46 up to Ascensus; 56 down to Scintilla —
it had been set optimistically).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 10:47:45 -05:00
martbost 3a93fda3eb Align dashboard next-in-line with the rotation queue
The chain-only BFS pick could promote structurally-next but inactive
positions (#21's dashboard said #38 — not in the queue, likely inactive —
while the team rotation was working #36). Next-in-line now prefers the
active rotation sponsor when they're in the member's leg and unqualified,
then the first chain-order queue participant, falling back to the pure
structural pick for legs outside the queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 10:34:17 -05:00
martbost 62ad2da2fa Auto-enqueue rotation joins
A verified rotation join (@chriskelly8, ID 73 under #36) still required
a manual "add to queue" step from the Telegram message and got missed.
Rotation-path submissions now insert themselves into the sponsor queue
as waiting positions (name from the submission, level from chain,
parent = active sponsor, dedupe-guarded), and the Telegram alert reports
the queue position instead of asking for action. #73 was added manually
to catch up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 10:23:25 -05:00
martbost 29e0d6e791 Personal invite pages (/join/<id>) + auto-count queue directs from the chain
Prospects who clicked a member's shared /my link landed on a bare stats
dashboard with no explanation of the program. New /join/<id> page carries the
full bridge-page story (video, strategy, matrix, live on-chain payout proof)
personalized to one sponsor: their live position stats as proof, their ID
pinned on the dApp join button, and the submit-your-ID form locked to them
(source invite-<id>). Dashboard share buttons + QR codes now hand out the
invite page; /my stays the member dashboard. Invalid/unregistered invite ids
fall back to the /start rotation. Tracked as new 'join' event with an admin
traffic column.

Also: the chain indexer now auto-increments a rotation sponsor's directs when
a registration names them as referrer (same op as the admin ⊕), announcing it
on Telegram. At 2/2 it alerts to Qualify instead of auto-rotating — moving
the team focus stays a human decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 08:18:30 -05:00
martbost 532cb18b68 Split submissions into rotation vs leg paths — decided by the chain
Two leg-build members submitted the /start form thinking it was required,
polluting rotation bookkeeping. Rather than two forms, the server now
classifies every submission by on-chain truth: referrer == active
rotation sponsor -> "rotation" (Telegram says +1 direct, add to queue);
anyone else -> "leg" (correct sponsor credited, "no rotation action
needed"). The member dashboard gains its own "Just joined under this
position?" form so leg builders have a proper landing spot; /start
feedback explains each path to the submitter; admin submissions table
shows a rotation/leg chip. Verified against live data: ID 65 claiming
sponsor 36 correctly classified leg under #62.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 05:39:02 -05:00
martbost d9ac9c9806 Settle the doctrine: recommend retiring at 2/2, frame spillover as bonus
Marty's final framing: the teachable rule stays "get your 2, retire
your link, help your 2" — but a late signup on a qualified link is
presented everywhere as a welcome bonus (full entry reward + depth via
spillover), never a rule violation. Applied consistently across the
strategy page, start callout, chatbot (canned + AI prompt), training
article, and both share-card notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:48:20 -05:00
martbost 72c8f137ae Correct spillover teaching across all site copy
The old copy claimed qualified links must be retired to avoid "extra
shallow legs" — factually wrong for this contract: extra signups on a
qualified link spill DOWNWARD (BFS) as depth, and the referrer keeps
earning the full entry reward on every signup with no cap at 2. Updated
the strategy flow, team-principle notice, strategy card, start-page
callout, chatbot (canned + AI prompt), training article, and the
qualified share card: the team play is still helping your 2 get their 2
(only own directs qualify a position), but continued personal promotion
is accurately described as paid depth-building, and spillover recipients
earn only when the spilled member upgrades — if qualified and leveled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:22:59 -05:00
martbost 0a05d97b32 Teach spillover in the chatbot and training page
- Chat: new canned answer covering spillover keywords (why "2 under me
  but 1/2 directs", pass-over eligibility) and the same facts added to
  the AI system prompt so both chat modes answer consistently.
- Training: "Understanding spillover" article after the videos — what it
  is, why it doesn't qualify you, why it still pays your position via
  matrix upgrade payments, and the qualified+leveled eligibility rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:05:44 -05:00
martbost 65b963b39b Persist admin sessions across redeploys
Sessions now load from and save to sessions.json in the data volume
(0600, atomic write, expired entries pruned on save), so a redeploy no
longer logs the admin out. Verified: session cookie survives a full
server restart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 15:04:22 -05:00
martbost 6a9cd96b8c Qualified dashboards promote the next unqualified position in their leg
Instead of only pointing at the team rotator, a qualified member's share
card now runs the moving-link wave automatically: breadth-first,
left-to-right, the first position below them with fewer than 2 directs
becomes "next in line" — their QR, page link, and join CTA all carry
that position's ID. When it qualifies, the card rotates to the next one.
Falls back to the team rotator when the whole leg is qualified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:58:31 -05:00
martbost 350d50798f Member dashboard: drill-down tree, list view, legend, and strategy-aware referral tool
- "Your team" now covers the member's FULL leg with click-to-drill
  pyramid, breadcrumb, and a list-view toggle (mirrors the admin matrix).
- Legend under both the member tree and admin matrix explaining the
  qualified checkmark, tier badges, and downline rollup.
- "Share this position" card: while under 2/2 it's a personal recruiting
  tool — per-member QR (vendored qrcode-generator, CSP-safe), copy link,
  and a join CTA carrying the member's ID as sponsor. At 2/2 the page
  automatically retires the link and redirects sharing to the team
  rotator, so personal referrals can't undermine the moving-link
  strategy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:53:06 -05:00
martbost 9f8898d2bd Add public member dashboard at /my/:id
Members enter their on-chain ID (or follow a /my/46-style link) and see
their own position: tier/level/directs/earnings facts, their subtree
pyramid with downline rollups and open slots, classified payment
history, and lineage to root — all chain-derived data only, none of the
admin operational config. Public endpoint /api/public/member is cached
(120s) and rate-limited (20/min/IP). Linked from the public footers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:35:05 -05:00
martbost e332a4f1c2 Allow renaming a sponsor from the queue
The name in the Sponsor Queue is now a click-to-rename control (pencil
appears on hover), using the existing PATCH endpoint; server trims and
caps the name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:47:07 -05:00
martbost 0f5f821b39 Email the admin a copy of every team-build alert
New config teamAlertEmail: the same team-gated on-chain events that post
to Telegram (registration, upgrade, payout at/below teamRootId) also go
to this address via SendGrid, so deep-leg activity surfaces even away
from Telegram.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:12:33 -05:00
martbost accd869c12 Default payment-email sender to marketingwithmarty.com
Both marketingwithmarty.com and mybrandedvoice.com are SendGrid
DKIM-authenticated; Marty's sending identity is marketingwithmarty.com,
so the default from and the admin label now reflect that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:10:41 -05:00
martbost e3f07057d9 Send automatic "you've been paid" emails via SendGrid
When the chain tail sees a payout to a member whose sponsor record has a
contact email, the site emails them the amount, source, and Polygonscan
link (plain text — deliverability). SendGrid key is pasted in a new
admin panel (stored in the data volume, like the OpenRouter key) or set
via SENDGRID_API_KEY. From address defaults to no-reply@mybrandedvoice.com
— the domain SendGrid is DKIM-authenticated for — and is editable in the
same panel. Not gated on teamRootId; any member with an email on file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:03:26 -05:00
martbost 6b17419646 Add sponsor contact emails and Telegram team-build activity alerts
Sponsors can carry an optional contact email (Add Sponsor form, ✉ inline
edit in the queue, shown under the name). New config teamRootId: any NEW
on-chain event at or below that member ID — registration, upgrade, or
payout seen by the live tail — posts to the Telegram group topic, with
the tx link and the sponsor's contact email when one is on file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:57:51 -05:00
martbost 7c41499312 Add on-chain payment proof: live payout feed, ID verification, admin lineage tools
New chain.js reads the RM Circle contract (0x33Bd…2DAF, Polygon) via free
public RPCs — no API keys. Daily snapshot rebuilds complete payout history
from getIncomeHistory (log providers prune old history), and a 60s
eth_getLogs tail catches new payouts with tx hashes, upgrade context, and
passed-over upline IDs.

- Bridge page: "Live payment proof" feed + timed toast pop-ups for payouts
  seen in the last 15 min, every row linking to Polygonscan.
- /start: same toasts; ID submissions are now verified against the contract
  (result shown to the member, in Telegram notify, and in admin).
- Admin: on-chain member lookup (lineage to root, directs, matrix children,
  full income history) and a collapsible full matrix tree view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 10:25:07 -05:00
martbost 89a84aa7ca Allow public pages to render in safelist/traffic-exchange iframes
Framing was blocked site-wide (X-Frame-Options DENY + frame-ancestors
none), so safelists and traffic exchanges showed a blank panel. Public
pages now send frame-ancestors *; admin.html keeps the strict lockdown.
Join button opens the dApp in a new tab so it escapes the exchange
iframe (dApps typically refuse to render framed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 08:37:56 -05:00
martbost fe357dd361 Use escaped control-char class in memberName sanitizer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:59:15 -05:00
martbost e5eefdfc61 Require name or Telegram handle on member ID submission
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:57:34 -05:00
martbost 2b40dc70c8 Support posting member submissions to a Telegram forum topic
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:46:25 -05:00
martbost 89f27b89a2 Close the conversion loop: member ID submission with Hermes Telegram notify
- Start page step 6 is now a bold gold-highlighted "Submit your NEW RM
  Circle ID" form with larger step numbers throughout; captures the
  sponsor the visitor was shown at join time.
- POST /api/public/submit-id: validates numeric ID, dedupes, stores on
  the volume, records a per-source "purchase" analytics event, fires a
  BeMob postback (txid=purchase-<clickid>) to close paid campaigns, and
  posts the new ID + sponsor + source to the configured Telegram chat
  (MB Hermes pattern). Rate-limited.
- Admin: Member ID Submissions table, "Confirmed joins" tile, and
  Telegram bot token / chat ID settings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:30:53 -05:00
martbost 63e8b4cdd0 Add paid-traffic tracking: clickid capture and BeMob conversion postback
- track.js stores ?clickid= (or ?cid=) for the session; rides through
  bridge -> start navigation alongside the existing source attribution.
- Join click (the conversion) sends the clickid; server fires a
  fire-and-forget S2S postback to the admin-configured BeMob postback
  URL (cid + txid=join-<clickid>, payout 0) and counts successes per
  source in analytics.
- New admin setting: BeMob postback URL (server-side only, not exposed
  in public config).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:26:34 -05:00
martbost c1ec687768 Let admins enable AI chat by pasting an OpenRouter key in the admin panel
Key is stored on the persistent volume (0600, never in git), read
dynamically per request, maskable status only in admin state, and
clearable to fall back to built-in answers. OPENROUTER_API_KEY env var
still takes precedence if ever set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 09:17:39 -05:00