InstantAdPay site skeleton: SIWE auth, live chain ledger, join links, buy flow
Zero-dependency Node server on the RM Circle pattern. Chain config lives in the volume so the same code runs the Amoy dress rehearsal and mainnet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// Wallet plumbing: EIP-1193 connect, chain add/switch, SIWE sign-in, and raw
|
||||
// calldata builders for the contract's tx functions (no library needed).
|
||||
window.IAPWallet = (function () {
|
||||
const SEL_BUY = '0xfd095e97'; // buy(uint32,uint32)
|
||||
const SEL_ACTIVATE = '0x1a93ec95'; // activate(uint32)
|
||||
const pad = v => BigInt(v).toString(16).padStart(64, '0');
|
||||
|
||||
function eth() {
|
||||
if (!window.ethereum) throw new Error('No wallet found. Open this page in a browser with MetaMask (or a wallet browser).');
|
||||
return window.ethereum;
|
||||
}
|
||||
async function ensureChain(c) {
|
||||
const want = '0x' + Number(c.chainId).toString(16);
|
||||
const cur = await eth().request({ method: 'eth_chainId' });
|
||||
if (cur === want) return;
|
||||
try {
|
||||
await eth().request({ method: 'wallet_switchEthereumChain', params: [{ chainId: want }] });
|
||||
} catch (e) {
|
||||
if (e.code !== 4902) throw e;
|
||||
await eth().request({ method: 'wallet_addEthereumChain', params: [{
|
||||
chainId: want, chainName: c.chainName, nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
|
||||
rpcUrls: [c.rpc], blockExplorerUrls: [c.explorer] }] });
|
||||
}
|
||||
}
|
||||
async function connect() {
|
||||
const c = await IAP.getConfig();
|
||||
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
|
||||
await ensureChain(c);
|
||||
return addr;
|
||||
}
|
||||
// SIWE: challenge -> personal_sign -> verify (server sets the session cookie)
|
||||
async function signIn() {
|
||||
const addr = await connect();
|
||||
const ch = await (await fetch('/api/auth/challenge', { method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr }) })).json();
|
||||
if (ch.error) throw new Error(ch.error);
|
||||
const sig = await eth().request({ method: 'personal_sign', params: [ch.message, addr] });
|
||||
const r = await (await fetch('/api/auth/verify', { method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr, signature: sig }) })).json();
|
||||
if (r.error) throw new Error(r.error);
|
||||
return r; // {address, memberId, sponsorId}
|
||||
}
|
||||
async function sendTx(data, valueWei) {
|
||||
const c = await IAP.getConfig();
|
||||
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
|
||||
await ensureChain(c);
|
||||
const tx = { from: addr, to: c.contract, data };
|
||||
if (valueWei) tx.value = '0x' + BigInt(valueWei).toString(16);
|
||||
return eth().request({ method: 'eth_sendTransaction', params: [tx] });
|
||||
}
|
||||
async function waitTx(hash) {
|
||||
const c = await IAP.getConfig();
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const r = await (await fetch(c.rpc, { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getTransactionReceipt', params: [hash] }) })).json();
|
||||
if (r.result) return r.result;
|
||||
await new Promise(res => setTimeout(res, 2500));
|
||||
}
|
||||
throw new Error('Timed out waiting for the transaction — check the explorer.');
|
||||
}
|
||||
// buy: quote is read live server-side; pad 2% for oracle drift, contract
|
||||
// refunds every wei of excess in the same transaction.
|
||||
async function buy(productId, sponsorId, costWei) {
|
||||
const value = BigInt(costWei) + BigInt(costWei) / 50n;
|
||||
const data = SEL_BUY + pad(productId) + pad(sponsorId || 0);
|
||||
const hash = await sendTx(data, value);
|
||||
return { hash, receipt: await waitTx(hash) };
|
||||
}
|
||||
async function activate(sponsorId) {
|
||||
const data = SEL_ACTIVATE + pad(sponsorId || 0);
|
||||
const hash = await sendTx(data, null);
|
||||
return { hash, receipt: await waitTx(hash) };
|
||||
}
|
||||
return { connect, signIn, buy, activate, waitTx };
|
||||
})();
|
||||
Reference in New Issue
Block a user