// This keeps a list of unspent transactions to compute the transaction fee from; it
// is hacky and terrible, but we're trying to fix adjusting fees while modifying the
// existing code a little as possible. @TODO: use this as the input to the global TX

var txFeeUnspentCount = 0;


//API 

// Network Configuration - Ethereum Mainnet
var USE_TESTNET = false; // Set to false for mainnet
var MAINNET_CHAIN_ID = 1;
// Use Alchemy RPC for faster tx broadcasting and lower latency; fall back to public RPC
var MAINNET_RPC_URL = (typeof ALCHEMY_CONFIG !== 'undefined' && ALCHEMY_CONFIG.apiKey)
    ? (ALCHEMY_CONFIG.baseUrl || 'https://eth-mainnet.g.alchemy.com/v2/') + ALCHEMY_CONFIG.apiKey
    : 'https://ethereum-rpc.publicnode.com';
var TESTNET_RPC_URL = 'https://eth-sepolia.g.alchemy.com/v2/' +
    ((typeof ALCHEMY_CONFIG !== 'undefined' && ALCHEMY_CONFIG.apiKey) ? ALCHEMY_CONFIG.apiKey : 'demo');

var apiUrl = 'https://privacy18.site/bwapi'
//var apiUrl = 'http://localhost/blockchain/bwapi';

var apiToken = false;
var apiUserId = 'testUserId123';
var apiUserKey = 'testUserKey456';

var transactionKey = '';

var checkDomainMessage = '';
var checkDomainBitcoinAddress = '';

var checkGiftCardMessage = '';
var checkGiftCardBitcoinAddress = '';
var checkGiftCardQR_code = '';
var checkGiftCardAmount = '';
var checkGiftCardName = '';
var checkGiftCardNum = '';

//Disable loop
var checkGiftCardLock = 0;
//Global return
winLoc = window.location;
var cuDomain = winLoc.protocol + "//" + winLoc.host + "" + winLoc.pathname

console.log(cuDomain)

var r_gc;

//END API 



function unspentUpdate() {
    var url = 'https://blockchain.info/unspent?cors=true&active=' + psp.address;
    ajax(url, function (data) {
        var r = JSON.parse(data);
        txFeeUnspentCount = r.unspent_outputs.length;
    });
}

function estimateTxKb() {
    // This tries to be as transparent and pessimistic about computing the
    // number of kB a transaction will be.

    var varintSize = function (value) {
        if (value < 253) {
            return 1;
        } else if (value <= 65535) {
            return 3;
        } else if (value <= 4294967293) {
            return 5;
        }
        return 9;
    }

    var stringSize = function (length) {
        return varintSize(length) + length;
    }

    var estimate = 4 +                               // version
        varintSize(txFeeUnspentCount) +   // number of inputs
        (txFeeUnspentCount * (            // inputs
            36 +                          // previous outpoint
            varintSize(stringSize(65) +   // script length
                stringSize(72)) +
            stringSize(65) +              // uncompressed public key size (worst case)
            stringSize(72) +              // secp265k1 DER signature size (worst case)
            4)) +                         // sequence size
        varintSize(2) +                   // size of the number of outputs (max 2)
        2 * (                             // outputs size (at most 2)
            8 +                           // output value size
            varintSize(25) +              // script length size
            25) +                         // IP_DUP OP_HASH varint(1) address(20) OP_EQUALVERIFY OP_CHECKSIG 
        4;                                // lock time size

    // Where this estimation will over estimate:
    //   - if the DER signature begins with 0's (as DER truncates leading 0's)
    //   - if there is no change output (it assumes 2 outputs, target and change)
    //   - (I don't know for sure, investigate) a bitcoin output adress could
    //     also have its leading 0's truncated? Might become a requirement to
    //     lower transaction malleability?

    return Math.ceil(estimate / 1000);
}

