// 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) { // poll our own server (CSP-friendly); it relays the receipt from the RPC for (let i = 0; i < 60; i++) { const r = await (await fetch('/api/tx/' + hash)).json(); if (r.found) return { status: r.status, blockNumber: r.blockNumber }; 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 }; })();