Repository navigation
Expand file tree
/
Copy pathdeploy-pot.html
More file actions
167 lines (157 loc) · 9.71 KB
/
Copy pathdeploy-pot.html
File metadata and controls
167 lines (157 loc) · 9.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<!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">
<title>Deploy the reward pot</title>
<style>
:root{--fond:#04090c;--carte:#0b1c22;--ligne:#16323c;--texte:#f2fbff;--doux:#9cc3cf;--accent:#35d3f0;--ok:#3ddc97;--ko:#ff6b6b}
body{margin:0;background:var(--fond);color:var(--texte);font:15px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}
main{max-width:640px;margin:0 auto;padding:24px 16px}
h1{font-size:20px;margin:0 0 6px}
h2{font-size:15px;margin:0 0 6px}
.carte{background:var(--carte);border:1px solid var(--ligne);border-radius:14px;padding:16px;margin:14px 0}
.ligne{display:flex;gap:10px;align-items:baseline;margin:6px 0}
.ligne b{min-width:18px}
code{font-size:12.5px;word-break:break-all;color:var(--doux)}
button{font:inherit;font-weight:700;padding:12px 18px;border-radius:12px;border:0;cursor:pointer;background:var(--accent);color:#012}
button[disabled]{opacity:.45;cursor:not-allowed}
.ok{color:var(--ok)} .ko{color:var(--ko)} .doux{color:var(--doux)}
ul{margin:6px 0 0;padding-left:18px} li{margin:4px 0}
</style>
</head>
<body>
<main>
<h1>Deploy the reward pot</h1>
<p class="doux">One transaction, signed by <b>your</b> wallet. This page never sees a key. The contract has no owner and
lands at the same address whoever sends it — the address depends only on its code.</p>
<div class="carte">
<h2>What this pot is</h2>
<ul class="doux">
<li><b>It pays the people who hold a block</b>, from a pot anyone can fill. Each round, one past
block is drawn and the balances at that exact block decide the split.</li>
<li><b>Nobody can take the money out.</b> There is no withdraw function in this contract — not for
an owner, not for an operator, not for me. What goes in only leaves through a proven claim, or
rolls over into a later round, and anyone can trigger that roll.</li>
<li><b>The draw cannot be picked after the fact.</b> A round can only be opened while its end is
still in the future, so the randomness that decides the drawn block does not exist yet when the
round is committed. The chain enforces that one.</li>
<li><b>Claims wait 6 hours after a root is posted.</b> The contract cannot check that root itself;
that delay is the window where anyone can recompute it and say it is wrong.</li>
<li class="ko"><b>What it cannot check, said plainly:</b> that the seed really is the randomness of
the end block, and that the root really matches the balances at the drawn block. Both are
recomputable by anyone from public data — that is why claims are not instant.</li>
</ul>
</div>
<div class="carte">
<div class="ligne"><b id="c1">…</b><span>Base mainnet reachable, hook address still empty</span></div>
<div class="ligne"><b id="c2">…</b><span>Dry run on Base returns the expected hook address</span></div>
<div class="ligne"><b id="c3">…</b><span>Wallet connected on Base</span></div>
<p class="doux" style="margin:10px 0 0">Expected contract: <code id="attendu"></code></p>
<p class="doux">Operator (can open and anchor — <b>cannot withdraw</b>): <code id="fee"></code>
· challenge delay <code id="vie"></code> h · claim window <code id="marq"></code> days</p>
</div>
<div class="carte">
<button id="connecter" type="button">Connect wallet</button>
<button id="deployer" type="button" disabled>Deploy — sign in your wallet</button>
<p id="etat" class="doux" style="margin:12px 0 0"></p>
<p id="relecture" class="doux" style="margin:8px 0 0"></p>
</div>
</main>
<script type="module">
/* ⛔ PAGE DE DEPLOIEMENT DU POT DE RECOMPENSE (2026-09-20), copie de celle du V5 qui a servi. `cast` sur la machine de Phil n a
* pas le compte importe ; cette page laisse SON wallet signer.
* Le contrat part par le deployeur CREATE2 standard : l adresse ne depend QUE du code (aucun proprietaire), donc
* n importe quel wallet peut l envoyer. La page VERIFIE avant d autoriser la signature :
* 1. aucun code a l adresse attendue ; 2. un eth_call de la transaction rend exactement cette adresse ;
* 3. wallet sur Base.
* ⛔⛔ ET APRES : elle relit le contrat deploye, y compris `porteLeLabel(<un vrai block ne par notre app>)`. C est la
* seule verification qui ne peut PAS se faire avant — forge ne sait pas executer un B20 (code 0xEF, execute
* nativement par le noeud Base : l EVM locale rend OpcodeNotFound). Si elle rend `false`, le verrou du label
* refuserait nos propres createurs, et il ne faut surtout pas brancher l app dessus. */
import { envoyerDepuisWallet } from './envoi.js';
const $ = (s) => document.querySelector(s);
const RPC = 'https://mainnet.base.org';
const rpc = async (method, params) => {
const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }).then((x) => x.json());
if (r.error) throw new Error(r.error.message || 'rpc error');
return r.result;
};
const marque = (id, ok, texte) => { const e = $(id); e.textContent = ok ? '✓' : '✗'; e.className = ok ? 'ok' : 'ko'; if (texte) e.title = texte; };
let tx = null, compte = null, pret = { vide: false, simu: false };
async function verifier() {
tx = await fetch('./deploy-pot.json', { cache: 'no-store' }).then((r) => r.json());
$('#attendu').textContent = tx.contrat;
$('#fee').textContent = tx.operateur;
$('#vie').textContent = String(tx.delaiContestationHeures);
$('#marq').textContent = String(tx.dureeReclamationJours);
const code = await rpc('eth_getCode', [tx.contrat, 'latest']);
pret.vide = code === '0x';
marque('#c1', pret.vide);
if (!pret.vide) {
$('#etat').innerHTML = '<span class="ok">The pot is already deployed at this address. Nothing to do.</span>';
void relire();
return;
}
const r = await rpc('eth_call', [{ to: tx.deployeur, data: tx.data, gas: '0x4c4b40' }, 'latest']);
pret.simu = ('0x' + String(r).slice(-40)).toLowerCase() === tx.contrat.toLowerCase();
marque('#c2', pret.simu);
majBouton();
}
function majBouton() { $('#deployer').disabled = !(pret.vide && pret.simu && compte); }
/** ⛔ Les trois lectures qui comptent, faites SUR LA CHAINE une fois le contrat vivant. */
async function relire() {
const appel = async (data) => rpc('eth_call', [{ to: tx.contrat, data }, 'latest']);
const lignes = [];
try {
/* ⛔ Les selecteurs sont MESURES avec cast sig et ecrits dans deploy-pot.json, jamais devines. */
const op = '0x' + String(await appel(tx.selecteurs.operateur)).slice(-40);
lignes.push((op.toLowerCase() === tx.operateur.toLowerCase() ? '✓' : '✗') + ' operator: ' + op);
const delai = BigInt(await appel(tx.selecteurs.delai));
lignes.push((delai === BigInt(tx.delaiContestationHeures) * 3600n ? '✓' : '✗')
+ ' challenge delay: ' + (Number(delai) / 3600) + ' h');
const duree = BigInt(await appel(tx.selecteurs.duree));
lignes.push((duree === BigInt(tx.dureeReclamationJours) * 86400n ? '✓' : '✗')
+ ' claim window: ' + (Number(duree) / 86400) + ' days');
const nb = BigInt(await appel(tx.selecteurs.periodes));
lignes.push((nb === 0n ? '✓' : '✗') + ' rounds opened so far: ' + nb + ' (a fresh pot starts empty)');
/* ⛔ LE TEMOIN QUI COMPTE : aucune porte de retrait ne doit repondre. */
const portes = ['0x51cff8d9', '0xf3fef3a3', '0x01681a62', '0xf2fde38b'];
let ouverte = 0;
for (const p of portes) { const r2 = await rpc('eth_call', [{ to: tx.contrat, data: p + '0'.repeat(64) }, 'latest']).catch(() => null); if (r2) ouverte++; }
lignes.push((ouverte === 0 ? '✓' : '✗') + ' no withdraw door answers ('
+ ouverte + ' of ' + portes.length + ' responded)' + (ouverte ? ' — DO NOT USE THIS POT' : ''));
} catch (e) {
lignes.push('✗ could not read the deployed pot: ' + (e.message || e));
}
$('#relecture').innerHTML = lignes.map((l) => '<div>' + l.replace(/&/g, '&').replace(/</g, '<') + '</div>').join('');
}
$('#connecter').addEventListener('click', async () => {
if (!window.ethereum) { $('#etat').textContent = 'No wallet found in this browser.'; return; }
try {
const comptes = await window.ethereum.request({ method: 'eth_requestAccounts' });
compte = comptes && comptes[0];
try { await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x2105' }] }); } catch (_) {}
const chaine = await window.ethereum.request({ method: 'eth_chainId' });
marque('#c3', !!compte && Number(chaine) === 8453);
if (Number(chaine) !== 8453) compte = null;
} catch (e) { $('#etat').textContent = 'Connection refused: ' + (e.message || e); }
majBouton();
});
$('#deployer').addEventListener('click', async () => {
$('#deployer').disabled = true;
$('#etat').textContent = 'Opening your wallet — check it shows Base and a contract call, then sign.';
const r = await envoyerDepuisWallet({ eth: window.ethereum, rpc, chaineAttendue: 8453, compte, to: tx.deployeur, data: tx.data, value: '0x0' });
if (r.etat !== 'CONFIRME') { $('#etat').innerHTML = '<span class="ko">Not done: ' + (r.pourquoi || r.etat) + '</span>'; majBouton(); return; }
const code = await rpc('eth_getCode', [tx.contrat, 'latest']);
$('#etat').innerHTML = code !== '0x'
? '<span class="ok">Deployed. The pot is live at ' + tx.contrat + '.</span> Transaction: <code>' + r.hash + '</code>'
: '<span class="ko">Transaction confirmed but no code at the address yet — send me the hash: </span><code>' + r.hash + '</code>';
void relire();
});
verifier().catch((e) => { $('#etat').textContent = 'Could not read Base right now: ' + (e.message || e); });
</script>
</body>
</html>