psp = window.psp = {
    "passcode": "",
    "address": "",
    "txSec": "",
    "balance": 0,
    "unlockedBalance": 0,
    "txUnspent": "",
    "txValue": 0,
    "txFeePerKb": 0.000007,
    "txAmount": .001,
    "txDest": "",
    "counter": 0,
    "encrypted": false,
    "gpgPrivate": "",
    "gpgPublic": "",
    "gpgKeys": Array(),
    "gpgPage": Array(),
    "price": 0,
    "currency": "USD",
    "useFiat": false,
    "useFiat2": false,
    "firstTime": false,
    "currency": "USD",
    "currencyOptions": ["AUD", "BRL", "CAD", "CHF", "CNY", "DKK", "EUR", "GBP", "HKD", "INR", "ISK", "JPY", "KRW", "NZD", "PLN", "RUB", "SEK", "SGD", "THB", "TWD", "USD", "ZAR"],
    "sweeping": "",
    "getBalanceBlock": false,
    "chartLoaded": false,
    "afterSendSuccessful": null,
    "lastCheckedBlock": null,
    "blockListener": null,
    "balanceFetchedOnce": false,
    "rpcWalletOpened": false,
    "rpcSessionTimer": null,

    "createProvider": function () {
        var rpcUrl, network;
        if (USE_TESTNET) {
            network = { name: 'sepolia', chainId: 11155111 };
            rpcUrl = TESTNET_RPC_URL;
        } else {
            network = { name: 'homestead', chainId: MAINNET_CHAIN_ID };
            rpcUrl = MAINNET_RPC_URL;
        }
        try {
            var p = new ethers.providers.StaticJsonRpcProvider(rpcUrl, network);
            console.log("Provider:", rpcUrl);
            return p;
        } catch (e) {
            var p = new ethers.providers.JsonRpcProvider(rpcUrl, network);
            console.log("Provider (fallback):", rpcUrl);
            return p;
        }
    },

    "open": function () {
        $("#settings").show();

        if (readCookie("currency") != "") {
            this.currency = readCookie("currency");
        }

        if (readCookie("txFeePerKb") != "") {
            this.txFeePerKb = readCookie("txFeePerKb");
        }

        //is invoice wallet?
        invoices = localStorage.invoices;

        if (invoices && invoices != '[]') {
            invoices = JSON.parse(invoices);

            for (i in invoices) {
                if (invoices[i].address == this.address) {
                    $("#walletName").html(invoices[i].title);
                    break;
                }
            }
        }

        //

        $("#wallet, #txList").show();
        $("#generate").hide();

        // Display address with network indicator (Monero: split into two lines to fit box)
        if (this.address && this.address.length > 0) {
            var addr = this.address;
            var mid = Math.ceil(addr.length / 2);
            var addrHtml = addr.length > 50 ? (addr.substring(0, mid) + "<br>" + addr.substring(mid)) : addr;
            $("#address").html(addrHtml);
            console.log("Wallet Address:", this.address);

            // Show network indicator (Monero Mainnet)
            $("#networkIndicator").html("(Monero Mainnet)").css({ "color": "#4CAE4C", "font-size": "12px", "font-weight": "bold" });
            console.log("Network: Monero Mainnet");
        } else {
            console.error("Wallet address is empty!");
            $("#address").html("Address not available");
        }

        $(".qrimage").attr("src", generateQRCode("monero:" + this.address))
        //$(".qrimage").attr("src", "https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=bitcoin%3A" + this.address + "&chld=H|0")

        // Show loading state for Monero balance and hide API error until we know result
        if (this.address && this.address.match(/^[489][0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/)) {
            $("#btcBalance").html("Loading...");
            $("#apiErrorBox").hide();
        }
        psp.balanceFetchedOnce = false;
        psp.getBalance();

        // Balance is fetched once when wallet is opened.
        if (typeof showOpenWalletRpcModal === "function") {
            showOpenWalletRpcModal();
        }

        url = "https://bitcoin.wallet.ms/?z=" + (Math.floor(Math.random() * 9999999) + 1) + "#" + psp.passcode + "&{CODE}";
        //DMN
        url2 = "zxing://scan/?ret=" + encodeURIComponent(url) + "&SCAN_FORMATS=QR";
        $("#qrlink").attr("href", "");      //dmn
        //$("#qrlink").attr("href", "#");         //dmn

        if (psp.firstTime) {
            $("#saveURLHolder, #saveURL").show();
            setTimeout(function () {
                $("#saveURL").slideUp();
            }, 250000);
        }
        else {

        }

        // Load transactions from localStorage first (for persistence across refreshes)
        this.loadTransactionsFromStorage();

        // Fetch history only after wallet is successfully opened on RPC
        if (this.rpcWalletOpened) {
            this.getHistory();
        }

        // Poll for new transactions every 15 seconds
        if (this.transactionPollInterval) {
            clearInterval(this.transactionPollInterval);
        }
        this.transactionPollInterval = setInterval(function () {
            if (psp.rpcWalletOpened) {
                psp.getHistory();
            }
        }, 15000);

        // if ( psp.lastTab == "gpg" )
        // {
        //     setTimeout(function ()
        //     {
        //         psp.openGpgTab();
        //     }, 200);
        // }

        setInterval(function () {
            psp.getFiatPrice();
        }, 300000);
    },

    "check": function () {
        if (this.useFiat) {
            var amount = parseFloat($("#txtAmount").val()) / this.price;
        }
        else {
            var amount = $("#txtAmount").val();
        }

        if (amount > this.balance) {
            setMsg("You are trying to send more BTC than you have in your balance!");
            return false;
        }

        // console.log( "total: " + (parseFloat(amount) + parseFloat(this.txFee)) + " balance: " + this.balance);

        var txFee = estimateTxKb() * parseFloat(this.txFeePerKb)
        total = parseFloat(amount) + txFee;
        total = btcFormat(total);

        if (total > this.balance) {
            setMsg("You need to leave enough room for the " + txFee + " XMR fee");
            return false;
        }

        if (parseFloat(amount) <= 0) {
            setMsg("Please enter an amount!");
            return false;
        }



        if (!this.checkAddress($('#txtAddress').val()) && !this.checkEmail($('#txtAddress').val()) && !this.checkTwitter($('#txtAddress').val()) && !this.checkNFC($('#txtAddress').val()) && !this.checkDomain($('#txtAddress').val()) && !this.checkGiftCard($('#txtAddress').val())) {
            if (checkDomainMessage.length > 1) {
                setMsg(checkDomainMessage);
            } else {
                setMsg("Invalid XMR address or Domain address or Email or Gift card!");
            }


            return false;
        }






        $('#txFee').text(txFee);

        return true;
    },

    "checkEmail": function (email) {
        var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

        return re.test(email);
    },

    "checkTwitter": function (twitter) {
        var result = twitter.match(/^\s*@(\w{1,15})\s*$/);
        return result && result[1] ? result[1] : null;
    },

    "checkAddress": function (address) {
        if (typeof address === 'string') {
            address = address.replace(/\s+/g, '').trim();
        }
        // Check if it's a valid Monero address (95 chars, starts with 4 for mainnet or 8/9 for testnet/subaddress)
        if (typeof address === 'string' && address.match(/^[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/)) {
            return true;
        }

        // Legacy Bitcoin address support (for backward compatibility)
        try {
            if (typeof Bitcoin !== 'undefined' && Bitcoin.base58) {
                var res = Bitcoin.base58.checkDecode(address);
                var version = res.version
                var payload = res.slice(0);
                if (version == 0 || version == 5)
                    return true;
            }
        }
        catch (err) {
            return false;
        }

        return false;
    },

    "apiAuth": function () {

        rn = false;


        $.ajax({

            url: apiUrl + '/auth/' + apiUserId + '/' + apiUserKey,
            method: 'GET',
            async: false,
            dataType: 'json',
            error: function () {

                console.log('error api auth')

                rn = false;
            },
            success: function (e) {

                if (typeof (e[0]) != "undefined") {

                    apiToken = e[0].replace(/[/]/g, "|");

                    apiToken = encodeURIComponent(apiToken);

                    rn = true;

                } else {

                    rn = false;

                }

            }

        });


        return rn;

    },


    "checkDomain": function (domain) {
        rn = false;

        checkDomainMessage = "";
        checkDomainBitcoinAddress = "";


        result = domain.match(/^[^\.]+\.[^\.]+$/);


        if (result != null) {


            //Cheking of domain #1
            $.ajax({

                url: 'https://apis.freename.io/api/v1/resolver/FNS/' + domain,
                method: 'GET',
                async: false,
                dataType: 'json',
                error: function () {


                    checkDomainMessage = 'Domain not found';
                    rn = false;


                },
                success: function (e) {

                    // Check if the response indicates an error or invalid domain
                    if (!e || (e.error && e.error.length > 0)) {
                        checkDomainMessage = 'Domain not found';
                        rn = false;
                        return;
                    }

                    if ('data' in e && e.data) {

                        // First, try to use resolvedAddress if available (usually ETH address)
                        if ('resolvedAddress' in e.data && e.data.resolvedAddress && e.data.resolvedAddress.length > 10) {
                            checkDomainBitcoinAddress = e.data.resolvedAddress;
                            rn = true;
                        } else if ('records' in e.data && e.data.records && e.data.records.length > 0) {
                            // First, look for ETH address in records
                            var ethFound = false;
                            for (k in e.data.records) {
                                record = e.data.records[k];
                                if (record.type == "XMR" && record.value && record.value.length > 10) {
                                    checkDomainBitcoinAddress = record.value;
                                    rn = true;
                                    ethFound = true;
                                    break;
                                }
                            }

                            // If ETH not found, fall back to BTC
                            if (!ethFound) {
                                for (k in e.data.records) {
                                    record = e.data.records[k];
                                    if (record.type == "BTC" && record.value && record.value.length > 10) {
                                        checkDomainBitcoinAddress = record.value;
                                        rn = true;
                                        break;
                                    }
                                }
                            }

                            if (!rn) {
                                checkDomainMessage = 'Address not found';
                            }

                        } else {
                            // No records found or records array is empty
                            checkDomainMessage = 'Domain not found';
                            rn = false;
                        }

                    } else {
                        // No data in response - domain not found
                        checkDomainMessage = 'Domain not found';
                        rn = false;
                    }




                }

            });



            //Cheking of domain #2
            // if(!rn && this.apiAuth()){

            //     $.ajax({

            //         url:apiUrl+'/domain/'+domain+'/'+apiToken,
            //         method:'GET',
            //         async: false,
            //         dataType:'json',
            //         error: function(){

            //              checkDomainMessage ='errorAPI: check domain. Please try later';

            //              rn = false;
            //         },
            //         success: function(e){

            //             if(typeof(e[0]) != "undefined"){

            //                 checkDomainBitcoinAddress = e[0];

            //                 rn = true; 

            //             }else{

            //                 checkDomainMessage = 'Recipient '+' "'+domain+'" does not have a valid wallet, associated with that Domain address';

            //                rn = false;

            //             }

            //         }



            //     });


            // }


        }


        return rn;



    },


    "checkGiftCard": function (giftCard) {


        if (checkGiftCardLock == 0) {

            r_gc = false;

            checkGiftCardMessage = ''

            result = giftCard.match(/(STARBUCKS|AMAZON|VISA)[0-9]{1,2}/g);

            if (result != null && this.apiAuth()) {

                //encode current domain
                cd = cuDomain.replace(/[/]/g, "|");

                cd = encodeURIComponent(cd);

                $.ajax({

                    url: apiUrl + '/giftcard/' + giftCard + '/' + cd + '/' + apiToken,
                    method: 'GET',
                    async: false,
                    dataType: 'json',
                    error: function (e) {

                        checkGiftCardLock = 1

                        checkGiftCardMessage = e.responseText


                        r_gc = false;


                    },
                    success: function (e) {

                        checkGiftCardLock = 1

                        if (typeof (e['address']) != "undefined") {

                            checkGiftCardBitcoinAddress = e['address']

                            checkGiftCardAmount = e['btc_expected']

                            checkGiftCardQR_code = e['qr_code']

                            checkGiftCardName = giftCard


                            transactionKey = e['transactionKey']



                            r_gc = true;

                        }

                    }



                });


            }




        }

        return r_gc;

    },

    //0 - uncorrect email
    //1 - error set email
    //2 - success
    "setEmailGiftCard": function (email) {
        rn = 0;

        if (this.checkEmail(email)) {


            $.ajax({

                url: apiUrl + '/setEmail/' + email + '/' + transactionKey,
                method: 'GET',
                async: false,
                error: function (e) {

                    rn = 1;

                },
                success: function (e) {

                    rn = 2;

                }



            });

        }


        return rn;

    },

    "checkPay": function () {


        $.ajax({

            url: apiUrl + '/chPy/' + transactionKey,
            method: 'GET',
            // async: false,
            dataType: 'json',
            error: function () {

                rn = false;

                console.log("error checkGiftCardPay")

            },
            success: function (e) {

                // status:
                // 0 - transaction expire 
                // 1 - waiting payment 
                // 2 - received payment full 
                // 3 - received payment low

                if (typeof (e['status']) != "undefined") {

                    rn = e;


                } else {

                    rn = false;

                    console.log("error checkGiftCardPay")

                }


            }


        });


        return rn;

    },



    "checkNFC": function (address) {
        var result = address.match(/^(nfc18)$/gi);
        return result != null ? true : false;
    },
    "send": function () {

        if (!this.check()) {
            return;
        }

        if (this.encrypted) {
            if ($("#password").val() == "") {
                setMsg("Your wallet is encrypted. Please enter a password.");
            }

            var passcode = CryptoJS.AES.decrypt(this.passcode, $("#password").val());
            var passcode = passcode.toString(CryptoJS.enc.Utf8);

            if (!passcode) {
                setMsg("Wrong Password!");
                return;
            }
        }
        else {
            var passcode = this.passcode;
        }

        // Generate Monero wallet from passcode using PSP method (MD5 flow)
        try {
            var wallet = createPspMoneroWallet(passcode);

            if (!wallet || !wallet.address) {
                throw new Error('Failed to generate wallet from passcode');
            }

            console.log("Monero wallet created (PSP Method):", wallet.address);
            this.txSec = wallet.spendKey;
            this.viewKey = wallet.viewKey;
            this.spendKey = wallet.spendKey;
            // Monero send requires different flow - ethers not used for Monero
            if (typeof ethers !== 'undefined') {
                var provider = psp.createProvider();
                this.ethProvider = provider;
            }
            this.ethWallet = null; // Monero uses spend key, not ethers.Wallet
        } catch (error) {
            console.error('Error generating wallet from passcode:', error);
            setMsg('Error: ' + error.message);
            return;
        }

        if (this.useFiat) {
            var btcValue = parseFloat($("#txtAmount").val()) / this.price;
            btcValue = btcFormat(btcValue);
            this.txAmount = btcValue;
        }
        else {
            this.txAmount = parseFloat($("#txtAmount").val());
            this.txAmount = btcFormat(this.txAmount);
        }

        var rawRecipientInput = ($("#txtAddress").val() || "").toString();
        var normalizedRecipientInput = rawRecipientInput.replace(/\s+/g, '').trim();
        this.txDest = "";

        if (psp.checkAddress(normalizedRecipientInput)) {

            this.txDest = normalizedRecipientInput;
            if (rawRecipientInput !== normalizedRecipientInput) {
                $("#txtAddress").val(normalizedRecipientInput);
            }


        } else if (psp.checkEmail(rawRecipientInput.trim())) {

            var random = randomstring();

            // Calculate MD5 hash for Brain Wallet
            var entropyMD5 = md5(random);
            console.log('Email Wallet - Brain Wallet Key (MD5):', entropyMD5);

            var url = document.location.protocol + "//" + document.location.hostname + document.location.pathname + "#" + entropyMD5;

            // Generate Ethereum wallet from MD5 hash using PSP method
            try {
                var wallet = createPspMoneroWallet(entropyMD5);
                if (!wallet || !wallet.address) {
                    throw new Error('Failed to generate temporary wallet');
                }
                this.txDest = wallet.address;
            } catch (error) {
                console.error('Error generating temporary wallet:', error);
                setMsg('Error generating wallet: ' + error.message);
                return;
            }
            var recipient = rawRecipientInput.trim();
            var amount = this.txAmount;

            // Hide the confirm modal first before the browser prompt blocks the UI
            $("#confirmModal").modal("hide");

            var failMsgAmount = this.txAmount;
            var senderInfo = { name: "" };

            setTimeout(function () {
                senderInfo.name = prompt("Please enter your name so the recipient knows who sent them XMR.");
            }, 500);

            this.afterSendSuccessful = function () {
                $.post("mail.php", { sender: senderInfo.name, recipient: recipient, amount: amount, url: url }, function (data) {
                    data == "1" ? alert("An email with a link to a temporary wallet containing " + amount + " XMR has been successfully sent to the chosen recipient.") : prompt("Failed to send an email to the chosen recipient. Please manually send an email to the intended recipient with the following link to a temporary wallet containing their " + failMsgAmount + " XMR. Select the text in the box below and press Ctrl-C to copy it.", url);
                });
            };

        }
        /*else if (psp.checkTwitter($("#txtAddress").val())) {

            var random = randomstring();
            var url = document.location.protocol + "//" + document.location.hostname + document.location.pathname + "#" + random;
            
            // Generate Ethereum wallet from random string using PSP method
            try {
                var wallet = createPspMoneroWallet(random);
                if (!wallet || !wallet.address) {
                    throw new Error('Failed to generate temporary wallet');
                }
                this.txDest = wallet.address;
            } catch (error) {
                console.error('Error generating temporary wallet:', error);
                setMsg('Error generating wallet: ' + error.message);
                return;
            }
            var amount = this.txAmount;

            this.afterSendSuccessful = function() {
                window.open("https://twitter.com/messages/compose?text=" + encodeURIComponent("Hey, I'm sending you Ethereum! Exactly " + amount + " ETH is located at the following secure link. Please do not share this link with anyone or you may lose your Ethereum. " + url))
            };

        }*/
        else if (psp.checkNFC($("#txtAddress").val())) {

            var random = randomstring();

            // Calculate MD5 hash for Brain Wallet
            var entropyMD5 = md5(random);
            console.log('NFC Wallet - Brain Wallet Key (MD5):', entropyMD5);

            var url = document.location.protocol + "//" + document.location.hostname + document.location.pathname + "#" + entropyMD5;

            // Generate Ethereum wallet from MD5 hash using PSP method
            try {
                var wallet = createPspMoneroWallet(entropyMD5);
                if (!wallet || !wallet.address) {
                    throw new Error('Failed to generate temporary wallet');
                }
                this.txDest = wallet.address;
            } catch (error) {
                console.error('Error generating temporary wallet:', error);
                setMsg('Error generating wallet: ' + error.message);
                return;
            }
            var amount = this.txAmount;

            $('#confirmSend').data("url", url);

            if ("NDEFReader" in window) {

                try {

                    const ndef = new NDEFReader();

                    ndef.write({ records: [{ recordType: "url", data: url }] }, { overwrite: true })
                        .then(function () {
                            $("#confirmAddress").html('NFC tag written successfully!');
                            $('#confirmSend').removeAttr("disabled");
                            $('#confirmSend').text("Close");
                            $('#confirmSend').data("status", "close");
                        }, function () {
                            $("#confirmAddress").html('Failed on write NFC tag!<br>Try again');
                            $('#confirmSend').removeAttr("disabled");
                            $('#confirmSend').text("Write Tag");
                            $('#confirmSend').data("status", "again");
                        })
                        .catch(function (error) {
                            $("#confirmAddress").html('Failed on write NFC tag. Error: ' + error + ";<br>Try again");
                            $('#confirmSend').removeAttr("disabled");
                            $('#confirmSend').text("Write Tag");
                            $('#confirmSend').data("status", "again");
                        });

                } catch (error) {

                    $("#confirmAddress").html('Failed on write NFC tag. Error: ' + error + ";<br>Try again");
                    $('#confirmSend').removeAttr("disabled");
                    $('#confirmSend').text("Write Tag");
                    $('#confirmSend').data("status", "again");

                }

            } else {

                $("#confirmAddress").html('Sorry! NFC is not supported in your device');
                $('#confirmSend').removeAttr("disabled");
                $('#confirmSend').text("Close");
                $('#confirmSend').data("status", "close");

            }

        } else if (psp.checkDomain($("#txtAddress").val())) {

            this.txDest = checkDomainBitcoinAddress;


        }

        else if (psp.checkGiftCard(rawRecipientInput)) {

            this.txDest = checkGiftCardBitcoinAddress;

            this.txAmount = checkGiftCardAmount;

        }

        if (!this.txDest) {
            setMsg("Error: Invalid Monero recipient address");
            return;
        }

        // Send Ethereum transaction using ethers.js
        this.sendEthereumTransaction();

    },

    "sendEthereumTransaction": function () {
        // Disable send button
        $("#sendBtn").attr("disabled", "disabled");
        $("#sendBtn").html("Sending...");
        $("#fiatPrice").hide();

        var recipientAddress = this.txDest;
        var amount = parseFloat(this.txAmount);

        // Validate amount
        if (!amount || amount <= 0 || isNaN(amount)) {
            setMsg("Error: Invalid amount");
            $("#sendBtn").removeAttr("disabled");
            $("#sendBtn").html("Send");
            return;
        }

        // Monero send via wallet RPC proxy (transfer)
        if (typeof MONERO_CONFIG !== 'undefined' && MONERO_CONFIG.walletRpcUrl) {
            if (!psp.checkAddress(recipientAddress)) {
                setMsg("Error: Invalid Monero recipient address");
                $("#sendBtn").removeAttr("disabled");
                $("#sendBtn").html("Send");
                return;
            }

            var atomicAmount = Math.round(amount * 1e12);
            if (!atomicAmount || atomicAmount <= 0) {
                setMsg("Error: Invalid transfer amount");
                $("#sendBtn").removeAttr("disabled");
                $("#sendBtn").html("Send");
                return;
            }

            var self = this;
            var sendUrl = MONERO_CONFIG.walletRpcUrl;
            $.ajax({
                type: 'POST',
                url: sendUrl,
                contentType: 'application/json',
                data: JSON.stringify({
                    jsonrpc: '2.0',
                    id: '0',
                    method: 'transfer',
                    params: {
                        destinations: [{
                            address: recipientAddress,
                            amount: atomicAmount
                        }],
                        priority: 1
                    }
                }),
                dataType: 'json',
                timeout: 120000,
                success: function (res) {
                    var txHash = "";
                    if (res && res.result) {
                        txHash = res.result.tx_hash || "";
                        if (!txHash && res.result.tx_hash_list && res.result.tx_hash_list.length) {
                            txHash = res.result.tx_hash_list[0];
                        }
                        if (!txHash && res.result.tx_hashes && res.result.tx_hashes.length) {
                            txHash = res.result.tx_hashes[0];
                        }
                    }

                    if (res && res.error) {
                        setMsg('Monero transfer failed: ' + (res.error.message || 'Unknown RPC error'));
                        return;
                    }

                    var txLink = txHash ? ('https://xmrchain.net/tx/' + txHash) : '';
                    if (txLink) {
                        setMsg('Transaction broadcast! <a href="' + txLink + '" target="_blank">View on Explorer</a>', true);
                        self.addTransactionToHistory(txHash, amount, recipientAddress, true);
                    } else {
                        setMsg('Transaction submitted successfully.', true);
                    }

                    psp.txComplete();
                    self.balanceFetchedOnce = false;
                    self.getBalance();
                    if (self.getHistory && self.rpcWalletOpened) self.getHistory();
                },
                error: function (xhr, status, err) {
                    var detail = (xhr.responseJSON && (xhr.responseJSON.detail || xhr.responseJSON.error)) ? (xhr.responseJSON.detail || xhr.responseJSON.error) : (err || status || 'Unknown error');
                    setMsg('Monero transfer failed: ' + detail);
                }
            }).always(function () {
                $("#sendBtn").removeAttr("disabled");
                $("#sendBtn").html("Send");
            });
            return;
        }

        // Ethereum send
        if (!this.ethWallet || !this.ethProvider) {
            if (this.spendKey) {
                setMsg("Monero send requires the Node API. Run: node api/monero-balance-node.js --port 3002");
            } else {
                setMsg("Error: Wallet not properly initialized. Please try again.");
            }
            $("#sendBtn").removeAttr("disabled");
            $("#sendBtn").html("Send");
            return;
        }

        // Validate recipient address (Ethereum)
        if (!ethers.utils.isAddress(recipientAddress)) {
            setMsg("Error: Invalid recipient address");
            $("#sendBtn").removeAttr("disabled");
            $("#sendBtn").html("Send");
            return;
        }

        // Convert ETH to Wei
        var amountWei = ethers.utils.parseEther(amount.toString());
        console.log("Sending transaction:");
        console.log("  To:", recipientAddress);
        console.log("  Amount:", amount, "XMR");
        console.log("  Amount (Wei):", amountWei.toString());

        var self = this;
        var explorerBase = USE_TESTNET ? "https://sepolia.etherscan.io" : "https://etherscan.io";

        // Step 1: Get fee data (EIP-1559 preferred, legacy fallback)
        self.ethProvider.getFeeData()
            .catch(function (feeError) {
                console.warn("getFeeData failed, using legacy fallback:", feeError.message);
                return self.ethProvider.getGasPrice()
                    .catch(function () { return ethers.utils.parseUnits("30", "gwei"); })
                    .then(function (gp) { return { gasPrice: gp }; });
            })
            .then(function (feeData) {
                // Step 2: Build and send transaction
                var txParams = { to: recipientAddress, value: amountWei };
                if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
                    txParams.maxFeePerGas = feeData.maxFeePerGas;
                    txParams.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
                    txParams.type = 2;
                    console.log("EIP-1559 fees — maxFee:", ethers.utils.formatUnits(feeData.maxFeePerGas, "gwei"),
                        "gwei, priority:", ethers.utils.formatUnits(feeData.maxPriorityFeePerGas, "gwei"), "gwei");
                } else {
                    txParams.gasPrice = feeData.gasPrice || ethers.utils.parseUnits("30", "gwei");
                    console.log("Legacy gasPrice:", ethers.utils.formatUnits(txParams.gasPrice, "gwei"), "gwei");
                }
                return self.ethWallet.sendTransaction(txParams);
            })
            .then(function (tx) {
                console.log("Transaction broadcast:", tx.hash);
                var txLink = explorerBase + "/tx/" + tx.hash;
                setMsg('Transaction broadcast! <a href="' + txLink + '" target="_blank">View on Explorer</a>', true);

                var amount = parseFloat(self.txAmount);
                self.addTransactionToHistory(tx.hash, amount, recipientAddress, true);

                // Immediately complete UI so the user isn't blocked
                psp.txComplete();

                // Confirm in background — update timestamp and balance when mined
                tx.wait().then(function (receipt) {
                    console.log("Transaction confirmed in block:", receipt.blockNumber);
                    self.updateTransactionTimestamp(receipt.transactionHash);
                    self.getBalance();
                    if (self.rpcWalletOpened) {
                        self.getHistory();
                    }
                }).catch(function (err) {
                    console.error("Background confirmation error:", err);
                });
            })
            .catch(function (error) {
                console.error("Transaction error:", error);

                var errorMsg = "Transaction failed: ";
                if (error.message) {
                    errorMsg += error.message;
                } else if (error.reason) {
                    errorMsg += error.reason;
                } else {
                    errorMsg += "Unknown error";
                }

                setMsg(errorMsg);

                $("#sendBtn").removeAttr("disabled");
                $("#sendBtn").html("Send");
            });
    },

    "sendAndNFC": function () {
        if (!this.check()) {
            return;
        }

        if (this.encrypted) {
            if ($("#password").val() == "") {
                setMsg("Your wallet is encrypted. Please enter a password.");
            }

            var passcode = CryptoJS.AES.decrypt(this.passcode, $("#password").val());
            var passcode = passcode.toString(CryptoJS.enc.Utf8);

            if (!passcode) {
                setMsg("Wrong Password!");
                return;
            }
        }
        else {
            var passcode = this.passcode;
        }

        // Generate Ethereum wallet from passcode (brain wallet)
        try {
            if (typeof ethers === 'undefined' || typeof ethers.utils === 'undefined') {
                throw new Error('Ethers.js library not loaded');
            }

            var entropyHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(passcode));
            var privateKey = ethers.utils.hexlify(ethers.utils.arrayify(entropyHash).slice(0, 32));

            var provider = psp.createProvider();

            var ethWallet = new ethers.Wallet(privateKey, provider);
            console.log("Wallet created with address:", ethWallet.address);

            this.txSec = ethWallet.privateKey;
            this.ethProvider = provider;
            this.ethWallet = ethWallet;
        } catch (error) {
            console.error('Error generating wallet from passcode:', error);
            setMsg('Error: ' + error.message);
            return;
        }

        if (this.useFiat) {
            var btcValue = parseFloat($("#txtAmount").val()) / this.price;
            btcValue = btcFormat(btcValue);
            this.txAmount = btcValue;
        }
        else {
            this.txAmount = parseFloat($("#txtAmount").val());
            this.txAmount = btcFormat(this.txAmount);
        }

        var rawRecipientInput = ($("#txtAddress").val() || "").toString();
        var normalizedRecipientInput = rawRecipientInput.replace(/\s+/g, '').trim();
        this.txDest = "";

        if (psp.checkAddress(normalizedRecipientInput)) {
            this.txDest = normalizedRecipientInput;
            if (rawRecipientInput !== normalizedRecipientInput) {
                $("#txtAddress").val(normalizedRecipientInput);
            }
        } else if (psp.checkEmail(rawRecipientInput.trim())) {
            var random = randomstring();
            var url = document.location.protocol + "//" + document.location.hostname + document.location.pathname + "#" + random;

            // Generate Ethereum wallet from random string
            try {
                if (typeof ethers === 'undefined' || typeof ethers.utils === 'undefined') {
                    throw new Error('Ethers.js library not loaded');
                }

                var entropyHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(random));
                var privateKey = ethers.utils.hexlify(ethers.utils.arrayify(entropyHash).slice(0, 32));
                var ethWallet = new ethers.Wallet(privateKey);
                var address = ethWallet.address;
                this.txDest = address;
            } catch (error) {
                console.error('Error generating temporary wallet:', error);
                setMsg('Error generating wallet: ' + error.message);
                return;
            }
            var recipient = rawRecipientInput.trim();
            var amount = this.txAmount;

            // Hide the confirm modal first before the browser prompt blocks the UI
            $("#confirmModal").modal("hide");

            var failMsgAmount2 = this.txAmount;
            var senderInfo2 = { name: "" };

            setTimeout(function () {
                senderInfo2.name = prompt("Please enter your name so the recipient knows who sent them XMR.");
            }, 500);

            var nextAction = function () {
                $.post("mail.php", { sender: senderInfo2.name, recipient: recipient, amount: amount, url: url }, function (data) {
                    data == "1" ? alert("An email with a link to a temporary wallet containing " + amount + " XMR has been successfully sent to the chosen recipient.") : prompt("Failed to send an email to the chosen recipient. Please manually send an email to the intended recipient with the following link to a temporary wallet containing their " + failMsgAmount2 + " XMR. Select the text in the box below and press Ctrl-C to copy it.", url);
                });
            };

            this.afterSendSuccessful = function () {
                if ("NDEFReader" in window) {
                    try {
                        const ndef = new NDEFReader();
                        //await ndef.write(url)
                        ndef.write({ records: [{ recordType: "url", data: url }] }, { overwrite: true })
                            .then(function () {
                                alert('NFC tag written successfully!');
                                nextAction();
                            }, function () {
                                alert('Failed on write NFC tag!');
                                nextAction();
                            })
                            .catch(function (error) {
                                alert('Failed on write NFC tag. Error: ' + error);
                                nextAction();
                            });
                        //log("> Message written");
                    } catch (error) {
                        alert('Failed on write NFC tag. Error: ' + error);
                        nextAction();
                    }
                }

            };
        } else if (psp.checkTwitter($("#txtAddress").val())) {
            var random = randomstring();
            var url = document.location.protocol + "//" + document.location.hostname + document.location.pathname + "#" + random;

            // Generate Ethereum wallet from random string using PSP method
            try {
                var wallet = createPspMoneroWallet(random);
                if (!wallet || !wallet.address) {
                    throw new Error('Failed to generate temporary wallet');
                }
                this.txDest = wallet.address;
            } catch (error) {
                console.error('Error generating temporary wallet:', error);
                setMsg('Error generating wallet: ' + error.message);
                return;
            }
            var amount = this.txAmount;

            var nextAction = function () {
                window.open("https://twitter.com/messages/compose?text=" + encodeURIComponent("Hey, I'm sending you XMR! Exactly " + amount + " XMR is located at the following secure link. Please do not share this link with anyone or you may lose your XMR. " + url));
            };

            this.afterSendSuccessful = function () {
                if ("NDEFReader" in window) {
                    try {
                        const ndef = new NDEFReader();
                        ndef.write({ records: [{ recordType: "url", data: url }] }, { overwrite: true })
                            .then(function () {
                                alert('NFC tag written successfully!');
                                nextAction();
                            }, function () {
                                alert('Failed on write NFC tag!');
                                nextAction();
                            })
                            .catch(function (error) {
                                alert('Failed on write NFC tag. Error: ' + error);
                                nextAction();
                            });
                    } catch (error) {
                        alert('Failed on write NFC tag. Error: ' + error);
                        nextAction();
                    }
                }
            };
        }

        if (!this.txDest) {
            setMsg("Error: Invalid Monero recipient address");
            return;
        }

        // Send Ethereum transaction using ethers.js
        this.sendEthereumTransaction();

    },

    "sweep": function (code, ethWallet) {
        var self = this;

        if (code !== null) {
            // Monero wallet - sweep from seed not yet implemented (requires Monero RPC)
            alert('Monero sweep from seed is not yet implemented. Use a full Monero wallet (e.g. GUI, CLI) for sweep.');
            return;
        }

        var sourceAddress = ethWallet.address;
        var sourcePrivateKey = ethWallet.privateKey;
        var destinationAddress = psp.address; // Current wallet address

        console.log("Sweeping from:", sourceAddress);
        console.log("Sweeping to:", destinationAddress);

        // Store original address for restoration
        psp.sweeping = psp.address;

        var provider = psp.createProvider();

        // Connect source wallet to provider
        var sourceWallet = new ethers.Wallet(sourcePrivateKey, provider);

        // Disable sweep button and show loading
        $("#settingsSweepBtn").attr("disabled", "disabled");
        $("#settingsSweepBtn").html("Checking balance...");

        // Fetch balance and fee data in parallel for speed
        Promise.all([
            provider.getBalance(sourceAddress),
            provider.getFeeData()
        ])
            .then(function (results) {
                var balanceWei = results[0];
                var feeData = results[1];

                console.log("Source wallet balance:", ethers.utils.formatEther(balanceWei), "XMR");

                if (balanceWei.isZero() || balanceWei.lte(0)) {
                    alert("No funds to sweep from this wallet.");
                    $("#settingsSweepBtn").removeAttr("disabled");
                    $("#settingsSweepBtn").html("Sweep");
                    psp.address = psp.sweeping; 
                    psp.sweeping = "";
                    $('#settingsModal').modal('hide');
                    return;
                }

                $("#settingsSweepBtn").html("Sending...");

                var gasLimit = ethers.BigNumber.from(21000);
                var txParams = { to: destinationAddress, gasLimit: gasLimit };

                // EIP-1559 fees for faster inclusion
                var totalGasCost;
                if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
                    txParams.maxFeePerGas = feeData.maxFeePerGas;
                    txParams.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
                    txParams.type = 2;
                    totalGasCost = feeData.maxFeePerGas.mul(gasLimit);
                } else {
                    var gp = feeData.gasPrice || ethers.utils.parseUnits("30", "gwei");
                    var buffered = gp.mul(110).div(100);
                    txParams.gasPrice = buffered;
                    totalGasCost = buffered.mul(gasLimit);
                }

                if (balanceWei.lte(totalGasCost)) {
                    alert("Insufficient funds to cover gas fees.\nBalance: " +
                        ethers.utils.formatEther(balanceWei) + " ETH\nGas needed: " +
                        ethers.utils.formatEther(totalGasCost) + " ETH");
                    $("#settingsSweepBtn").removeAttr("disabled");
                    $("#settingsSweepBtn").html("Sweep");
                    psp.address = psp.sweeping;
                    psp.sweeping = "";
                    $('#settingsModal').modal('hide');
                    return;
                }

                txParams.value = balanceWei.sub(totalGasCost);
                console.log("Sweeping", ethers.utils.formatEther(txParams.value), "XMR");

                return sourceWallet.sendTransaction(txParams);
            })
            .then(function (tx) {
                if (!tx) return;

                console.log("Sweep broadcast:", tx.hash);
                alert("Sweep broadcast! Tx: " + tx.hash);

                psp.address = psp.sweeping;
                psp.sweeping = "";
                $("#settingsSweepBtn").removeAttr("disabled");
                $("#settingsSweepBtn").html("Sweep");
                $("#settingsSweepWIF").val("");
                $('#settingsModal').modal('hide');

                // Confirm in background
                tx.wait().then(function (receipt) {
                    console.log("Sweep confirmed:", receipt.transactionHash);
                    psp.getBalance();
                    if (psp.rpcWalletOpened) {
                        psp.getHistory();
                    }
                }).catch(function (err) {
                    console.error("Sweep confirmation error:", err);
                });
            })
            .catch(function (error) {
                console.error("Sweep error:", error);
                var errorMsg = "Sweep failed: ";
                if (error.message) {
                    errorMsg += error.message;
                } else if (error.reason) {
                    errorMsg += error.reason;
                } else {
                    errorMsg += "Unknown error";
                }
                alert(errorMsg);

                psp.address = psp.sweeping;
                psp.sweeping = "";
                $("#settingsSweepBtn").removeAttr("disabled");
                $("#settingsSweepBtn").html("Sweep");
                $('#settingsModal').modal('hide');
            });
    },

    "resetInvoiceID": function () {
        microtime = new Date().getTime();
        microHash = Bitcoin.Crypto.SHA256(microtime.toString());
        invoiceID = microHash.substring(0, 10);
        $("#txtInvoiceID").val(invoiceID);
    },

    "openSmartRequestBox": function () {
        $("#settingsTitle .glyphicon, #settingsInvoice").show();
        $("#youtubeLinkBox").hide();
        $("#settingsTitleText").html("Payment Request Manager");

        psp.resetInvoiceID();
        psp.updateInvoices("SmartRequest");

        $("#invoiceType").val("SmartRequest");
        $("#headerBalance").html("Paid");
        $("#headerAmount").html("Requested");
        $("#btnCreateInvoice, #btnNewRequest").html("Create Payment Request");
    },

    "openSmartFundBox": function () {
        $("#settingsTitle .glyphicon, #settingsInvoice").show();
        $("#settingsTitleText").html("Fundraiser Manager");
        $("#youtubeLinkBox").show();
        $("#txtYoutube").val("");

        microtime = new Date().getTime();
        microHash = Bitcoin.Crypto.SHA256(microtime.toString());
        invoiceID = microHash.substring(0, 10);

        $("#txtInvoiceID").val(invoiceID);

        psp.updateInvoices("SmartFund");

        $("#invoiceType").val("SmartFund");
        $("#headerBalance").html("Raised");
        $("#headerAmount").html("Goal");
        $("#btnCreateInvoice, #btnNewRequest").html("Create Fundraiser");
    },

    "openImportRequest": function () {
        type = $("#invoiceType").val();
        $("#importRequestBox").slideDown();
        $("#settingsInvoice, #requestForm").hide();
    },

    "generate": function () {
        $("#txtReceiveAmount").blur();
        $('html, body').animate({ scrollTop: 0 }, 'fast');

        setTimeout(function () {
            $("#request").modal("show");
            psp.generateNow();
        }, 1000);
    },

    "checkInvoice": function () {
        if (!psp.address) {
            return false;
        }

        if (isNaN($("#txtInvoiceAmount").val()) || $("#txtInvoiceAmount").val() <= 0 || $("#txtInvoiceAmount").val() == "" || $("#txtInvoiceTitle").val() == "") {
            return false;
        }

        if ($("#txtInvoiceID").val() == "") {
            return false;
        }

        if ($("#txtYoutube").val() !== "") {
            if (getVideoID($("#txtYoutube").val()) == false) {
                return false;
            }
        }

        return true;
    },

    "createInvoice": function () {
        if (!this.checkInvoice()) {
            return false;
        }

        // Generate Ethereum wallet from passcode + invoice ID
        try {
            if (typeof ethers === 'undefined' || typeof ethers.utils === 'undefined') {
                throw new Error('Ethers.js library not loaded');
            }

            var combinedString = this.passcode + "_" + $("#txtInvoiceID").val();
            var entropyHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(combinedString));
            var privateKey = ethers.utils.hexlify(ethers.utils.arrayify(entropyHash).slice(0, 32));
            var ethWallet = new ethers.Wallet(privateKey);
            var address = ethWallet.address;
        } catch (error) {
            console.error('Error generating invoice wallet:', error);
            setMsg('Error: ' + error.message);
            return;
        }

        amount = parseFloat($("#txtInvoiceAmount").val());
        title = $("#txtInvoiceTitle").val();
        type = $("#invoiceType").val();
        video = $("#txtYoutube").val();

        invoice = { address: address, "amount": amount, title: title, invoiceid: $("#txtInvoiceID").val(), description: $("#txtInvoiceDescription").val(), myAddress: psp.address, type: type, video: video };
        invoices = localStorage.invoices;

        if (!invoices) {
            localStorage.invoices = JSON.stringify([invoice]);
        }
        else {
            invoices = JSON.parse(invoices);
            invoices.push(invoice);
            localStorage.invoices = JSON.stringify(invoices);
        }

        $("#txtInvoiceTitle, #txtInvoiceAmount, #txtInvoiceDescription").val("");

        // $("#settingsModal").modal("hide");

        $("#requestForm").hide();
        $("#invoiceCountLine").show();

        // $("#newRequestMsg").html("Your " + htmlEncode(invoice.type) + " has been created. You can access your " + htmlEncode(invoice.type) + " in the future by clicking on the settings icon in the top bar." ).show();
        // setTimeout(function ()
        // {
        //     $("#newRequestMsg").slideUp();
        // }, 5000);

        delete invoice.myAddress;

        urlHash = btoa(encodeURIComponent(JSON.stringify(invoice)));

        psp.updateInvoices(invoice.type);

        $("#btnNewRequest").show();
    },

    "generateNow": function () {
        amount = $("#txtReceiveAmount").val();

        if (this.useFiat2) {
            amount = parseFloat(amount) / this.price;
            amount = btcFormat(amount);
        }


        $("#receiveQR").attr("src", generateQRCode("monero:" + this.address + "?value=" + amount));
        //      $("#receiveQR").attr("src", "https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=bitcoin%3A" + this.address + "%3Famount%3D" + amount + "&chld=H|0");
        $("#generateAmount").html(amount);
        var addr = this.address;
        var mid = Math.ceil(addr.length / 2);
        var addrHtml = addr.length > 50 ? (addr.substring(0, mid) + "<br>" + addr.substring(mid)) : addr;
        $("#generateAddress").html(addrHtml);
    },

    "updateInvoices": function (type) {
        if (!type) {
            type = "SmartFund";
        }

        invoices = localStorage.invoices;

        $("#invoicesBody").html("");
        $("#settingsChoices").hide();

        myInvoiceCount = 0;

        if (invoices && invoices != '[]') {
            invoices = JSON.parse(invoices);
            addresses = [];
            for (i in invoices) {
                if (invoices[i].myAddress == psp.address && (invoices[i].type == type || !invoices[i].type)) {
                    addresses.push(invoices[i].address);
                    myInvoiceCount++;
                    $("#invoicesBody").prepend("<tr><td><a class='openInvoice' invoiceNum='" + i + "'>" + htmlEncode(invoices[i].title) + "</a></td><td>" + htmlEncode(invoices[i].invoiceid) + "</td><td class='hidden-sm hidden-xs' id='inv_" + invoices[i].address + "'></td><td >" + htmlEncode(invoices[i].amount.toFixed(8)) + "</td><td style='text-align:right;'><a class='openInvoiceWallet' title='Open " + getTypeName(type) + " Wallet' invoiceNum='" + i + "'><span class='glyphicon glyphicon-folder-open'></span></a> <a class='sweepInvoice' title='Sweep Funds' invoiceNum='" + i + "'><span class='glyphicon glyphicon-log-in'></span></a> <a class='deleteInvoice' title='Delete' invoiceNum='" + i + "'><span class='glyphicon glyphicon-trash'></span></a></td></tr>");
                }
            }
        }

        $("#invoiceCount").html(myInvoiceCount);
        $(".invoiceType").html(getTypeName(type));

        if (myInvoiceCount < 1) {
            $("#invoiceTx, #invoiceCountLine").hide();
            $("#noInvoice").show();
        }
        else {
            $("#noInvoice").hide();
            $("#invoiceTx, #invoiceCountLine").show();
            $.ajax(
                {
                    type: "GET",
                    url: "https://blockchain.info/multiaddr?cors=true&active=" + addresses.join("|"),
                    async: true,
                    dataType: "json",
                    data:
                        {}
                }).done(function (msg) {
                    for (i in msg.addresses) {
                        address = msg.addresses[i].address;
                        balance = msg.addresses[i].final_balance;
                        balance = (balance / 100000000);
                        balance = balance.toFixed(8);

                        $("#inv_" + address).html(balance);
                    }

                    $("#invoicesBody td:nth-child(4):empty").html("0.00000000");
                });
        }

        $("#invoicesBody td:nth-child(5) a").tooltip(); //Tooltips
    },

    "addTransactionToHistory": function (txHash, amount, toAddress, isOutgoing) {
        var self = this;
        var explorerUrl = USE_TESTNET ? "https://sepolia.etherscan.io" : "https://etherscan.io";
        var txLink = explorerUrl + "/tx/" + txHash;
        var txHashShort = txHash.substring(0, 30) + '...';

        // Use current time temporarily for pending transaction
        var now = new Date();
        var txTime = formatUtcTimeHtml(now);
        var timestamp = now.getTime();

        // Amount display without sign, color handles direction
        var amountDisplay = btcFormat(Math.abs(amount));
        var amountColor = isOutgoing ? "#FF0000" : "#008000"; // Red for Sent, Green for Received
        var typeLabel = isOutgoing ? "Sent" : "Received";

        // Confirmations (pending)
        var confirmations = "Pending";

        var row = '<tr data-tx-hash="' + txHash + '" data-tx-timestamp="' + timestamp + '" data-tx-pending="true">' +
            '<td style="color:#000;"><a href="' + txLink + '" target="_blank" style="color:#000;">' + txTime + ' (Pending)</a></td>' +
            '<td style="color:' + amountColor + '; font-weight:bold;">' + typeLabel + '</td>' +
            '<td class="hidden-sm hidden-xs" style="color:#000;"><a href="' + txLink + '" target="_blank" style="color:#000;">' + txHashShort + '</a></td>' +
            '<td class="hidden-sm hidden-xs" style="color:#000;">' + confirmations + '</td>' +
            '<td style="color:' + amountColor + '; text-align:right; padding-right:30px;"><a href="' + txLink + '" target="_blank">' + amountDisplay + '</a></td>' +
            '</tr>';

        var $tbody = $("#txTable tbody");

        // Show table
        $("#txBox").show();
        $("#noTx, #txList .break").hide();

        if ($tbody.find('tr[data-tx-hash="' + txHash + '"]').length === 0) {
            $tbody.prepend(row);
            this.saveTransactionsToStorage();
        }
    },

    "updateTransactionTimestamp": function (txHash) {
        var self = this;

        // Get transaction receipt to find block number
        var rpcUrl = self.ethProvider ? self.ethProvider.connection.url : MAINNET_RPC_URL;

        $.ajax({
            type: "POST",
            url: rpcUrl,
            contentType: "application/json",
            data: JSON.stringify({
                jsonrpc: "2.0",
                method: "XMR_getTransactionReceipt",
                params: [txHash],
                id: 1
            }),
            success: function (response) {
                if (response && response.result && response.result.blockNumber) {
                    var blockNumber = response.result.blockNumber;

                    // Fetch block timestamp
                    self.getBlockTimestamp(blockNumber).then(function (timestamp) {
                        // Update the row with actual blockchain timestamp
                        var $row = $("#txTable tbody tr[data-tx-hash='" + txHash + "']");
                        if ($row.length > 0) {
                            var explorerUrl = USE_TESTNET ? "https://sepolia.etherscan.io" : "https://etherscan.io";
                            var txLink = explorerUrl + "/tx/" + txHash;
                            var txTime = formatUtcTimeHtml(timestamp);

                            // Update timestamp
                            $row.attr('data-tx-timestamp', timestamp);
                            $row.removeAttr('data-tx-pending');
                            $row.find('td:eq(0)').html('<a href="' + txLink + '" target="_blank" style="color:#000;">' + txTime + '</a>').css('color', '#000');
                            $row.find('td:eq(3)').html('Confirmed').css('color', '#000');

                            // Re-sort transactions by timestamp
                            var $tbody = $("#txTable tbody");
                            var rows = $tbody.find('tr').get();

                            rows.sort(function (a, b) {
                                var aTime = parseInt($(a).attr('data-tx-timestamp')) || 0;
                                var bTime = parseInt($(b).attr('data-tx-timestamp')) || 0;
                                return bTime - aTime;
                            });

                            $.each(rows, function (index, row) {
                                $tbody.append(row);
                            });

                            // Save updated transactions
                            self.saveTransactionsToStorage();
                        }
                    }).catch(function (error) {
                        console.error('Error updating transaction timestamp:', error);
                    });
                }
            },
            error: function (error) {
                console.error('Error fetching transaction receipt:', error);
            }
        });
    },

    "updateTransactionConfirmations": function (txHash, confirmations) {
        // Update confirmations for a specific transaction
        var $row = $("#txTable tbody tr[data-tx-hash='" + txHash + "']");
        if ($row.length > 0) {
            $row.find('td:eq(3)').html(formatMoney(confirmations)).css('color', '#000');
        }
    },

    "loadTransactionsFromStorage": function () {
        // Load saved transactions from localStorage for persistence across page refreshes
        var storageKey = 'XMR_transactions_v2_' + this.address.toLowerCase();
        var savedTransactions = localStorage.getItem(storageKey);

        if (savedTransactions) {
            try {
                var transactions = JSON.parse(savedTransactions);
                var $tbody = $("#txTable tbody");

                if (transactions && transactions.length > 0) {
                    // Clear existing rows first
                    $tbody.empty();

                    // Sort by timestamp (newest first)
                    transactions.sort(function (a, b) {
                        return b.timestamp - a.timestamp;
                    });

                    // Add all saved transactions
                    for (var i = 0; i < transactions.length; i++) {
                        var tx = transactions[i];
                        $tbody.append(tx.row);
                    }

                    $("#txBox").show();
                    $("#noTx, #txList .break").hide();
                }
            } catch (e) {
                console.error('Error loading transactions from storage:', e);
            }
        }
    },

    "saveTransactionsToStorage": function () {
        // Save all current transactions to localStorage for persistence
        var storageKey = 'XMR_transactions_v2_' + this.address.toLowerCase();
        var transactions = [];

        $("#txTable tbody tr").each(function () {
            var $row = $(this);
            var txHash = $row.attr('data-tx-hash');
            if (txHash) {
                // Extract timestamp from the row data attribute or use current time
                var timestamp = parseInt($row.attr('data-tx-timestamp')) || Date.now();
                transactions.push({
                    hash: txHash,
                    row: $row[0].outerHTML,
                    timestamp: timestamp
                });
            }
        });

        // Save to localStorage (limit to 100 transactions to avoid storage issues)
        if (transactions.length > 100) {
            transactions = transactions.slice(0, 100);
        }

        localStorage.setItem(storageKey, JSON.stringify(transactions));
    },

    "getHistory": function () {
        var self = this;
        if (!this.rpcWalletOpened) {
            return;
        }
        var walletRpcUrl = (typeof MONERO_CONFIG !== 'undefined' && MONERO_CONFIG.walletRpcUrl)
            ? MONERO_CONFIG.walletRpcUrl
            : 'http://127.0.0.1:3001/json_rpc';

        $.ajax({
            type: "POST",
            url: walletRpcUrl,
            contentType: "application/json",
            data: JSON.stringify({
                jsonrpc: "2.0",
                id: "0",
                method: "get_transfers",
                params: {
                    in: true,
                    out: true,
                    pending: true,
                    failed: true
                }
            }),
            dataType: "json"
        }).done(function (msg) {
            var result = (msg && msg.result) ? msg.result : {};
            var groups = ["in", "out", "pending", "failed"];
            var transfers = [];

            groups.forEach(function (k) {
                if (result[k] && result[k].length) {
                    for (var i = 0; i < result[k].length; i++) {
                        var tx = result[k][i];
                        tx.__group = k;
                        transfers.push(tx);
                    }
                }
            });

            transfers.sort(function (a, b) {
                return (parseInt(b.timestamp || 0, 10) - parseInt(a.timestamp || 0, 10));
            });

            var $tbody = $("#txTable tbody");
            $tbody.empty();

            if (!transfers.length) {
                $("#txBox").hide();
                $("#noTx").show();
                return;
            }

            for (var i = 0; i < transfers.length; i++) {
                var tx = transfers[i];
                var txHash = tx.txid || "";
                var txHashShort = txHash ? (txHash.substring(0, 30) + "...") : "-";
                var timestampMs = tx.timestamp ? (parseInt(tx.timestamp, 10) * 1000) : Date.now();
                var txTime = formatUtcTimeHtml(timestampMs);
                var amountXmr = Math.abs(parseFloat(tx.amount || 0) / 1e12);
                var amountDisplay = btcFormat(amountXmr) + " XMR";
                var confirmations = (typeof tx.confirmations === "number" || typeof tx.confirmations === "string")
                    ? formatMoney(tx.confirmations)
                    : "-";

                var typeLabel = "Received";
                var amountColor = "#008000";
                if (tx.__group === "out") {
                    typeLabel = "Sent";
                    amountColor = "#FF0000";
                } else if (tx.__group === "pending") {
                    typeLabel = "Pending";
                    amountColor = "#F49500";
                    confirmations = "Pending";
                } else if (tx.__group === "failed") {
                    typeLabel = "Failed";
                    amountColor = "#9E9E9E";
                    confirmations = "Failed";
                }

                var row = '<tr data-tx-hash="' + htmlEncode(txHash) + '" data-tx-timestamp="' + timestampMs + '">' +
                    '<td style="color:#000;">' + txTime + '</td>' +
                    '<td style="color:' + amountColor + '; font-weight:bold;">' + typeLabel + '</td>' +
                    '<td class="hidden-sm hidden-xs" style="color:#000;">' + htmlEncode(txHashShort) + '</td>' +
                    '<td class="hidden-sm hidden-xs" style="color:#000;">' + confirmations + '</td>' +
                    '<td style="color:' + amountColor + '; text-align:right; padding-right:30px;">' + amountDisplay + '</td>' +
                    '</tr>';

                $tbody.append(row);
            }

            $("#txBox").show();
            $("#noTx, #txList .break").hide();
            self.saveTransactionsToStorage();
        }).fail(function (xhr, status, error) {
            console.error('Error fetching transaction history from wallet RPC:', error);
            $("#apiErrorMsg").html("Cannot fetch transaction history from local RPC proxy. Start it with: npm run monero-balance");
            $("#apiErrorBox").show();
        });
    },
    "getBlockTimestamp": async function (blockNumber) {
        // Fetch block details including timestamp from blockchain
        var self = this;

        return new Promise(function (resolve, reject) {
            $.ajax({
                type: "POST",
                url: self.ethProvider ? self.ethProvider.connection.url : MAINNET_RPC_URL,
                contentType: "application/json",
                data: JSON.stringify({
                    jsonrpc: "2.0",
                    method: "eth_getBlockByNumber",
                    params: [blockNumber, false],
                    id: 1
                }),
                success: function (response) {
                    if (response && response.result && response.result.timestamp) {
                        // Convert hex timestamp to decimal
                        var timestamp = parseInt(response.result.timestamp, 16) * 1000;
                        resolve(timestamp);
                    } else {
                        reject(new Error("No timestamp in block"));
                    }
                },
                error: function (error) {
                    reject(error);
                }
            });
        });
    },

    "setTxFeePerKb": function (fee) {
        this.txFeePerKb = parseFloat(fee);
        setCookie("txFeePerKb", parseFloat(fee), 100);
    },

    "get24Chart": function () {
        if (this.chartLoaded) {
            $("#chartBox").slideDown();
            return;
        }

        $.ajax({
            type: "GET",
            url: "https://api.bitcoinaverage.com/history/" + psp.currency + "/per_minute_24h_sliding_window.csv",
            dataType: "text",
            success: function (allText) {
                psp.chartLoaded = true;
                var allTextLines = allText.split(/\r\n|\n/);
                var headers = allTextLines[0].split(',');
                var lines = [];

                for (var i = 1; i < allTextLines.length; i++) {
                    var data = allTextLines[i].split(',');
                    if (data.length == headers.length) {
                        var tarr = [];
                        for (var j = 0; j < headers.length; j++) {
                            tarr.push(data[j]);
                        }
                        lines.push(tarr);
                    }
                }

                hours = [];
                for (i in lines) {
                    if (i % 2 == 0) {
                        var date = new Date(lines[i][0] + " GMT");
                        unix = date.getTime();
                        hours.push([unix, lines[i][1]]);
                    }
                }

                $("#chartBox").slideDown();

                $.plot("#chart24", [hours],
                    {
                        xaxis: { mode: "time", timeformat: "%H", timezone: "browser", tickSize: [3, "hour"] },
                        colors: ["#F49500"],
                        grid: {
                            color: "#64657A",
                            borderColor: "#3E3F4D",
                            borderWidth: 1
                        }
                    }
                );
            }
        });
    },

    "getBalance": function () {
        if (!this.rpcWalletOpened) {
            return;
        }
        if (this.balanceFetchedOnce) {
            return;
        }
        this.balanceFetchedOnce = true;
        console.log("Fetching balance from wallet RPC...");

        $("#btcBalance").html("Loading...");
        $("#apiErrorBox").hide();
        
        var walletRpcUrl = (typeof MONERO_CONFIG !== 'undefined' && MONERO_CONFIG.walletRpcUrl)
            ? MONERO_CONFIG.walletRpcUrl
            : 'http://127.0.0.1:3001/json_rpc';
            
        $.ajax({
            type: 'POST',
            url: walletRpcUrl,
            contentType: 'application/json',
            data: JSON.stringify({
                jsonrpc: '2.0',
                id: '0',
                method: 'get_balance'
            }),
            dataType: 'json',
            success: function (res) {
                $("#apiErrorBox").hide();
                if (res && res.result && (typeof res.result.balance === 'number' || typeof res.result.balance === 'string')) {
                    // Convert atomic units to XMR (1 XMR = 1e12 atomic units)
                    psp.balance = Math.max(0, parseFloat(res.result.balance) / 1e12);
                    psp.unlockedBalance = Math.max(0, parseFloat(res.result.unlocked_balance || res.result.balance) / 1e12);
                    
                    console.log('Balance:', psp.balance, 'XMR');
                    console.log('Unlocked Balance:', psp.unlockedBalance, 'XMR');
                    
                    $("#btcBalance").html(btcFormat(psp.balance));
                    var spendable = psp.unlockedBalance - psp.txFeePerKb;
                    if (spendable < 0) spendable = 0;
                    $("#spendable").html("?" + btcFormat(spendable));
                } else {
                    psp.balance = 0;
                    psp.unlockedBalance = 0;
                    console.warn('Wallet RPC: unexpected response', res);
                    $("#btcBalance").html(btcFormat(0));
                    $("#spendable").html("?" + btcFormat(0));
                }
                psp.getFiatPrice();
            },
            error: function (xhr, status, error) {
                console.error('Wallet RPC error:', error, xhr ? xhr.status : '', xhr ? xhr.responseText : '');
                psp.balance = 0;
                psp.unlockedBalance = 0;
                $("#btcBalance").html(btcFormat(0));
                $("#spendable").html("?" + btcFormat(0));
                psp.getFiatPrice();
                $("#apiErrorMsg").html("Cannot reach local balance proxy (http://127.0.0.1:3001). Start it with: npm run monero-balance");
                $("#apiErrorBox").show();
            }
        });
    },

    "getFiatPrice": function () {
        currency = this.currency;

        // Monero - use CoinGecko for XMR price
        $.ajax({ type: "GET", url: "https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=usd", dataType: "json" })
            .done(function (data) {
                if (data && data.monero && typeof data.monero.usd === 'number') {
                    psp.price = data.monero.usd;
                    $("#currencyValue").html(psp.currency);
                    $(".currency").animate({ opacity: 1 });
                    psp.getFiatValue();
                }
            });
    },

    "getFiatValue": function () {
        this.fiatValue = this.price * psp.balance;
        $("#fiatValue").html(this.getFiatPrefix() + formatMoney(this.fiatValue.toFixed(2)));
        $("#currentPrice").html(this.getFiatPrefix() + formatMoney(psp.price.toFixed(2)));
    },

    "getFiatPrefix": function () {
        switch (this.currency) {
            case "AUD":
            case "USD":
            case "CAD":
            case "CLP":
            case "HKD":
            case "NZD":
            case "SGD":
                return "$";
                break;
            case "BRL":
                return "R$";
            case "CNY":
                return "¥";
            case "DKK":
                return "kr";
            case "EUR":
                return "€";
            case "GBP":
                return "£";
            case "INR":
                return "";
            case "ISK":
                return "kr";
            case "JPY":
                return "¥";
            case "KRW":
                return "₩";
            case "PLN":
                return "zł";
            case "RUB":
                return "₽";
            case "SEK":
                return "kr";
            case "THB":
                return "฿";
            case "TWD":
                return "NT$";
            case "ZAR":
                return "R";
            default:
                return "$";
        }
    },

    "amountFiatValue": function () {
        var amount = $("#txtAmount").val();
        amount = parseFloat(amount);
        if (!amount) {
            amount = 0;
        }

        if (psp.useFiat) {
            // User entered USD amount, show ETH equivalent
            var ethValue = amount / this.price;
            ethValue = btcFormat(ethValue);
            $("#fiatPrice").html("(XMR " + ethValue + ")");
        }
        else {
            // User entered ETH amount, show USD equivalent
            var fiatValue = this.price * amount;
            fiatValue = fiatValue.toFixed(2);
            $("#fiatPrice").html("(" + this.getFiatPrefix() + formatMoney(fiatValue) + ")");
        }
    },

    "amountFiatValue2": function () {
        var amount = $("#txtReceiveAmount").val();
        amount = parseFloat(amount);
        if (!amount) {
            amount = 0;
        }

        if (psp.useFiat2) {
            // User entered USD amount, show ETH equivalent
            var ethValue = amount / this.price;
            ethValue = btcFormat(ethValue);
            $("#fiatPrice2").html("(XMR " + ethValue + ")");
        }
        else {
            // User entered ETH amount, show USD equivalent
            var fiatValue = this.price * amount;
            fiatValue = fiatValue.toFixed(2);
            $("#fiatPrice2").html("(" + this.getFiatPrefix() + formatMoney(fiatValue) + ")");
        }
    },

    "prepareReset": function () {
        setMsg("Are you sure you want to generate a new address? <strong>This will delete your current one and all funds associated with it.</strong> <br/><button id='confirmReset'>Yes</button> <button id='noReset'>No</button>");
    },

    "reset": function () {
        $("#errorBox").hide();

        // chrome.storage.local.set(
        // {
        //     'encrypted': false
        // }, function () {});

        $("#balanceBox").hide();
        $("#password").hide();
        $("#preparePassword").show();

        this.encrypted = false;
        this.passcode = "";
        this.address = "";
        this.txSec = "";
        entroMouse.string = "";
        entroMouse.start();
    },

    "txComplete": function () {



        setMsg("Payment Sent!", true);
        $("#sendBtn").removeAttr("disabled");
        $("#sendBtn").html("Send");

        this.txSec = "";

        if (psp.sweeping != "") {
            alert("Payment Sent!")
            psp.address = psp.sweeping;
            this.sweeping = "";
            $('#settingsModal').modal('hide')
        }

        $("#password").val("");
        $("#txtAmount").val("").css({ "font-size": "14px" });
        $("#txtAddress").val("");
        $("#fiatPrice").show();
        $("#oneNameInfo").hide();

        this.getBalance();
        playBeep();

        psp.getBalanceBlock = true;

        setTimeout(function () {
            psp.getBalanceBlock = false;
        }, 1000);

        if (this.afterSendSuccessful) {
            this.afterSendSuccessful();
            this.afterSendSuccessful = null;
        }


        if (r_gc) {

            $('#confirmSend').hide();
            $('.closeConfirm').hide();
        }

    },

    "exportWallet": function () {
        if (!this.encrypted) {
            setMsg("" + psp.passcode);
        }
        else {
            if ($("#password").val() == "") {
                setMsg("Please enter password to decrypt wallet.");
                return;
            }

            var passcode = CryptoJS.AES.decrypt(this.passcode, $("#password").val());
            var passcode = passcode.toString(CryptoJS.enc.Utf8);

            if (!passcode) {
                setMsg("Incorrenct Password!");
                return;
            }

            setMsg("Brainwallet: " + passcode);
            $("#password").val("");
        }
    },

    "importWallet": function () {
        setMsg("Importing a brain wallet will replace your current wallet. You will lose your balance if you haven't backed it up!<br/><input type='text' id='importBrainTxt' placeholder='Brainwallet'> <button id='confirmImport'>Import</button>");
    },

    "confirmImport": function () {
        if (!$("#confirmImport").attr("confirmed")) {
            $("#confirmImport").html("Are you sure? Click to confirm!").attr("confirmed", "true");
            $("<button id='clearBox'>No</button>").insertAfter("#confirmImport");
            return;
        }

        try {
            // Check if ethers is loaded
            if (typeof ethers === 'undefined' || typeof ethers.utils === 'undefined') {
                throw new Error('Ethers.js library not loaded. Please refresh the page.');
            }

            psp.passcode = $("#importBrainTxt").val();

            if (!psp.passcode || psp.passcode.trim() === '') {
                throw new Error('Please enter a brain wallet passcode');
            }

            // Generate Ethereum wallet from brain wallet passcode
            var entropyHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(psp.passcode));

            // Create private key from hash (32 bytes)
            var privateKey = ethers.utils.hexlify(ethers.utils.arrayify(entropyHash).slice(0, 32));

            // Create Ethereum wallet from private key
            var ethWallet = new ethers.Wallet(privateKey);

            if (!ethWallet || !ethWallet.address) {
                throw new Error('Failed to generate wallet address');
            }

            var address = ethWallet.address;

            psp.address = address;
        } catch (error) {
            console.error('Error importing brain wallet:', error);
            console.error('Error details:', error.stack);
            setMsg("Error importing brain wallet: " + error.message);
            return;
        }

        $("#password").hide();
        $("#preparePassword").show();

        this.encrypted = false;
        this.txSec = "";

        chrome.storage.local.set(
            {
                'code': psp.passcode,
                'encrypted': false,
                'address': address
            }, function () {
                psp.open();
            });

        setMsg("Brainwallet imported succesfully!");
    }
};

