git / moonbite-network / static/qr-utils.js · raw

/**
 * QR Code Utilities for MoonBite Wallet
 * Generates and reads QR codes for addresses and seed phrases
 */

const QRUtils = (() => {
    /**
     * Generate QR code canvas element
     * Uses data URL encoding for addresses/seeds
     */
    function generateQRCode(text, size = 200) {
        const canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;

        // Simplified QR encoding - in production use qrcode.js library
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, size, size);

        // Create pattern based on text
        const hash = hashText(text);
        const pixelSize = size / 32;

        ctx.fillStyle = '#000000';
        for (let i = 0; i < hash.length; i++) {
            if (parseInt(hash[i], 16) % 2 === 0) {
                const row = Math.floor(i / 8);
                const col = i % 8;
                ctx.fillRect(col * pixelSize * 4, row * pixelSize * 4, pixelSize * 3, pixelSize * 3);
            }
        }

        return canvas;
    }

    /**
     * Hash text to generate consistent QR-like pattern
     */
    function hashText(text) {
        let hash = 0;
        for (let i = 0; i < text.length; i++) {
            const char = text.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return Math.abs(hash).toString(16).padStart(32, '0');
    }

    /**
     * Request camera permission and start scanning
     */
    async function startQRScanner(onDetect, onError) {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({
                video: { facingMode: 'environment' }
            });

            return {
                stop: () => {
                    stream.getTracks().forEach(track => track.stop());
                },
                stream: stream
            };
        } catch (error) {
            onError('Camera permission denied or not available');
            return null;
        }
    }

    /**
     * Validate address format (MoonBite bech32)
     */
    function validateAddress(address) {
        // MoonBite uses bech32 with prefix 'moon1'
        if (!address.startsWith('moon1')) {
            return { valid: false, error: 'Address must start with moon1' };
        }

        if (address.length !== 62) {
            return { valid: false, error: 'Invalid address length' };
        }

        // Basic bech32 validation
        const validChars = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
        for (let i = 5; i < address.length; i++) {
            if (!validChars.includes(address[i].toLowerCase())) {
                return { valid: false, error: 'Invalid address characters' };
            }
        }

        return { valid: true };
    }

    /**
     * Create printable paper wallet HTML
     */
    function generatePaperWallet(mnemonic, address) {
        const words = mnemonic.split(' ');
        const html = `
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <title>MoonBite Paper Wallet</title>
                <style>
                    body {
                        font-family: monospace;
                        padding: 20px;
                        max-width: 800px;
                        margin: 0 auto;
                    }
                    .header { text-align: center; margin-bottom: 30px; }
                    .section { margin-bottom: 30px; page-break-inside: avoid; }
                    .warning {
                        color: red;
                        font-weight: bold;
                        border: 2px solid red;
                        padding: 10px;
                        margin: 20px 0;
                    }
                    .words {
                        display: grid;
                        grid-template-columns: 1fr 1fr;
                        gap: 10px;
                        font-size: 16px;
                        line-height: 1.8;
                    }
                    .address {
                        word-break: break-all;
                        font-size: 12px;
                        background: #f0f0f0;
                        padding: 10px;
                        border-radius: 5px;
                    }
                    .qr-placeholder {
                        width: 200px;
                        height: 200px;
                        border: 1px solid #000;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        margin: 20px auto;
                        text-align: center;
                    }
                </style>
            </head>
            <body>
                <div class="header">
                    <h1>MoonBite Paper Wallet</h1>
                    <p>Generated: ${new Date().toLocaleString()}</p>
                </div>

                <div class="warning">
                    SECURITY WARNING<br>
                    KEEP THIS DOCUMENT IN A SAFE PLACE<br>
                    DO NOT SHARE YOUR SEED PHRASE<br>
                    ANYONE WITH YOUR SEED CAN ACCESS YOUR FUNDS
                </div>

                <div class="section">
                    <h2>Your 12-Word Seed Phrase</h2>
                    <p>Write these words down in order. This is the ONLY way to recover your wallet.</p>
                    <div class="words">
                        ${words.map((w, i) => `<div>${i + 1}. ${w}</div>`).join('')}
                    </div>
                </div>

                <div class="section">
                    <h2>Seed Phrase QR Code</h2>
                    <div class="qr-placeholder">[QR Code Here]</div>
                </div>

                <div class="section">
                    <h2>Your Wallet Address</h2>
                    <p>Use this address to receive funds:</p>
                    <div class="address">${address}</div>
                    <div class="qr-placeholder">[Address QR Code]</div>
                </div>

                <div class="section">
                    <h2>Storage Instructions</h2>
                    <ol>
                        <li>Print this document</li>
                        <li>Store in a safe place (safe, safe deposit box, etc.)</li>
                        <li>Keep separate from this device</li>
                        <li>Do NOT take screenshots</li>
                        <li>Do NOT store digitally without encryption</li>
                        <li>Test recovery on a different device before relying on it</li>
                    </ol>
                </div>

                <div class="warning">
                    This document contains sensitive information.<br>
                    Treat it like cash or valuables.
                </div>
            </body>
            </html>
        `;
        return html;
    }

    return {
        generateQRCode,
        startQRScanner,
        validateAddress,
        generatePaperWallet
    };
})();