Add admin-gated direct on-chain enrollment fallback (/direct-join)

Contingency tool: if the official RM Circle dApp ever goes down, positions can
still be created/upgraded straight from the member's own wallet. Vanilla JS
talks only to window.ethereum (CSP-safe, no external libs), builds register()
[0x30de37e4, sponsorId+tier, value=base*1.05] and upgrade() [0xd55ec697,
value=next-level cost] calls decoded from live txs, reads prices via
getAllCosts() and position via getMember(address). Page lives outside public/
and is served only through a getSession-gated route, so it's admin-only. The
contract is public and immutable, so this cements a company-domain-independent
path to enroll.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-18 05:38:26 -05:00
parent c165226779
commit 438b27ea3b
3 changed files with 210 additions and 1 deletions
+74
View File
@@ -0,0 +1,74 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>Direct On-Chain Enrollment · RM Circle</title><link rel="stylesheet" href="/styles.css">
<style>
.dj-wrap{width:min(760px,calc(100% - 32px));margin:24px auto 70px}
.dj-card{background:var(--card,#0b1e30);border:1px solid var(--line);border-radius:16px;padding:20px 22px;margin:0 0 16px}
.dj-mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px;color:var(--gold);word-break:break-all}
.dj-row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.dj-kv{display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px solid var(--line);font-size:14px}
.dj-kv:last-child{border-bottom:0}
.dj-kv b{color:var(--text)}
.dj-log{font-size:13px;line-height:1.5;padding:8px 10px;border-left:2px solid var(--line);margin:6px 0;color:var(--muted);background:#08192880}
.dj-log.ok{border-color:var(--ok);color:#cfeede}
.dj-log.err{border-color:var(--danger);color:#f3b7ac}
.dj-field{margin:0 0 12px}
.dj-field label{display:block;font-size:12px;color:#a7b8c6;text-transform:uppercase;letter-spacing:.7px;font-weight:800;margin:0 0 6px}
#logWrap{max-height:260px;overflow:auto}
.dj-hide{display:none}
</style></head>
<body class="admin-bg">
<header class="wrap nav"><a class="brand" href="/admin"><div class="brand-mark">RM</div><span><span>RM Circle</span><small>Contingency · On-Chain Enrollment</small></span></a><div class="nav-actions"><a class="btn btn-secondary btn-sm" href="/admin">← Admin</a></div></header>
<main class="dj-wrap">
<div class="callout warning"><strong>Contingency tool — for use only if the official RM Circle dApp is ever unavailable.</strong> This page builds the enrollment/upgrade transaction that <em>your own wallet</em> signs and pays, directly to the smart contract. It holds no funds and has no authority. If an amount is ever wrong the contract simply rejects it and you keep your POL. Normal joins should still go through the official dApp and the <a href="/start" style="color:var(--gold)">Getting Started page</a>.</div>
<div class="dj-card">
<h2 style="margin:0 0 10px;font-size:20px">The contract</h2>
<div class="dj-kv"><span>RM Circle (Polygon Mainnet, chain 137)</span></div>
<div class="dj-mono" style="margin:6px 0 8px">0x33BdAEEfd6d17D80aE53816c916dFb26c4fB2DAF</div>
<a class="btn btn-secondary btn-sm" href="https://polygonscan.com/address/0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf#code" target="_blank" rel="noopener">View verified code on Polygonscan ↗</a>
</div>
<div class="dj-card">
<h2 style="margin:0 0 10px;font-size:20px">1 · Connect your wallet</h2>
<button id="connectBtn" class="btn btn-primary">Connect wallet</button>
<div id="connected" class="dj-hide">
<div class="dj-kv"><span>Wallet</span><b id="wallet" class="dj-mono">—</b></div>
<div class="dj-kv"><span>Network</span><b>Polygon Mainnet ✓</b></div>
<div class="dj-kv"><span>Balance</span><b id="bal">—</b></div>
</div>
</div>
<div id="posBox" class="dj-card dj-hide">
<h2 style="margin:0 0 8px;font-size:20px">Your existing position</h2>
<p id="posInfo" style="color:var(--muted);font-size:14px;margin:0"></p>
<div id="upBox" class="dj-hide" style="margin-top:14px">
<div class="dj-kv"><span>Upgrade to</span><b id="upNext">—</b></div>
<div class="dj-kv"><span>Cost</span><b id="upCost">—</b></div>
<button id="upgradeBtn" class="btn btn-teal" style="margin-top:12px" data-tier="2" data-level="1">Upgrade my position →</button>
</div>
<p id="upInfo" style="color:var(--muted);font-size:14px;margin:8px 0 0"></p>
</div>
<div class="dj-card">
<h2 style="margin:0 0 10px;font-size:20px">2 · Register a new position</h2>
<p style="color:var(--muted);font-size:14px;margin:0 0 14px">A wallet can hold one position. To create a new one, connect a fresh wallet funded with enough POL, enter the sponsor ID to join under, and register.</p>
<div class="dj-field"><label for="sponsorId">Sponsor ID (who you join under)</label><input id="sponsorId" class="input" inputmode="numeric" placeholder="e.g. 21"></div>
<div class="dj-field"><label for="tier">Tier</label><select id="tier" class="select"><option value="2">Premium (full — team standard)</option><option value="1">Standard (half)</option></select></div>
<div class="dj-kv"><span>You will pay (Premium)</span><b id="joinCost">—</b></div>
<button id="registerBtn" class="btn btn-primary" style="margin-top:14px">Register position →</button>
</div>
<div class="dj-card">
<h2 style="margin:0 0 8px;font-size:16px">Advanced (optional)</h2>
<div class="dj-field"><label for="customPol">Override POL amount</label><input id="customPol" class="input" placeholder="leave blank to use the auto-calculated amount"></div>
<p style="color:var(--muted);font-size:12px;margin:0">Only use this if you need to send a specific amount. The auto amount is read live from the contract's own price table.</p>
</div>
<div class="dj-card">
<h2 style="margin:0 0 8px;font-size:16px">Activity</h2>
<div id="logWrap"><div id="log"></div></div>
</div>
<p class="micro" style="text-align:center">No income is guaranteed. Cryptocurrency and smart-contract participation carry risk. Only use funds you can afford to lose.</p>
</main>
<script src="/direct-join.js"></script></body></html>