- /admin: email magic-code sign-in allowlisted to ADMIN_EMAIL, 12h admin
session (cookie iap.adm, persisted in the volume). Bearer ADMIN_PASSWORD
API access still works. Member area shows an Admin link for that email.
- House ads: admin places banner/text/login/solo/video/featured/visits
campaigns owned by house@instantadpay.com that cost nothing; budget is
only a delivery cap, spend is never charged or burned.
- Admin APIs: overview, all campaigns (+pause/resume any), members
(+re-point sponsor), reports (+resolve), pending burns, rates/site
config get+patch, creative upload.
- fmtPol rounds to two decimals everywhere (dashboard, toasts, prices).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The email fired at signup, before the member picked a username, so it always
said "A new member." Fire it instead when the username is first set (onboarding
/api/my/profile, empty->set transition only) so the sponsor's email names them
(@username). Removed the signup/verify triggers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Members were activating with sponsorId 0 (their own root) whenever their sponsor
couldn't be resolved — dead pre-wipe links (dkain) or no link at all — so their
purchases rolled to admin instead of building the tree. Default an unresolvable
sponsor to the configured catch position (siteConfig.defaultSponsorId, default
1) in both the buy path (/api/sponsor) and the free-activation path (/api/me),
guarded so #1 itself is never self-sponsored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The prior commit changed wallet.js but the HTML still referenced ?v=r, so
cached browsers kept the old file. Point the HTML at ?v=t.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AppKit's modal.disconnect() can stall on the WalletConnect relay (mobile),
leaving the Disconnect button spinning forever ("just loads and says
disconnect") and never reaching the reload. Race the disconnect against a
1.2s timeout, and fire-and-forget from the button with a guaranteed reload
that drops in-memory wallet state so the picker returns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Testers were tapping the highlighted "Most Popular" $50 tile's Buy thinking it
was a general buy, and getting a $50 charge they couldn't afford. Each tile
already buys its own package; label the button "Buy $20" etc. so it's
unambiguous which package a tap purchases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trust's in-app browser was serving a cached pre-fix HTML page that pointed at
old JS, re-triggering the numeric-chainId crash even after the fix shipped.
Switch HTML from no-cache to no-store so every load fetches the current page and
its current asset versions. Versioned assets still cache for an hour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/sponsor (which the buy flow reads to set sponsorIdIfNew) resolved ONLY the
iap.sponsor cookie. A member whose join cookie was absent at buy time (different
device, cleared cookies, return visit) resolved to 0 and activated on-chain as
their own root instead of under their real sponsor — irreversible. Prefer the
logged-in account's stored sponsorRef, cookie only as anonymous fallback,
matching /api/me.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chain guard did cur.toLowerCase() on the eth_chainId result. Per EIP-695
that's a hex string, but some wallets (Orlando's) return a number, so
cur.toLowerCase was undefined -> "cur.toLowerCase is not a function" aborted the
purchase. Compare chain ids numerically via a chainNum() normalizer (handles
hex string, decimal string, and number) in both the sendTx guard and ensureChain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On click, AppKit's session hasn't always rehydrated yet, so currentAddress()
returned null and we popped the picker even for an already-connected wallet —
and nothing closed it, leaving it stuck on screen after a purchase. Wait briefly
for the existing session to rehydrate before opening the picker, and always
close the modal once we have an address.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
- 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>
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>
- 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>
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>
"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>
- 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>
- 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>
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>
- 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>