/** * SmirkAuth - Smirk Wallet Authentication Module * * Handles "Login with Smirk" wallet integration for Wownerogue. * Uses the Smirk browser extension API (window.smirk) for wallet interactions. * * Flow: * 1. Get challenge from server * 2. Connect to Smirk extension to get public keys * 3. Sign challenge with wallet * 4. Verify signature with backend * 5. Auto-set payout address from wallet */ const SmirkAuth = { /** * Check if Smirk extension is installed */ isAvailable() { return typeof window.smirk !== 'undefined'; }, /** * Current link status */ _isLinked: false, _walletAddress: null, /** * Main login flow */ async login() { if (!this.isAvailable()) { throw new Error('Smirk extension not installed. Please install it from your browser extension store.'); } if (!window.socket || !window.socket.id) { throw new Error('Not connected to server. Please wait for connection.'); } const socketId = window.socket.id; // Step 1: Get challenge from server const challengeRes = await fetch('/api/auth/smirk/challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ socketId }) }); if (!challengeRes.ok) { const error = await challengeRes.json(); throw new Error(error.message || 'Failed to get challenge from server'); } const { challenge } = await challengeRes.json(); // Step 2: Connect to Smirk extension let keys; try { keys = await window.smirk.connect(); } catch (smirkError) { throw new Error('Failed to connect to Smirk wallet: ' + (smirkError.message || 'Unknown error')); } if (!keys || !keys.wow) { throw new Error('Smirk wallet did not return a WOW address'); } // Step 3: Sign the challenge let signResult; try { signResult = await window.smirk.signMessage(challenge); } catch (signError) { throw new Error('Failed to sign challenge: ' + (signError.message || 'Unknown error')); } // Find WOW signature const wowSig = signResult.signatures?.find(s => s.asset === 'wow'); if (!wowSig) { throw new Error('WOW signature not found in Smirk response'); } // Step 4: Verify signature with backend const verifyRes = await fetch('/api/auth/smirk/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ socketId, challenge, publicKey: wowSig.publicKey, signature: wowSig.signature }) }); if (!verifyRes.ok) { const error = await verifyRes.json(); throw new Error(error.message || 'Signature verification failed'); } const verifyData = await verifyRes.json(); // Step 5: Auto-set payout address from wallet if (keys.wow && window.socket) { window.socket.emit('address:update', { address: keys.wow }); } // Update internal state this._isLinked = true; this._walletAddress = keys.wow; return { success: true, linked: verifyData.linked, address: keys.wow }; }, /** * Check current link status */ async checkStatus() { if (!window.socket || !window.socket.id) { return { linked: false, hasPayoutAddress: false }; } try { const res = await fetch(`/api/auth/smirk/status?socketId=${encodeURIComponent(window.socket.id)}`); if (res.ok) { const data = await res.json(); this._isLinked = data.linked; return data; } } catch (e) { console.warn('Failed to check Smirk status:', e.message); } return { linked: false, hasPayoutAddress: false }; }, /** * Create and return the Smirk login button element */ _createButton() { const btn = $('