// Wallet plumbing via Reown AppKit — the universal connector every wallet is // built for (all wallets, QR + mobile deep-links, working icons). AppKit is // lazy-loaded from the CDN on first use; once connected we drive the raw // EIP-1193 provider for chain switch, SIWE sign-in, and contract transactions. 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'); const APPKIT_URL = 'https://cdn.jsdelivr.net/npm/@reown/appkit-cdn@1.8.23/dist/appkit.js'; let modal = null, akPromise = null, provider = null; async function initAppKit(c) { if (modal) return modal; if (akPromise) return akPromise; akPromise = (async () => { const mod = await import(APPKIT_URL); const { createAppKit, WagmiAdapter, networks } = mod; const netMap = { 80002: networks.polygonAmoy, 137: networks.polygon }; const net = netMap[Number(c.chainId)] || networks.polygonAmoy; const projectId = String(c.walletConnectProjectId || '').trim(); const wagmiAdapter = new WagmiAdapter({ networks: [net], projectId }); modal = createAppKit({ adapters: [wagmiAdapter], networks: [net], projectId, defaultNetwork: net, metadata: { name: c.siteName || 'InstantAdPay', description: c.tagline || 'Advertise and earn, paid on-chain.', url: location.origin, icons: [location.origin + '/logo-icon.png'] }, features: { analytics: false, email: false, socials: [] } }); return modal; })(); return akPromise; } function currentAddress() { try { return (modal && modal.getAddress && modal.getAddress()) || null; } catch (e) { return null; } } function waitForConnection(timeoutMs) { if (currentAddress()) return Promise.resolve(currentAddress()); return new Promise((resolve, reject) => { let done = false, unsub = null; const finish = (addr, err) => { if (done) return; done = true; try { unsub && unsub(); } catch (e) {} err ? reject(err) : resolve(addr); }; try { unsub = modal.subscribeAccount(acc => { if (acc && acc.isConnected && acc.address) finish(acc.address); }); } catch (e) {} const t0 = Date.now(); (function poll() { if (done) return; const a = currentAddress(); if (a) return finish(a); if (Date.now() - t0 > (timeoutMs || 180000)) return finish(null, new Error('Wallet connection timed out. Tap Connect and try again.')); setTimeout(poll, 400); })(); }); } async function resolveProvider() { for (let i = 0; i < 20; i++) { try { const p = modal.getWalletProvider ? await Promise.resolve(modal.getWalletProvider()) : null; if (p && p.request) return p; } catch (e) {} await new Promise(r => setTimeout(r, 300)); } throw new Error('Could not reach your wallet. Try connecting again.'); } function eth() { if (!provider) throw new Error('Connect your wallet first.'); return provider; } async function connect() { const c = await IAP.getConfig(); await initAppKit(c); let addr = currentAddress(); if (!addr) { try { await modal.open(); } catch (e) {} addr = await waitForConnection(180000); } provider = await resolveProvider(); await ensureChain(c).catch(() => {}); // AppKit already connects on the right network; switch is best-effort return addr; } async function ensureChain(c) { const want = '0x' + Number(c.chainId).toString(16); let cur; try { cur = await eth().request({ method: 'eth_chainId' }); } catch (e) { return; } if (cur === want) return; try { await eth().request({ method: 'wallet_switchEthereumChain', params: [{ chainId: want }] }); } catch (e) { if (e && e.code !== 4902) return; // don't hard-fail; AppKit/wallet usually handles the network const addParams = { chainId: want, chainName: c.chainName, nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 }, rpcUrls: [c.rpc] }; if (c.explorer && /^https?:\/\//i.test(c.explorer)) addParams.blockExplorerUrls = [c.explorer]; try { await eth().request({ method: 'wallet_addEthereumChain', params: [addParams] }); } catch (e2) {} } } // 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); // hex-encode the message (Trust and others require hex for personal_sign) const hexMsg = '0x' + Array.from(new TextEncoder().encode(ch.message)).map(b => b.toString(16).padStart(2, '0')).join(''); const sig = await eth().request({ method: 'personal_sign', params: [hexMsg, 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; } async function sendTx(data, valueWei) { const c = await IAP.getConfig(); const addr = await connect(); const tx = { from: addr, to: c.contract, data }; if (valueWei) tx.value = '0x' + BigInt(valueWei).toString(16); // let the wallet set gas/fees (its estimate meets the chain's minimums) return eth().request({ method: 'eth_sendTransaction', params: [tx] }); } async function waitTx(hash) { 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.'); } async function buy(productId, sponsorId, costWei) { const value = BigInt(costWei) + BigInt(costWei) / 50n; // 2% oracle-drift pad; contract refunds excess 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) }; } async function disconnect() { try { if (modal && modal.disconnect) await modal.disconnect(); } catch (e) {} provider = null; try { Object.keys(localStorage).forEach(k => { if (/wc@2|walletconnect|w3m|wcm|reown|wagmi|appkit/i.test(k)) localStorage.removeItem(k); }); } catch (e) {} } return { connect, signIn, buy, activate, waitTx, disconnect }; })();