Independent team training resource • Participation involves risk • No income is guaranteed
+
+
Watch first
See how simple this is.
A quick walkthrough of the team build in action. Watch this before you dive into the strategy below.
+
+
The strategy
Simple enough to duplicate.
Each position gets two directs to qualify, then retires its link and helps the next two positions repeat the process. Late signups on a qualified link are a welcome bonus — they pay that member and spill downward as depth.
2
Get exactly two
Use your position's link until two direct positions are placed beneath you and your position is qualified.
↘
Move the effort down
Retire your link and help each of your two directs use their own links until they each have two — that's what qualifies them. If someone still joins through your link anyway, it's a bonus: it pays you and spills down as depth.
↑
Upgrade responsibly
Use earned POL to advance when practical. Active builders may also choose to self-fund, but only within their own risk tolerance.
+
+
Depth over width
2 → 4 → 8 → 16 → 32
The first major team milestone is 30 correctly placed positions across the first four generations: 2 + 4 + 8 + 16.
2
4
8
16
Team principle: once your two are in place, retire your link and shift to helping them get their two — their own directs are the only thing that qualifies their positions to catch payments. Spillover grows your team but never qualifies anyone, so the effort always moves down.
+
+
Live payment proof
Real payouts, straight from the blockchain.
Every payment in this program happens on a public smart contract on Polygon — nobody can fake, hide, or edit it. Below are the latest member payouts, read live from the contract. Tap any row to verify the transaction yourself on Polygonscan.
Reading the blockchain…
Data is read directly from the RM Circle smart contract (0x33Bd…2DAF) on Polygon Mainnet. Member numbers are on-chain IDs, not names. Past payouts are not a promise of future results.
+
+
Ready to join?
Join under Member #—.
Your invite carries your sponsor automatically — no codes to remember. Here's the whole process, start to finish.
+
1
Install or open MetaMask
Use the official MetaMask website or your device's official app store. Never install a wallet from a link sent by a stranger.
Premium entry is 362 POL plus a little extra for network gas. Polygon Mainnet is chain ID 137. New to wallets? The training videos walk through every step.
+
3
Join with your sponsor's link
The button below opens the official RM Circle dApp with Member #— already set as your sponsor. Confirm the sponsor ID matches before signing, and never share your Secret Recovery Phrase with anyone.
After your purchase confirms, the RM Circle dApp shows your new member ID. Type it below — we verify it on the blockchain, your sponsor gets notified, and the team rotation starts working on your 2.
+
What happens next: get your 2 directs to qualify, then retire your link and help your 2 get their 2 — that's the whole system. You'll get your own invite page just like this one the moment you're in.
+
Risk reminder: participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.
+
+
+
diff --git a/public/join.js b/public/join.js
new file mode 100644
index 0000000..a895d62
--- /dev/null
+++ b/public/join.js
@@ -0,0 +1,89 @@
+// Personal invite page (/join/): the full bridge-page story, personalized
+// to one sponsor. Pulls the sponsor's live position via /api/public/member,
+// pins their ID on every CTA, and submits new IDs with sponsorId locked to
+// the page. Prospects who land on a bad/unregistered ID get routed to /start
+// (the team rotation) instead of a dead end.
+(function(){
+ const esc=s=>String(s??'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]));
+ const fmt=n=>Number(n||0).toLocaleString(undefined,{maximumFractionDigits:2});
+ const date=ts=>ts?new Date(ts*1000).toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'}):'—';
+
+ function pathId(){const m=location.pathname.match(/^\/join\/(\d+)$/);return m?m[1]:''}
+ const id=pathId();
+
+ function setIds(idStr){
+ document.getElementById('invId').textContent='#'+idStr;
+ document.getElementById('invIdJoin').textContent='#'+idStr;
+ document.getElementById('invIdBtn').textContent='#'+idStr;
+ document.querySelectorAll('.inv-id-inline').forEach(el=>el.textContent='#'+idStr);
+ }
+
+ function fallbackToRotation(msg){
+ const facts=document.getElementById('invFacts');
+ facts.innerHTML=`
`;
+ const jb=document.getElementById('joinBtn');jb.textContent='Open the Team Sponsor Page →';jb.href='/start';jb.removeAttribute('target');
+ }
+
+ async function load(){
+ if(!id){fallbackToRotation('This invite link is missing its member ID.');return}
+ setIds(id);
+ try{
+ const r=await fetch('/api/public/member?id='+id);
+ const d=await r.json();
+ if(!r.ok||!d.registered)throw new Error('This invite ID isn’t registered on the smart contract.');
+ render(d);
+ }catch(x){fallbackToRotation(x.message)}
+ }
+
+ function render(d){
+ const q=document.getElementById('invQualified');
+ q.outerHTML=d.directCount>=2
+ ?'★ Qualified'
+ :`Building — ${d.directCount}/2 directs`;
+ document.getElementById('invFacts').innerHTML=
+ `
Level${esc(d.levelName)}
`+
+ `
Total received${fmt(d.totalEarnedPol)} POL
`+
+ `
Team below them${d.subtree?`${d.subtree.downCount} member${d.subtree.downCount===1?'':'s'}`:'—'}
`+
+ `
Directs${d.directCount}/2
`+
+ `
Tier${esc(d.tierName)}
`+
+ `
Member since${date(d.joinedAt)}
`;
+ // Qualified sponsor: still a valid join (doctrine: late signups pay them
+ // and spill down), but surface the team wave so nobody's surprised.
+ const note=document.getElementById('invQualNote');
+ if(d.directCount>=2&&d.nextInLine){
+ note.innerHTML=`
This position is already qualified — joining here still works (you'll be placed in their team leg and your entry pays them directly). The team's current focus is helping Member #${d.nextInLine.id} get their 2 — either way, you're on the same team.
`;
+ }
+ const jb=document.getElementById('joinBtn');
+ if(d.referralUrl)jb.href=d.referralUrl;
+ const cp=document.getElementById('copySponsor');
+ cp.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(String(d.id));cp.textContent='Copied ✓';setTimeout(()=>cp.textContent='Copy Sponsor ID',1400)}catch(e){}});
+ }
+
+ document.getElementById('joinForm').addEventListener('submit',async e=>{
+ e.preventDefault();
+ const form=e.currentTarget,msg=document.getElementById('joinMsg');
+ const newId=form.elements.newId.value.trim(),memberName=form.elements.memberName.value.trim();
+ if(!/^\d{1,10}$/.test(newId)){msg.style.color='var(--danger)';msg.textContent='Numbers only — the ID shown by the RM Circle dApp.';return}
+ if(!memberName){msg.style.color='var(--danger)';msg.textContent='Add your name or Telegram handle.';return}
+ msg.style.color='var(--muted)';msg.textContent='Verifying on the blockchain…';
+ try{
+ const r=await fetch('/api/public/submit-id',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({newId,memberName,sponsorId:id||'?',source:'invite-'+(id||'?'),clickid:window.ctbGetClickId?window.ctbGetClickId():''})});
+ const d=await r.json();
+ if(!r.ok)throw new Error(d.error||'Submission failed');
+ form.classList.add('hidden');
+ msg.style.color='var(--ok)';
+ const dash=` Open your own dashboard →`;
+ if(d.duplicate)msg.innerHTML=`✓ ID ${esc(newId)} was already submitted — you're on the list.${dash}`;
+ else if(d.onchain&&d.onchain.registered)msg.innerHTML=`✓ Verified on the blockchain! ID ${esc(newId)} is registered under sponsor #${d.onchain.referrerId} (${esc(d.onchain.tier)}). The team has been notified — welcome aboard.${dash}`;
+ else if(d.path==='notfound'){msg.style.color='var(--danger)';msg.innerHTML=`⚠ ID ${esc(newId)} isn't on the smart contract yet — double-check the number from the RM Circle dApp. Your submission is saved and the team will verify it manually.`;}
+ else msg.innerHTML=`✓ Got it — submitted. The team will verify your placement.${dash}`;
+ }catch(x){msg.style.color='var(--danger)';msg.textContent=x.message}
+ });
+
+ // matrix dots (same as bridge)
+ for(const pid of ['p8','p16']){
+ const node=document.getElementById(pid);if(!node)continue;
+ const count=pid==='p8'?8:16;for(let i=0;i=2){
const nx=d.nextInLine;
if(nx){
- const nxDash=`https://rmcircle.saasy.top/my/${nx.id}`;
- el.innerHTML=`
★ Qualified The team play now moves down. Next in line: #${nx.id} (${nx.directCount}/2 directs). Help them get their 2 — share their page or send prospects the join button, which carries #${nx.id} as sponsor. (If someone still joins through your own link, that's a bonus — the entry reward is yours and they spill down your leg — but only #${nx.id}'s own directs can qualify them.)
When #${nx.id} reaches 2/2 this rotates to the next position in your leg — the qualification wave keeps moving down. General traffic can still go through the team rotation.
★ Qualified The team play now moves down. Next in line: #${nx.id} (${nx.directCount}/2 directs). Help them get their 2 — share their invite page (the full team-build pitch, personalized to them) or send prospects the join button, which carries #${nx.id} as sponsor. (If someone still joins through your own link, that's a bonus — the entry reward is yours and they spill down your leg — but only #${nx.id}'s own directs can qualify them.)
When #${nx.id} reaches 2/2 this rotates to the next position in your leg — the qualification wave keeps moving down. General traffic can still go through the team rotation.
You need ${need} more direct${need===1?'':'s'} to qualify. Until then this is your personal recruiting page — share the link or let a prospect scan the code. They'll see this position's live results, and the join button below carries your ID #${d.id} as sponsor.
When you reach 2/2 this page automatically retires your link from the team's focus and promotes the next position in your leg. Any late signup on your own link is a bonus — it pays you and spills down as depth.
`;
+ el.innerHTML=`
You need ${need} more direct${need===1?'':'s'} to qualify. Until then, share your personal invite page — the link and QR below. A prospect who opens it gets the full team-build story (video, strategy, live blockchain payouts) with your ID #${d.id} pinned as sponsor and your position's real results as proof. This dashboard stays yours; the invite page is what you share.
When you reach 2/2 this page automatically retires your link from the team's focus and promotes the next position in your leg. Any late signup on your own link is a bonus — it pays you and spills down as depth.
`;
const qr=document.getElementById('dQr');if(qr)qr.innerHTML=qrSvg(dashUrl);
const cp=document.getElementById('copyShare');
if(cp)cp.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(dashUrl);cp.textContent='Copied ✓';setTimeout(()=>cp.textContent='Copy my page link',1400)}catch(e){}});
diff --git a/public/track.js b/public/track.js
index 4a785f7..ba4a81e 100644
--- a/public/track.js
+++ b/public/track.js
@@ -19,6 +19,6 @@
}catch(e){}
window.ctbGetClickId=function(){try{return sessionStorage.getItem('ctb.clickid')||''}catch(e){return ''}};
const path=location.pathname.replace(/\/$/,'')||'/';
- const page=path==='/'?'bridge':(path==='/start'?'start':(path==='/training'?'training':null));
+ const page=path==='/'?'bridge':(path==='/start'?'start':(path==='/training'?'training':(/^\/join\/\d+$/.test(path)?'join':null)));
if(page)fetch('/api/public/track',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({event:page,source:getSource()})}).catch(()=>{});
})();
diff --git a/server.js b/server.js
index fa76a73..e9f3ea3 100644
--- a/server.js
+++ b/server.js
@@ -225,7 +225,7 @@ function getConfig() { return readJson(CONFIG_FILE); }
function activeSponsor(sponsors) { return sponsors.find(s=>s.status==='active') || sponsors.find(s=>s.status==='waiting') || null; }
function getAnalytics() { try { return readJson(ANALYTICS_FILE); } catch (e) { return { sources: {} }; } }
function recordEvent(event, source) {
- if (!['bridge','start','click','training','postback','purchase'].includes(event)) return;
+ if (!['bridge','start','click','training','postback','purchase','join'].includes(event)) return;
const s = String(source||'').toLowerCase().trim().replace(/[^a-z0-9.()\-_:/ ]/g,'').slice(0,80) || '(direct)';
const a = getAnalytics(); if (!a.sources) a.sources = {};
if (!a.sources[s]) { if (Object.keys(a.sources).length >= 500) return; a.sources[s] = { bridge:0, start:0, click:0 }; }
@@ -382,7 +382,7 @@ const server=http.createServer(async(req,res)=>{
if(pathname==='/health'||pathname.startsWith('/api/'))return await handleApi(req,res,pathname);
if(req.method!=='GET'&&req.method!=='HEAD')return send(res,405,'Method Not Allowed',{'Content-Type':'text/plain; charset=utf-8'});
let file;
- if(pathname==='/')file=path.join(PUBLIC_DIR,'index.html');else if(pathname==='/start'||pathname==='/start/')file=path.join(PUBLIC_DIR,'start.html');else if(pathname==='/training'||pathname==='/training/')file=path.join(PUBLIC_DIR,'training.html');else if(pathname==='/admin'||pathname==='/admin/')file=path.join(PUBLIC_DIR,'admin.html');else if(pathname==='/my'||pathname==='/my/'||/^\/my\/\d{1,15}$/.test(pathname))file=path.join(PUBLIC_DIR,'my.html');else{
+ if(pathname==='/')file=path.join(PUBLIC_DIR,'index.html');else if(pathname==='/start'||pathname==='/start/')file=path.join(PUBLIC_DIR,'start.html');else if(pathname==='/training'||pathname==='/training/')file=path.join(PUBLIC_DIR,'training.html');else if(pathname==='/admin'||pathname==='/admin/')file=path.join(PUBLIC_DIR,'admin.html');else if(pathname==='/my'||pathname==='/my/'||/^\/my\/\d{1,15}$/.test(pathname))file=path.join(PUBLIC_DIR,'my.html');else if(/^\/join\/\d{1,15}$/.test(pathname))file=path.join(PUBLIC_DIR,'join.html');else if(pathname==='/join'||pathname==='/join/'){res.writeHead(302,{Location:'/start'});return res.end();}else{
const safe=path.normalize(pathname).replace(/^([.][.][/\\])+/, '').replace(/^[/\\]+/,'');file=path.join(PUBLIC_DIR,safe);if(!file.startsWith(PUBLIC_DIR))file='';
}
if(file&&staticFile(req,res,file))return;return staticFile(req,res,path.join(PUBLIC_DIR,'404.html'),404);
@@ -420,4 +420,21 @@ chain.startIndexer(evt=>{
if(sp&&sp.email)sendPaidEmail(sp.email,sp.name,evt);
}
}catch(e){console.error('paid email error',e.message)}
+ // Auto-count directs (Marty 2026-08-14, "prevent manual effort"): a new
+ // registration whose referrer sits in the rotation queue increments that
+ // sponsor's directs automatically — same operation as the admin ⊕ button.
+ // Deliberately does NOT auto-qualify at 2/2: rotating the team focus stays
+ // a human call, so it alerts instead.
+ try{
+ if(evt.type==='registered'&&evt.referrerId!=null){
+ const sponsors=getSponsors();
+ const idx=sponsors.findIndex(x=>String(x.id)===String(evt.referrerId));
+ if(idx>=0&&sponsors[idx].status!=='qualified'&&(Number(sponsors[idx].directs)||0)<2){
+ sponsors[idx].directs=Math.min(2,(Number(sponsors[idx].directs)||0)+1);
+ saveSponsors(sponsors);
+ const s=sponsors[idx];
+ sendTelegram(`🤖 AUTO-COUNT: #${evt.id} is a DIRECT for queue sponsor #${s.id}${s.name?` (${s.name})`:''} — now ${s.directs}/2.${s.directs>=2?'\n🎯 2/2 reached — open /admin and hit Qualify when you want the rotation to move.':''}`);
+ }
+ }
+ }catch(e){console.error('auto-direct error',e.message)}
});