MoonPay card on-ramp for crypto-new members (zero custody)

- GET /api/public/moonpay-url: returns a MoonPay checkout link for POL on
  Polygon (pol_polygon). With partner keys in config (moonpayPublicKey/
  moonpaySecretKey, now PATCHable), the URL is HMAC-signed and prefilled
  with the member's own wallet address + shortfall amount; without keys it
  falls back to MoonPay's generic buy page. MoonPay is merchant of record -
  the site never touches funds.
- Join page: when the connected wallet can't cover the Premium entry, a
  funding box appears with the exact shortfall, a buy button (new tab), and
  a refresh-balance button.
- Training page: "buy POL with a card" callout after the funding video with
  the three things to get right (POL, Polygon network, own address).
- Start page: one-line card-buy pointer under the sponsor card.
- Chatbot canned answer + AI system prompt updated (funding guidance).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-19 09:44:01 -05:00
parent f0a35a0127
commit d620555209
6 changed files with 52 additions and 4 deletions
+7
View File
@@ -27,6 +27,13 @@
<div class="dj-kv"><span>Balance</span><b id="bal">—</b></div> <div class="dj-kv"><span>Balance</span><b id="bal">—</b></div>
</div> </div>
<div id="already" style="display:none;margin-top:12px;color:#f0b4aa;font-size:14px"></div> <div id="already" style="display:none;margin-top:12px;color:#f0b4aa;font-size:14px"></div>
<div id="fundBox" style="display:none;margin-top:14px;padding:14px;border:1px solid rgba(240,197,109,.4);border-radius:12px;background:rgba(240,197,109,.06)">
<div style="font-weight:800;margin-bottom:6px">💳 Need POL? Buy it with a card.</div>
<p id="fundMsg" style="color:var(--muted);font-size:13px;margin:0 0 10px"></p>
<button id="fundBtn" class="btn btn-secondary" style="width:100%">Buy POL with card / Apple Pay (MoonPay) →</button>
<button id="fundRefresh" class="btn btn-secondary btn-sm" style="width:100%;margin-top:8px">I've bought it — refresh my balance</button>
<p class="micro" style="margin:8px 0 0">MoonPay opens in a new tab. Choose <strong>POL on Polygon</strong>; the crypto is delivered to <strong>your own wallet</strong> — this site never touches your money. Card fees are MoonPay's (~4.5%), and first purchases can take a few minutes to arrive.</p>
</div>
</div> </div>
<div id="joinBox" class="dj-card" style="display:none"> <div id="joinBox" class="dj-card" style="display:none">
+1 -1
View File
@@ -21,7 +21,7 @@
{k:['metamask','wallet','create wallet','install','set up wallet','setup wallet'], {k:['metamask','wallet','create wallet','install','set up wallet','setup wallet'],
a:()=>`Install MetaMask only from the <a href="https://metamask.io/" target="_blank" rel="noopener noreferrer">official site</a> or your device's official app store, then create your wallet. <a href="/training">Video 2 in the training</a> walks through it step by step. Never share your Secret Recovery Phrase with anyone — not the team, not this site, not a "support agent".`}, a:()=>`Install MetaMask only from the <a href="https://metamask.io/" target="_blank" rel="noopener noreferrer">official site</a> or your device's official app store, then create your wallet. <a href="/training">Video 2 in the training</a> walks through it step by step. Never share your Secret Recovery Phrase with anyone — not the team, not this site, not a "support agent".`},
{k:['fund','funding','buy pol','get pol','deposit','exchange','transfer','send pol'], {k:['fund','funding','buy pol','get pol','deposit','exchange','transfer','send pol'],
a:()=>`You'll need about ${pol()} POL plus a little extra for gas, on Polygon Mainnet. <a href="/training">Video 3 in the training</a> shows how to fund your wallet. Double-check you're sending POL on the Polygon network — not another chain.`}, a:()=>`You'll need about ${pol()} POL plus a little extra for gas, on Polygon Mainnet. <a href="/training">Video 3 in the training</a> shows how to fund your wallet. Brand new to crypto? You can buy POL directly with a debit/credit card, Apple Pay, or Google Pay via <a href="https://www.moonpay.com/buy/pol" target="_blank" rel="noopener noreferrer">MoonPay</a> — pick POL on the Polygon network and send it to your own wallet address. The join page also offers this automatically if your connected wallet is short. Double-check you're getting POL on Polygon — not another chain.`},
{k:['join','buy','purchase','premium position','enter','sign up','signup','register','get started','start'], {k:['join','buy','purchase','premium position','enter','sign up','signup','register','get started','start'],
a:()=>`Ready to join? Go to the <a href="/start">Getting Started page</a> — it shows the current team sponsor and lets you connect your wallet and buy your position right on the site, with the sponsor ID filled in automatically. <a href="/training">Video 4 in the training</a> walks through the whole process (and Video 5 covers the dApp backup method).`}, a:()=>`Ready to join? Go to the <a href="/start">Getting Started page</a> — it shows the current team sponsor and lets you connect your wallet and buy your position right on the site, with the sponsor ID filled in automatically. <a href="/training">Video 4 in the training</a> walks through the whole process (and Video 5 covers the dApp backup method).`},
{k:['sponsor','current sponsor','who do i join under','placement','whose link','referral'], {k:['sponsor','current sponsor','who do i join under','placement','whose link','referral'],
+22 -1
View File
@@ -57,7 +57,26 @@
}catch(e){ $('sponsorLine').textContent='Could not load the current sponsor — please refresh.'; } }catch(e){ $('sponsorLine').textContent='Could not load the current sponsor — please refresh.'; }
} }
async function refreshBalance(){ try{ var b=await req('eth_getBalance',[account,'latest']); $('bal').textContent=polStr(BigInt(b))+' POL'; }catch(e){} } var lastBal=null;
async function refreshBalance(){ try{ var b=await req('eth_getBalance',[account,'latest']); lastBal=BigInt(b); $('bal').textContent=polStr(lastBal)+' POL'; updateFundBox(); }catch(e){} }
// Card on-ramp: shown only when the connected wallet can't cover the entry.
function updateFundBox(){
var box=$('fundBox'); if(!box||!costs||lastBal===null) return;
var need=regValue();
if(lastBal>=need){ box.style.display='none'; return; }
var shortPol=Math.max(62,Math.ceil(Number(need-lastBal)/1e18)+5);
$('fundMsg').textContent='This wallet holds '+polStr(lastBal)+' POL; the Premium entry is '+polStr(need)+' POL plus a little gas. You can buy the remaining ~'+shortPol+' POL with a debit/credit card, Apple Pay, or Google Pay — delivered straight to this wallet.';
box.dataset.pol=shortPol;
box.style.display='block';
}
async function openMoonpay(){
try{
var box=$('fundBox');
var r=await (await fetch('/api/public/moonpay-url?address='+encodeURIComponent(account||'')+'&pol='+encodeURIComponent((box&&box.dataset.pol)||''))).json();
if(r&&r.url){ window.open(r.url,'_blank','noopener'); log(r.signed?'MoonPay opened in a new tab with your wallet address pre-filled — complete the purchase there, then come back and refresh your balance.':'MoonPay opened in a new tab. Choose POL on the Polygon network and paste YOUR wallet address ('+account+') as the destination, then come back and refresh your balance.','ok'); }
}catch(e){ log('Could not open MoonPay: '+(e.message||e),'err'); }
}
async function connect(){ async function connect(){
try{ try{
@@ -133,5 +152,7 @@
loadSponsor(); loadSponsor();
$('connectBtn').addEventListener('click',connect); $('connectBtn').addEventListener('click',connect);
$('joinBtn').addEventListener('click',doJoin); $('joinBtn').addEventListener('click',doJoin);
var fb=$('fundBtn'); if(fb)fb.addEventListener('click',openMoonpay);
var fr=$('fundRefresh'); if(fr)fr.addEventListener('click',function(){ log('Checking your balance…'); refreshBalance(); });
}); });
})(); })();
+1 -1
View File
@@ -5,7 +5,7 @@
<body> <body>
<header class="wrap nav"><a class="brand" href="/"><div class="brand-mark">RM</div><span><span id="brandName">RM Circle</span><small>Getting Started</small></span></a><div class="nav-actions"><a class="btn btn-secondary" href="/">← Strategy</a><a class="btn btn-secondary" href="/training">Training</a></div></header> <header class="wrap nav"><a class="brand" href="/"><div class="brand-mark">RM</div><span><span id="brandName">RM Circle</span><small>Getting Started</small></span></a><div class="nav-actions"><a class="btn btn-secondary" href="/">← Strategy</a><a class="btn btn-secondary" href="/training">Training</a></div></header>
<main class="sponsor-shell"><div class="wrap"><div class="section-head"><div class="eyebrow">Current placement</div><h1 style="font-size:clamp(34px,5vw,54px);margin:8px 0 10px">Use the sponsor shown on <span class="gold">this page.</span></h1><p>Team placements rotate as positions become qualified. Do not use an old screenshot or saved referral link.</p></div> <main class="sponsor-shell"><div class="wrap"><div class="section-head"><div class="eyebrow">Current placement</div><h1 style="font-size:clamp(34px,5vw,54px);margin:8px 0 10px">Use the sponsor shown on <span class="gold">this page.</span></h1><p>Team placements rotate as positions become qualified. Do not use an old screenshot or saved referral link.</p></div>
<div class="sponsor-grid"><aside class="sponsor-card"><span class="live-badge"><span class="dot"></span> Current team sponsor</span><div class="sponsor-id" id="sponsorId">—</div><div class="sponsor-name" id="sponsorName">Loading current placement…</div><div id="progressArea"><div style="display:flex;justify-content:space-between;color:var(--muted);font-size:13px"><span>Qualification progress</span><strong id="progressText">0 / 2</strong></div><div class="progress-line"><span id="progressBar" style="width:0%"></span></div></div><div class="facts"><div class="fact"><small>Program</small><strong>Premium</strong></div><div class="fact"><small>Entry</small><strong><span id="entryPol">362</span> POL</strong></div><div class="fact"><small>Network</small><strong>Polygon</strong></div><div class="fact"><small>Gas token</small><strong>POL</strong></div></div><a id="joinButton" class="btn btn-primary" style="width:100%" href="/join-now">Join With Current Sponsor →</a><button id="copyButton" class="btn btn-secondary" style="width:100%;margin-top:9px">Copy Sponsor ID</button><p class="micro" id="queueText" style="text-align:center"></p><a id="dappJoinLink" class="micro hidden" href="#" target="_blank" rel="noopener noreferrer" style="display:block;text-align:center;margin-top:4px">Prefer the official RM Circle dApp? Join there →</a></aside> <div class="sponsor-grid"><aside class="sponsor-card"><span class="live-badge"><span class="dot"></span> Current team sponsor</span><div class="sponsor-id" id="sponsorId">—</div><div class="sponsor-name" id="sponsorName">Loading current placement…</div><div id="progressArea"><div style="display:flex;justify-content:space-between;color:var(--muted);font-size:13px"><span>Qualification progress</span><strong id="progressText">0 / 2</strong></div><div class="progress-line"><span id="progressBar" style="width:0%"></span></div></div><div class="facts"><div class="fact"><small>Program</small><strong>Premium</strong></div><div class="fact"><small>Entry</small><strong><span id="entryPol">362</span> POL</strong></div><div class="fact"><small>Network</small><strong>Polygon</strong></div><div class="fact"><small>Gas token</small><strong>POL</strong></div></div><a id="joinButton" class="btn btn-primary" style="width:100%" href="/join-now">Join With Current Sponsor →</a><button id="copyButton" class="btn btn-secondary" style="width:100%;margin-top:9px">Copy Sponsor ID</button><p class="micro" style="text-align:center;margin-top:9px">💳 No crypto yet? You can <a href="https://www.moonpay.com/buy/pol" target="_blank" rel="noopener nofollow" style="color:var(--teal)">buy POL with a card via MoonPay</a> — sent to your own wallet. How-to in <a href="/training" style="color:var(--teal)">the training</a>.</p><p class="micro" id="queueText" style="text-align:center"></p><a id="dappJoinLink" class="micro hidden" href="#" target="_blank" rel="noopener noreferrer" style="display:block;text-align:center;margin-top:4px">Prefer the official RM Circle dApp? Join there →</a></aside>
<section><div class="callout" style="margin-bottom:16px"><strong>First time?</strong> <a href="/training" style="color:var(--teal)">Watch the four training videos</a> — how the build works, creating your MetaMask wallet, funding it, and buying your position.</div><div class="instruction-list"><article class="instruction"><div class="num">1</div><div><h3>Install or open a wallet</h3><p>Use <strong>MetaMask</strong> or <strong>Trust Wallet</strong> — only from the official website or your device's official app store. Never install a wallet from a link sent by a stranger.</p><div style="margin-top:10px"><a class="btn btn-secondary btn-sm" href="https://metamask.io/" target="_blank" rel="noopener noreferrer">Official MetaMask Site ↗</a></div></div></article> <section><div class="callout" style="margin-bottom:16px"><strong>First time?</strong> <a href="/training" style="color:var(--teal)">Watch the four training videos</a> — how the build works, creating your MetaMask wallet, funding it, and buying your position.</div><div class="instruction-list"><article class="instruction"><div class="num">1</div><div><h3>Install or open a wallet</h3><p>Use <strong>MetaMask</strong> or <strong>Trust Wallet</strong> — only from the official website or your device's official app store. Never install a wallet from a link sent by a stranger.</p><div style="margin-top:10px"><a class="btn btn-secondary btn-sm" href="https://metamask.io/" target="_blank" rel="noopener noreferrer">Official MetaMask Site ↗</a></div></div></article>
<article class="instruction"><div class="num">2</div><div><h3>Use Polygon Mainnet</h3><p>In MetaMask, select Polygon. Polygon Mainnet uses chain ID <strong>137</strong>, and POL is the native gas token.</p></div></article> <article class="instruction"><div class="num">2</div><div><h3>Use Polygon Mainnet</h3><p>In MetaMask, select Polygon. Polygon Mainnet uses chain ID <strong>137</strong>, and POL is the native gas token.</p></div></article>
<article class="instruction"><div class="num">3</div><div><h3>Fund your wallet with POL</h3><p>Premium entry is <strong><span id="entryPol2">362</span> POL</strong>. Keep a small additional POL balance available for network gas. POL's USD value changes with the market.</p></div></article> <article class="instruction"><div class="num">3</div><div><h3>Fund your wallet with POL</h3><p>Premium entry is <strong><span id="entryPol2">362</span> POL</strong>. Keep a small additional POL balance available for network gas. POL's USD value changes with the market.</p></div></article>
+2
View File
@@ -14,6 +14,8 @@
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 3 · 3 min</div><h2 style="margin:6px 0 6px">Funding your wallet</h2><p style="color:var(--muted);margin:0 0 12px">Get POL onto Polygon Mainnet — enough for the 362 POL Premium entry plus a little extra for gas.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/funding_your_wallet_poster.jpg" src="/training/funding_your_wallet.mp4"></video></div> <div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 3 · 3 min</div><h2 style="margin:6px 0 6px">Funding your wallet</h2><p style="color:var(--muted);margin:0 0 12px">Get POL onto Polygon Mainnet — enough for the 362 POL Premium entry plus a little extra for gas.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/funding_your_wallet_poster.jpg" src="/training/funding_your_wallet.mp4"></video></div>
<div class="callout" style="max-width:880px;margin:0 auto 26px"><strong>💳 Brand new to crypto? You can buy POL with a card.</strong> If you don't have crypto anywhere yet, the simplest path is <a href="https://www.moonpay.com/buy/pol" target="_blank" rel="noopener nofollow">MoonPay</a> — buy with a debit/credit card, Apple Pay, or Google Pay. Three things to get right: choose <strong>POL on the Polygon network</strong>, paste <strong>your own wallet address</strong> from Video 2 as the destination, and buy about <strong>385 POL</strong> (the 362 entry plus a little gas). The crypto is delivered straight to your wallet — this site never touches your money. MoonPay handles ID verification and charges its own card fee (~4.5%); a first purchase can take a few minutes to arrive. The join page will also offer this automatically if your connected wallet is short.</div>
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 4 · 4 min · the new way to join</div><h2 style="margin:6px 0 6px">Joining right on the site — connect wallet &amp; buy your position</h2><p style="color:var(--muted);margin:0 0 12px">The whole join process, start to finish: open your invite link (or <a href="/start" style="color:var(--teal)">the start page</a>), connect your wallet, and buy your Premium position in one guided flow — the sponsor ID is filled in for you automatically.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/new_integrated_join_process_poster.jpg" src="/training/new_integrated_join_process.mp4"></video></div> <div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 4 · 4 min · the new way to join</div><h2 style="margin:6px 0 6px">Joining right on the site — connect wallet &amp; buy your position</h2><p style="color:var(--muted);margin:0 0 12px">The whole join process, start to finish: open your invite link (or <a href="/start" style="color:var(--teal)">the start page</a>), connect your wallet, and buy your Premium position in one guided flow — the sponsor ID is filled in for you automatically.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/new_integrated_join_process_poster.jpg" src="/training/new_integrated_join_process.mp4"></video></div>
<div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 5 · 5 min · backup method</div><h2 style="margin:6px 0 6px">Buying the Premium position in the dApp</h2><p style="color:var(--muted);margin:0 0 12px">If the on-site wallet connect isn't an option for you, you can still join the original way: connect to the RM Circle dApp with the current team sponsor and complete your Premium entry there. Double-check the sponsor ID matches the start page before signing.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/buying_the_premium_position_poster.jpg" src="/training/buying_the_premium_position.mp4"></video></div> <div class="video-card" style="max-width:880px;margin:0 auto 26px"><div style="padding:6px 6px 0"><div class="eyebrow">Video 5 · 5 min · backup method</div><h2 style="margin:6px 0 6px">Buying the Premium position in the dApp</h2><p style="color:var(--muted);margin:0 0 12px">If the on-site wallet connect isn't an option for you, you can still join the original way: connect to the RM Circle dApp with the current team sponsor and complete your Premium entry there. Double-check the sponsor ID matches the start page before signing.</p></div><video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata" poster="/training/buying_the_premium_position_poster.jpg" src="/training/buying_the_premium_position.mp4"></video></div>
+19 -1
View File
@@ -45,6 +45,7 @@ FACTS:
- RESILIENCE ("what if the creators disappear / owner loses keys / it falls apart over time"): the contract is autonomous and immutable — NO admin action, heartbeat, or living operator is required for joins, upgrades, matrix placement, or payouts; there is no pause switch and no expiry. Verified on-chain that the founder, development, and fee-receiver wallets are ordinary wallets (EOAs), NOT smart contracts — an ordinary wallet always accepts incoming POL even if its key is lost forever, so a dead or abandoned admin wallet cannot block any member payment (only the project's OWN uncollected fee would sit idle). The contract stores no balance (every payment is delivered in the same transaction). If the owner's key were lost, only the four limited admin powers freeze in place; members are unaffected. Details in section 6 of https://rmcircle.team/contract. - RESILIENCE ("what if the creators disappear / owner loses keys / it falls apart over time"): the contract is autonomous and immutable — NO admin action, heartbeat, or living operator is required for joins, upgrades, matrix placement, or payouts; there is no pause switch and no expiry. Verified on-chain that the founder, development, and fee-receiver wallets are ordinary wallets (EOAs), NOT smart contracts — an ordinary wallet always accepts incoming POL even if its key is lost forever, so a dead or abandoned admin wallet cannot block any member payment (only the project's OWN uncollected fee would sit idle). The contract stores no balance (every payment is delivered in the same transaction). If the owner's key were lost, only the four limited admin powers freeze in place; members are unaffected. Details in section 6 of https://rmcircle.team/contract.
- Current team sponsor: ${a ? `ID ${a.id}${c.showSponsorName && a.name ? ` (${a.name})` : ''}, ${a.directs}/2 directs` : 'shown on the start page'}. ${waiting} placement(s) waiting. Placements rotate as positions qualify — always verify on https://rmcircle.team/start right before joining. - Current team sponsor: ${a ? `ID ${a.id}${c.showSponsorName && a.name ? ` (${a.name})` : ''}, ${a.directs}/2 directs` : 'shown on the start page'}. ${waiting} placement(s) waiting. Placements rotate as positions qualify — always verify on https://rmcircle.team/start right before joining.
- Site pages: https://rmcircle.team/ (strategy overview + roadmap + live team stats), https://rmcircle.team/start (current sponsor + join steps), https://rmcircle.team/training (6 videos — team overview, wallet setup, funding, the new connect-wallet join flow on the site, the dApp backup method, how payments work — + spillover article), https://rmcircle.team/how-pay-works (the two income streams shown as a pay-flow diagram + Premium/Standard tier comparison), https://rmcircle.team/contract (plain-language security review of the verified smart contract — code can't change, no pooled funds, locked rules, honest list of operator powers), https://rmcircle.team/my (member dashboard), https://rmcircle.team/tools (for existing team members who want to promote — share-ready promo videos, copy-paste social posts, short/long email swipes, and a downloadable banner kit in every standard size; to write promos in their own voice, mybrandedvoice.com), https://rmcircle.team/disclaimer (affiliate/earnings/risk disclosures). - Site pages: https://rmcircle.team/ (strategy overview + roadmap + live team stats), https://rmcircle.team/start (current sponsor + join steps), https://rmcircle.team/training (6 videos — team overview, wallet setup, funding, the new connect-wallet join flow on the site, the dApp backup method, how payments work — + spillover article), https://rmcircle.team/how-pay-works (the two income streams shown as a pay-flow diagram + Premium/Standard tier comparison), https://rmcircle.team/contract (plain-language security review of the verified smart contract — code can't change, no pooled funds, locked rules, honest list of operator powers), https://rmcircle.team/my (member dashboard), https://rmcircle.team/tools (for existing team members who want to promote — share-ready promo videos, copy-paste social posts, short/long email swipes, and a downloadable banner kit in every standard size; to write promos in their own voice, mybrandedvoice.com), https://rmcircle.team/disclaimer (affiliate/earnings/risk disclosures).
- BUYING POL WITH A CARD (for people brand new to crypto): the site links to MoonPay (moonpay.com/buy/pol) on the training page, the start page, and automatically on the join page when a connected wallet's balance is short. Guidance to give: choose POL on the POLYGON network, send it to YOUR OWN wallet address, buy about entry + gas (~385 POL). MoonPay is an independent company (merchant of record) — it handles ID verification and charges its own card fee (~4.5%); this site never touches or holds anyone's money. First purchases can take a few minutes to arrive.
- COACHING DOCTRINE (teach forward): whenever you give a member guidance about helping their team, frame it so they learn to run the same play for their own two — e.g. "do X, then show your two how to spot this on THEIR dashboard's Coach Your Team panel." The goal is never just fixing one member's next step; it is teaching people how to teach. Every member's dashboard has the same Coach Your Team panel, so the play duplicates at every depth. - COACHING DOCTRINE (teach forward): whenever you give a member guidance about helping their team, frame it so they learn to run the same play for their own two — e.g. "do X, then show your two how to spot this on THEIR dashboard's Coach Your Team panel." The goal is never just fixing one member's next step; it is teaching people how to teach. Every member's dashboard has the same Coach Your Team panel, so the play duplicates at every depth.
- Telegram group for live team help: ${c.telegramUrl || 'https://t.me/cryptoteambuild'} - Telegram group for live team help: ${c.telegramUrl || 'https://t.me/cryptoteambuild'}
- OBJECTIONS (answer honestly, never with hype or promises): - OBJECTIONS (answer honestly, never with hype or promises):
@@ -468,6 +469,23 @@ async function handleApi(req,res,pathname){
sendEmailRaw(email,`Alerts on for RM Circle position #${id}`,`You're now subscribed to alerts for RM Circle position #${id}.\n\nYou'll get an email when this position is paid, and when it needs an upgrade to catch incoming pay.\n\nSee your position anytime: https://rmcircle.team/my/${id}\nStop these alerts: ${unsubUrl(id)}\n\n— The RM Circle Team`); sendEmailRaw(email,`Alerts on for RM Circle position #${id}`,`You're now subscribed to alerts for RM Circle position #${id}.\n\nYou'll get an email when this position is paid, and when it needs an upgrade to catch incoming pay.\n\nSee your position anytime: https://rmcircle.team/my/${id}\nStop these alerts: ${unsubUrl(id)}\n\n— The RM Circle Team`);
return json(res,200,{ok:true,subscribed:true,email:maskEmail(email)}); return json(res,200,{ok:true,subscribed:true,email:maskEmail(email)});
} }
if(req.method==='GET'&&pathname==='/api/public/moonpay-url'){
// Card on-ramp deep link. With MoonPay partner keys configured (admin
// config), returns a SIGNED checkout URL prefilled with the member's own
// wallet address + POL amount; otherwise a generic MoonPay buy page.
// Zero custody either way — MoonPay is merchant of record, crypto goes
// straight to the member's wallet.
const q=new URL(req.url,'http://x').searchParams;
const addr=(q.get('address')||'').trim();
let pol=Math.round(Number(q.get('pol'))||0); if(!pol||pol<62)pol=Math.max(62,Math.ceil((Number(getConfig().premiumEntryPol)||362)*1.05)+5); if(pol>100000)pol=100000;
const c=getConfig(), pk=(c.moonpayPublicKey||'').trim(), sk=(c.moonpaySecretKey||'').trim();
if(pk&&sk&&/^0x[0-9a-fA-F]{40}$/.test(addr)){
const qs='?apiKey='+encodeURIComponent(pk)+'&currencyCode=pol_polygon&walletAddress='+encodeURIComponent(addr)+'&quoteCurrencyAmount='+pol;
const sig=crypto.createHmac('sha256',sk).update(qs).digest('base64');
return json(res,200,{url:'https://buy.moonpay.com/'+qs+'&signature='+encodeURIComponent(sig),signed:true,pol});
}
return json(res,200,{url:'https://www.moonpay.com/buy/pol',signed:false,pol});
}
if(req.method==='GET'&&pathname==='/api/public/current-sponsor'){ if(req.method==='GET'&&pathname==='/api/public/current-sponsor'){
const sponsors=getSponsors(),c=getConfig(),a=activeSponsor(sponsors);if(!a)return json(res,404,{error:'No active sponsor is currently assigned.'}); const sponsors=getSponsors(),c=getConfig(),a=activeSponsor(sponsors);if(!a)return json(res,404,{error:'No active sponsor is currently assigned.'});
return json(res,200,{sponsor:publicSponsorPayload(a,c),waitingCount:sponsors.filter(s=>s.status==='waiting').length,message:'Always use the current sponsor shown on this page. Team placement rotates as members qualify.'}); return json(res,200,{sponsor:publicSponsorPayload(a,c),waitingCount:sponsors.filter(s=>s.status==='waiting').length,message:'Always use the current sponsor shown on this page. Team placement rotates as members qualify.'});
@@ -573,7 +591,7 @@ async function handleApi(req,res,pathname){
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors}); const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
} }
if(req.method==='PATCH'&&pathname==='/api/admin/config'){ if(req.method==='PATCH'&&pathname==='/api/admin/config'){
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next}); const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
} }
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/); const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'}); if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});