- accounts/db: positions table + JSON store (add/list/owner/remove; main-wallet and cross-account guards)
- auth verify: asPosition links a second wallet without touching the session; position wallets can't mint a session
- /api/my/positions (chain-refreshed member ids, buyer counts, credits) + remove
- credits pooled across main + positions on /api/me, dashboard, campaigns; campaign charged to the best-funded position
- My line: own positions listed on level 1 as 'You · position N'
- Buy pane: Qualified Start card + Add a position flow + Buy-from picker with connected-wallet guard
- Wallet pane: Your positions card; chatbot answer updated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- accounts.wall_offers (JSON, up to 2 offers: label, https link, banner) set from
Profile > Your wall; upload supported; locks show the buyer threshold.
- /api/wall assembles: position 1 = own line banner; positions 2-3 = own offer
when unlocked and set, else upline banners (only uplines with a live banner),
else house ads. Response carries unlocked + buyerCount; wall labels show
"this wall" / "their line" / "InstantAdPay".
- Chatbot canned answer + facts, follow-up email 7 mention the ladder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Headless Playwright checks that boot a throwaway local copy for anything that
signs in or writes; npm run qa / qa:public / qa:member / qa:earn.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- /api/my/line sums TierPaid events per buyer for the signed-in member; the
downline rows show "+X POL" (what that person has paid you so far).
- AppKit featuredWalletIds: MetaMask, Phantom, SafePal, Coinbase first; Trust
Wallet stays available under All wallets.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stops early when the wallet lacks the POL. Only Trust Wallet users see the
proportion warning (smaller package / add POL / other wallet); everyone else
goes straight to the wallet confirmation. Buy-pane tip + chatbot reworded.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Probe the session after connect and retry once after a "disconnected" send
error by wiping the stale session and re-opening the picker (Trust kills the
session after its own security stop).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Drafted through the Branded Voice engine (gain, logic, PAS, logic, honest
fear of loss, AIDA, gain + newsletter hand-off), checked against the contract
facts. {{paid:a|b}} resolves per reader from on-chain credits. Defaults now
live in drip-defaults.json; admin overrides still in the volume.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- /join/<token>[?v=angle] now serves a capture page (email first, wallet
later) with angle-matched hook copy, sponsor line, worked-example ledger,
how-it-works, live package ladder, and per-angle og tags (og:url keeps ?v=).
The sponsor cookie is set exactly as before; ?v= is remembered and stored
on the account as joined_via (shown in admin Members).
- drip.js: 4-step getting-started sequence (24h/48h/96h/168h) queued when a
free account is created with the pre-checked opt-in; ticker every 10 min;
signed /unsubscribe link in every email; MySQL + JSON storage.
- Admin > Settings: edit the sequence as JSON, reset to defaults, send any
step to the admin inbox; Overview shows follow-ups in flight.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Social posts for X / Facebook / LinkedIn / Telegram-WhatsApp with post and
share buttons; 5 SMS-sized Text-a-friend messages (Text it / WhatsApp /
Telegram / Copy); email swipes short, standard, long, follow-up.
- Banner kit adds 1080x1080, 1080x1920 and 1280x720 (tools/gen-social-banners.cjs)
with Download buttons.
- Printable half-sheet handouts at /flyers with the member's QR + invite link
(qrlib.js, two per letter page, cut line, print-one).
- Objection handling bank: truth + ready-to-send reply per objection.
- Invite-link strip, Branded Voice CTA, pill menu covers every kit; chatbot
canned answer + prompt facts updated. Angle links carry ?v= for the
upcoming hook pages.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Remove the "sign in with just your wallet" door from the sign-in card.
- A wallet-only session (no account) is walked to the email card with a
finish-setup note; verifying the code links that wallet to the account
and retires the wallet-only session, so member #, purchases and payouts
stay attached and username/profile work.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- /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>