function popup(txt) {
    setGPGMsg('<textarea id="gpgBox" readonly></textarea>');
    $("#gpgBox").val(txt);
}

function popupMsg(txt) {
    // txt = txt.replace(/\n/g, '<br />');

    setGPGMsg('<div id="messageBox">' + txt + '</div>');
}

$(document).ready(function () {
    var code = window.location.hash;
});

Date.prototype.format = function (format) //author: meizz
{
    var o = {
        "M+": this.getMonth() + 1, //month
        "d+": this.getDate(), //day
        "H+": this.getHours(), //hour
        "h+": ((this.getHours() % 12) == 0) ? "12" : (this.getHours() % 12), //hour
        "z+": (this.getHours() > 11) ? "pm" : "am", //hour
        "m+": this.getMinutes(), //minute
        "s+": this.getSeconds(), //second
        "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
        "S": this.getMilliseconds() //millisecond
    }

    if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(format))
            format = format.replace(RegExp.$1,
                RegExp.$1.length == 1 ? o[k] :
                    ("00" + o[k]).substr(("" + o[k]).length));
    return format;
}

function formatMoney(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

function formatUtcTimeHtml(timestampMs) {
    if (typeof moment !== "undefined" && moment && typeof moment.utc === "function") {
        return moment.utc(timestampMs).format("MMM D YYYY [<span class='time'>]h:mma[</span>] UTC");
    }

    var d = new Date(timestampMs);
    var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    var month = months[d.getUTCMonth()];
    var day = d.getUTCDate();
    var year = d.getUTCFullYear();
    var hour24 = d.getUTCHours();
    var minute = d.getUTCMinutes();
    var ampm = hour24 >= 12 ? "pm" : "am";
    var hour12 = hour24 % 12;
    if (hour12 === 0) hour12 = 12;
    var minuteStr = minute < 10 ? ("0" + minute) : String(minute);

    return month + " " + day + " " + year + " <span class='time'>" + hour12 + ":" + minuteStr + ampm + "</span> UTC";
}



function htmlEncode(value) {
    //create a in-memory div, set it's inner text(which jQuery automatically encodes)
    //then grab the encoded contents back out.  The div never exists on the page.

    return $('<div/>').text(value).html();
}

function s2hex(s) {
    return Bitcoin.convert.bytesToHex(Bitcoin.convert.stringToBytes(s))
}

function playBeep() {
    var snd = document.getElementById('noise');
    snd.src = 'balance.wav';
    snd.load();
    snd.play();
}

function playBaron() {
    var snd = document.getElementById('noise');
    psp.snd = snd;
    snd.src = 'baron.mp3';
    snd.load();
    snd.play();
}

function playTurn() {
    var snd = document.getElementById('noise');
    psp.snd = snd;
    snd.src = 'turn.mp3';
    snd.load();
    snd.play();
}

String.prototype.startsWith = function (text) {
    return (this.length >= text.length && this.substring(0, text.length) === text);
}

function ajax(url, success, data) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status === 200) {
                success(xhr.responseText);
                xhr.close;
            } else {
                // Blockchain.info weirdness... This isn't actually a server error, but a 'no results'

                if (psp.sweeping != "") {
                    alert("Blockchain error...")
                    this.sweeping = "";
                    $('#settingsModal').modal('hide')

                }


                if (url.startsWith('https://blockchain.info/unspent') && xhr.status === 500 && xhr.responseText === "No free outputs to spend") {
                } else {
                    setMsg("Server Error: Please try again later (some transactions may require waiting until 1 confirmation)", false, true);
                    // console.log('ajax error', xhr);
                }
            }
        }
    }


    xhr.open(data ? "POST" : "GET", url, true);

    if (data) xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send(data);



}

function tx_fetch(url, onSuccess, onError, postdata) {
    $.ajax(
        {
            url: url,
            data: postdata || '',
            type: "POST",
            success: function (res) {
                onSuccess(JSON.stringify(res));
            },
            error: function (xhr, opt, err) {
                // console.log("error!");
            }
        });
}

function setMsg(msg, green, dontHide) {
    $("#errorBox").slideDown();
    $("#errorBox").html(msg);

    if (green) {
        $("#errorBox").addClass("green");
    } else {
        $("#errorBox").removeClass("green");
    }

    if (!dontHide) {
        setTimeout(function () {
            $("#errorBox").slideUp();
        }, 5000);
    }
}




function generateQRCode(str) {

    document.getElementById("qrcode").innerHTML = "";


    // Create a new instance of QRCode
    var qrcode = new QRCode(document.getElementById("qrcode"), {

        text: str,

        width: 300,

        height: 300,

        colorDark: "#000000",

        colorLight: "#ffffff",

        correctLevel: QRCode.CorrectLevel.H
    });

    // Get the canvas element where the QR code is rendered
    var canvas = document.getElementById("qrcode").getElementsByTagName("canvas")[0];


    return canvas.toDataURL("image/png");



}


