/** * 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. Get wallet addresses via getAddresses() and auto-set payout address */ 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 WOW public key'); } // 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: Get wallet addresses and auto-set payout address // Note: keys.wow is a public key (hex), NOT an address // We need to call getAddresses() to get the actual WOW address let walletAddress = null; try { const addresses = await window.smirk.getAddresses(); if (addresses && addresses.wow && window.socket) { window.socket.emit('address:update', { address: addresses.wow }); walletAddress = addresses.wow; console.log('Smirk WOW address set:', addresses.wow.substring(0, 20) + '...'); } } catch (addrErr) { console.warn('Could not get Smirk addresses:', addrErr.message); // Fall back - user will need to manually enter address } // Update internal state this._isLinked = true; this._walletAddress = walletAddress; this._publicKey = wowSig.publicKey; return { success: true, linked: verifyData.linked, address: walletAddress }; }, /** * 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 = $('