Commit Graph

315 Commits

Author SHA1 Message Date
martbost f53bb7db85 Sponsor notifications: new referral + referral purchase
- notifyNewReferral (was nudgeReferrer): email a member's sponsor on EVERY new
  referral, activated or not. Resolve the sponsor from the join token by member
  id, share code, or username (was code-only and skipped already-activated
  sponsors). Reworded from an activation nudge to a real "you have a new
  referral" note; keeps the payouts reminder only for un-activated sponsors.
- Purchase event now also emails the buyer's direct sponsor that their referral
  bought a package, noting whether it's a $20+ qualifying purchase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 11:34:34 -05:00
martbost 873a9e5e4a Mainnet indexer fix + retire testnet copy
- chain.js: derive the chain tip as the MAX height across the RPC pool and scan
  getLogs against that same synced node. Load-balanced public RPCs (publicnode)
  intermittently answer eth_blockNumber from a replica lagging thousands of
  blocks behind, which stalled the mainnet scan (latest < lastBlock, 0 events)
  and could skip freshly-mined events. This unblocks the live ledger + the
  purchase-confirmation email (both driven by the event scan).
- disclaimer: replace the "rehearsal/testnet" section with a live-on-Polygon
  real-money notice (it's live now).
- Point the dead Amoy fallbacks (purchase email tx link, contract-page source
  link) at Polygon mainnet / the verified mainnet contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 11:20:47 -05:00
martbost 7b131dad28 Gas floor fix, chain guard, and dashboard footer
- wallet.js: fetch correct EIP-1559 fees from the network (Amoy/Polygon Bor
  enforce a ~25-30 gwei priority floor MetaMask's estimate misses) via new
  /api/gas; hard chain-guard before signing so a tx never lands on the wrong
  network; accept the wallet's usual networks in AppKit so its modal stops
  looping and drive add+switch ourselves.
- chain.js: suggestedFees() from eth_maxPriorityFeePerGas + base fee.
- Dashboard: move the Site links (Ad packages / Live ledger / The contract)
  out of the sidebar into a page footer; hide the sidebar scrollbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 10:49:34 -05:00
martbost 8958a9d145 Fix credits showing 0 + purchase confirmation email
- chain.rpc(): retry the RPC pool 3 rounds with backoff. Public Amoy nodes
  routinely return transient "Temporary internal error. Please retry" on
  eth_call; a single miss was silently surfacing as 0 credits / chainReadError
  on the dashboard even when the buy succeeded on-chain.
- emailOnEvent: send a purchase confirmation on the Purchase event — credits
  added, new ad-credit balance, POL paid, and a link to view the tx on the
  explorer. Fires server-side for every wallet and the Mini App.
- Added a third Amoy RPC (tenderly) to the volume config for read redundancy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 10:26:53 -05:00
martbost 09804cda22 Fix wallet add/switch-network: use a clean RPC, not the WC proxy
AppKit's built-in networks advertise rpc.walletconnect.org/v1/?chainId=…&projectId=…
as the chain RPC. Wallets reject that query-string URL as "Invalid URL" when
adding/switching the network — Trust showed "Invalid URL", desktop MetaMask
looped on Switch Network, and the mobile buy failed because the switch to Amoy
never completed. Clone the chain with our clean public RPC before handing it to
AppKit/Wagmi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 10:14:23 -05:00
martbost 7d6818a8d4 Buy flow: survive mobile app-switch fetch drops
Mobile drops in-flight fetches when the page returns from the wallet app, which
aborted the purchase before the tx ("Failed to fetch"). Retry /api/me and
/api/sponsor, and make waitTx keep polling through transient fetch failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 10:08:34 -05:00
martbost 8eedfbe6e6 CSP: allow Coinbase Wallet endpoints (connect-src *.coinbase.com)
AppKit's Coinbase Wallet connector calls coinbase.com; allow it so Coinbase
Wallet works alongside the others.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:58:33 -05:00
martbost b172ef674d CSP: allow AppKit's inline bootstrap script by hash (keep no unsafe-inline)
AppKit injects one inline script; allow it via its sha256 hash rather than
opening script-src to unsafe-inline, preserving the strict policy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:55:34 -05:00
martbost 0995293398 CSP: allow blob: images + reown font for AppKit modal
AppKit renders wallet icons from blob: URLs and uses its brand font from
fonts.reown.com; add blob: to img-src and fonts.reown.com to font-src so the
picker shows proper icons and type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:52:19 -05:00
martbost bd4040ff4f Rebuild wallet connect on Reown AppKit (universal, all wallets, working icons)
Replaces the low-level ethereum-provider + deprecated modal (broken icons, jank)
with Reown AppKit loaded from the CDN — the standard connector every wallet
supports, with QR + mobile deep-links and a polished picker. Keeps the exact
SIWE sign-in and contract buy/activate logic, driving AppKit's EIP-1193 provider.
CSP widened for the AppKit SDK/RPC + a worker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:48:38 -05:00
martbost b0f807f6a5 WalletConnect buy: stop overriding gas (let wallet set fees)
Setting fees made MetaMask show an unclearable "site-suggested fee" alert. Hand
fee control back to the wallet; its own estimate meets Amoy's minimum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:38:15 -05:00
martbost 952f2367d6 WalletConnect: always show the wallet picker (no auto-deeplink to last wallet)
Clear WALLETCONNECT_DEEPLINK_CHOICE before each connect so users aren't trapped
auto-launching a wallet that can't finish (e.g. Trust on Amoy); the All Wallets
picker shows every time so they can choose MetaMask.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:32:38 -05:00
martbost 89d636df82 Disconnect wallet: clear all WC state + reload so the picker truly reappears
Clearing localStorage alone wasn't enough — WalletConnect keeps the last wallet
and session in memory, so it auto-reconnected the same wallet. Broadened the
storage purge and reload the page after disconnect, guaranteeing a clean pick.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:26:34 -05:00
martbost 379a5352ea Add "Disconnect wallet" button (clears stuck WC session / switch wallets)
A stuck WalletConnect request or wrong-wallet choice could trap testers. New
IAPWallet.disconnect() drops the WC session, forgets the chosen provider, and
clears WC localStorage so the next connect re-offers the picker. Surfaced as a
"Disconnect wallet" button on the Wallet pane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:21:42 -05:00
martbost d23eb10c58 WalletConnect buy: lower maxFeePerGas ceiling to 50 gwei
250 gwei ceiling tripped MetaMask's "network fee higher than necessary" alert on
Amoy (base fee ~0). 50 gwei keeps priority above Polygon's ~25 gwei floor while
reading as a normal fee, so buyers get a clean Confirm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:12:19 -05:00
martbost 7469a4907c WalletConnect buy: set EIP-1559 fees above Polygon's min priority fee
Amoy/Polygon reject txs whose priority fee is under ~25 gwei ("gas tip below
minimum"); wallets lowball it. Set explicit maxPriorityFeePerGas 30 gwei,
maxFeePerGas 250 gwei ceiling, and a 600k gas limit so buys/activations aren't
rejected or mis-estimated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 09:07:00 -05:00
martbost 90e33a42b5 WalletConnect: add redirect metadata (return to dApp after wallet approvals)
Sets metadata.redirect so mobile wallets bounce the user back to /my after each
approval instead of stranding them in the wallet app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 08:56:10 -05:00
martbost dcfc5ca86e WalletConnect: make the chain optional (fixes Trust hang on Amoy)
Requiring Amoy (80002) in the session namespaces made Trust Wallet hang on
"redirecting" — testnet wallets that don't natively list Amoy can't satisfy a
required-chain proposal. Switched to optionalChains so the session establishes;
the chain is switched/added after connect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 08:44:21 -05:00
martbost 653a5e333a Amoy rehearsal: faucet points testers to the public Amoy faucet
anvil_setBalance doesn't exist on Amoy. The faucet now connects the wallet,
copies the address, and opens faucet.polygon.technology so testers fund their
own wallet with test POL (choose Polygon Amoy). Server endpoint returns the
faucet link + address instead of minting balance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 08:33:46 -05:00
martbost 9cff0dc667 WalletConnect: gate to public chains (137/Amoy), not the private rehearsal
External wallets reject a WalletConnect session for the private rehearsal chain
(31337) with "user rejected", since they don't recognize custom chains. Only
offer WalletConnect on chains wallets know (Polygon mainnet / Amoy); it lights up
automatically when the site points at a public chain. Rehearsal keeps the
injected/dApp-browser path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 08:05:58 -05:00
martbost f0e42e47bb Add WalletConnect: connect from any mobile browser (no dApp browser)
Mobile users in a normal browser hit "no wallet found" because EIP-6963 only
sees injected/extension wallets. WalletConnect fixes it: the connect picker now
offers WalletConnect (auto-selected when no injected wallet is present), which
shows a QR on desktop and opens the wallet app directly on mobile — no in-app
dApp browser, no second login. Lazy-loads the @walletconnect/ethereum-provider
UMD from jsdelivr; the result is a plain EIP-1193 provider so sign/switch/buy are
unchanged. CSP widened for the SDK + WC relay. Project id lives in site config
(public value); gated so nothing changes until it's set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 07:36:07 -05:00
martbost 93912fa621 Ad viewing: full-screen in-page overlay instead of a new tab
Mobile and in-app wallet (dApp) browsers handle tabs badly, so opening each ad in
a new tab made the earn loop hard to navigate and impossible to close. Ads now
open in a full-screen in-page overlay that reuses the /view dwell + human-check +
credit logic in an iframe: an always-working close control, no window.close, no
tab-switching. The framed /view relaxes its focus-pause to visibility only and
messages the dashboard when it credits or the user is done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 07:24:48 -05:00
martbost 50c0bd1aa7 Ad view: reliable exit on mobile/dApp browsers
window.close() is blocked in mobile and in-app wallet (dApp) browsers, which left
users stuck on the ad-view tab. Made "Back to dashboard" the primary action, and
"Close tab" now falls back to navigating to /my#earn when the browser refuses to
close the tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 07:17:39 -05:00
martbost 8d9f56f68a Newsletter opt-in: silent Sendy subscribe on join
Pre-checked "InstantAdPay newsletter" opt-in on the join screen (read by both the
email-code and password signup paths). On new-account creation only, the server
silently subscribes them to the Sendy "InstantAdPay Newsletter" list
(boolean=true, opt-out always wins). New sendy.js helper reads the API key from
SENDY_API_KEY env or DATA_DIR/sendy.key on the volume (same pattern as
sendgrid.key); subscribe is fire-and-forget and never blocks signup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 07:13:50 -05:00
martbost 8ec6286ff7 Add MoonPay card on-ramp (same underpinning as RM Circle)
Signed /api/moonpay-url endpoint: public key from MOONPAY_PUBLIC_KEY env or site
config, SECRET from MOONPAY_SECRET_KEY env ONLY (never site config, since
/api/config exposes siteConfig). Wallet-prefilled signed MoonPay URL when keys
are set, else a generic buy page. "Buy POL with a card" button under the Buy
packages tiles. Zero custody: MoonPay is merchant of record; crypto goes
straight to the buyer's wallet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 06:26:15 -05:00
martbost 6e8777b1d4 Banners: add 125x125 + 336x280, fill skyscrapers; scrollable sidebar
- New 125x125 (square button) and 336x280 (large rectangle) promo banners;
  rectangle branch scales relative so both rectangles look right.
- 160x600 / 120x600 skyscrapers now filled with feature bullets (instant
  payouts, 7 ad formats, earn by viewing, free to join) + CTA.
- Promo tools lists the new sizes; previews cache-busted (?v=3).
- Back-office sidebar is scrollable (overflow-y:auto) so the Legal links at the
  bottom are reachable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 10:20:00 -05:00
martbost 2bfe019c16 Wall: drop the view countdown — a click marks the ad viewed and unlocks join
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 10:13:22 -05:00
martbost 0c803c07cb Legal pages (Terms, Privacy, Disclaimer) + always-present footer
- New /terms, /privacy, /disclaimer pages in the site style with real content
  tailored to an on-chain ad+referral platform (no income guarantee, crypto
  risk, rehearsal note, privacy of email/wallet/profile, acceptable use).
- renderNav now appends a persistent footer with legal + site links on every
  public page; member-area sidebar gets a Legal link group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:38:08 -05:00
martbost 1934225d84 Refresh promo banner kit — code-composited, on-brand, crisp
Regenerated hero 1200x630, 728x90, 468x60, 300x250, 160x600, 120x600 by
compositing the new logo + headline + mint CTA on a dark mint-glow ground
(canvas, not AI) so text is always crisp and on-brand. Leaderboard tagline
auto-fits; previews cache-busted (?v=2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:31:51 -05:00
martbost 4e50e02e9d Ship logo (header + favicon) + bolder downline level labels
- Logo lockup (mint lightning/arrow icon + InstantAdPay wordmark) replaces the
  text wordmark in the back-office sidebar and public nav; icon added as the
  favicon on every page. Assets: /logo.png, /logo-icon.png.
- Downline level captions (LEVEL 1 / LEVEL 2 …) are now a bold mint pill instead
  of faint grey — far more readable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:26:03 -05:00
martbost e430b40090 Surge badge ribbon 0.73->0.76 (dead-center on the banner)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:16:09 -05:00
martbost 8eb8d18769 Surge badge: name ribbon position 0.79->0.73 (was below the ribbon)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:12:13 -05:00
martbost 8e089070dc Recolor achievement badges: Surge purple, Circuit red starburst, Nexus navy
New kie.ai art per Marty's palette — Surge dark neon purple, Circuit bright red
starburst, Nexus navy-blue gradient (Spark stays mint). Full-bleed dark, ornate,
blank ribbon; ribbonY re-tuned per badge; ?v=2 cache-bust on the badge images.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 09:03:45 -05:00
martbost ae580b966c 125x125 sidebar banner (sized ad serving) + shorts report link
- New 125x125 "square button" banner size; ad serve now filters by width/height,
  and the sidebar slot serves a 125x125 banner (empty until such inventory exists).
- Shorts reel gets a "report this short" link (posts to /api/report-ad).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:47:17 -05:00
martbost 31a6560e9e Training center: new members-area pane for videos + materials
New "Training" nav pane lists curated lessons (inline video for mp4/webm, links
for external videos and docs) from /api/training (admin-curated via
data/training.json, with a sensible default).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:41:26 -05:00
martbost 42d0e15fea Visual downline tree on the Overview
"Your line at a glance": YOU + three levels of member chips with connectors,
your qualified directs highlighted in gold, and "+ open" placeholders on levels
1-2 showing what's next. Loads from /api/my/line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:38:15 -05:00
martbost 0dbd99f69e Wall page: mint QR, achievement badge, admin-ad backfill, join gated on views
- QR is mint-green (still high-contrast for reliable scanning).
- The wall shows the owner's achievement badge next to their avatar.
- Positions 2 & 3 fill with upline line-banners, then ADMIN ADS when there's no
  upline (configurable via data/admin-wall-ads.json; sensible default) so the
  wall is never sparse.
- "Join free through this wall" is disabled until the visitor has viewed every
  ad on the wall, with a "X/Y viewed" prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:35:44 -05:00
martbost a4119d652d Sponsor shown on sign-in + new-member onboarding (username, optional profile)
- Sign-in page shows "You're joining the line of @X" when arriving via a sponsor
  link (/api/sponsor now returns the sponsor name + avatar).
- After a new account verifies (or signs up) with no username yet, an onboarding
  modal prompts for a username and an optional bio (both skippable), before the
  welcome tour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:29:12 -05:00
martbost 9fd85e0d6c Multi-wallet support: EIP-6963 discovery + picker
Detects all injected wallets via EIP-6963 (Trust, MetaMask, SafePal, Phantom,
OKX, TokenPocket, and any 6963 wallet), with named fallbacks for ones that don't
announce and window.ethereum(.providers). One wallet connects directly; multiple
show a picker. connect()/sendTx() resolve a provider before use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:24:28 -05:00
martbost 4a9e4c789e Daily login bonus (streak) + solo-ad budget default
- Daily login bonus: once/day, base 5 credits + gentle streak (+1/day, cap +5)
  for consecutive days; granted after sign-in, toast + cha-ching. New
  login_day/login_streak on earned_credits; /api/my/login-bonus.
- Solo ad: selecting Solo now defaults Budget to 50 credits (5cr x 10 min
  deliveries) so entering 10 isn't rejected after submit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:19:40 -05:00
martbost a8968041dc Email notifications (welcome + payout + passed-up) and net->network label
- Welcome email on new account: onboarding steps + who their sponsor is.
- On-chain event emails: a payout received (TierPaid/AwardPaid) and a payout
  that passed you by unqualified (PassedUp), wired into the chain event stream.
- Campaign stats: "+N net" relabeled to "+N network" (clearer than "net").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:15:28 -05:00
martbost 19b7770a4d Video once-per-day dedup + featured gold border & spacing
- Video watch-to-earn: a member can no longer re-earn from the same video the
  same day. New video_seen table (dual-mode); serveVideo excludes today's
  already-watched videos; videowatch double-checks before crediting.
- Featured card: equal top/bottom margin (no longer touches the stat cards) and
  a gold border on the card + the rotating link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:13:09 -05:00
martbost bc95965814 Featured to top, centered, capped; banner ads capped 728px centered; slot CTA
- Featured card moved to the top of Overview, centered, max 760px; the rotating
  link is capped to 728px and centered (banner-sized, not a huge block).
- Served banner creatives cap at min(100%,728px), centered — kills the oversized
  sample banner; ad slots center their creative + label.
- Featured self-sell: shows "N of C slots open today" + "Feature your link →"
  that jumps to Campaigns with Featured preselected.
- Campaign target/image/video URL fields get autocomplete=off and clear when the
  Campaigns pane opens, so no residual (e.g. massifly) URL carries over.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:08:47 -05:00
martbost 3a6d9d037c Ad-report admin recipient via ADMIN_EMAIL env only (siteConfig is public)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:59:26 -05:00
martbost 4991fe96e8 Ad reporting + featured rotates one link + clear campaign form on submit
- Ad reporting (auto-approved ads need a safety valve): new reports.js store
  (dual-mode) + ad_reports table + POST /api/report-ad; a "⚠ report" control on
  served ad slots opens a reason prompt and notifies the admin by email
  (siteConfig.adminEmail / ADMIN_EMAIL).
- Featured links now show ONE link at a time and cycle (true rotation), instead
  of listing all links at once.
- Campaign form now clears EVERY field on submit (target/creative/title/etc.).
  A leftover target URL was carrying into the next campaign — the same reason a
  video short was saved with the wrong (massifly) CTA target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:57:12 -05:00
martbost ea1b2ea0ac Campaign budget field contextual + earnings full-history link
- Hide the free-form "Budget (credits)" field for fixed-cost campaign types
  (Featured = day-slot priced, Verified visits = flat pack) where spend is derived.
- Overview "Recent payouts" gets a "full history ->" link to the Earnings pane,
  which already lists every on-chain payout/referral/purchase with verify links
  (it just wasn't discoverable from the Overview mini-chart).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:44:00 -05:00
martbost 958bd5ac20 Live members-area updates: SSE toasts + cha-ching/pop sounds, no refresh needed
- The members area subscribes to the chain event stream (/api/feed/live) and
  reacts to anything relevant to the member: a payout received (cha-ching + toast
  + auto-refresh), a referral qualifying, a passed-up payout (missed), a settled
  purchase, a new activation in the line. No page refresh required.
- New chat message -> "pop" sound + toast + badge; ping heartbeat (20s) now
  returns chatUnread and pops on an increase.
- Sounds are synthesized via Web Audio (no asset files, CSP-safe).
- Surge badge: nudge the name down (ribbonY .779 -> .805) to center on its ribbon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:36:47 -05:00
martbost 7fbf9add37 Align downline rows into a grid (name | contact | date | action)
Each row was an independent flex line, so variable-width emails threw the date
column out of alignment across rows. Now a CSS grid with a shared template keeps
the date and Message columns lined up; long names/emails truncate; responsive
stack on narrow screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:28:20 -05:00
martbost 1363bd7b0b Fix: buy links wallet first; achievement milestones use live buyerCount
- Buying now ensures the wallet is LINKED to the account before the purchase
  (one signature if not linked). Without it, a wallet only connected for the
  faucet bought successfully but credits resolved against member id 0 and never
  showed until a manual link. Matches the "buying links your wallet" promise.
- Dashboard computed achievement milestones from buyerCount BEFORE reading it
  from chain (always 0), so Surge/Circuit/Nexus never unlocked even when
  qualification advanced. Moved the milestone block after the chain read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:24:26 -05:00
martbost d275822cd4 Fix "undefined joined your line" in Overview recent-activity
The referral row read r.email but dashboard referral objects carry r.name
(username or masked email). Use r.name with an escape + fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 07:09:32 -05:00