/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var walletPubKeyRegex = /^(bc1|[13]|D)[a-zA-HJ-NP-Z0-9]{25,}$/; var paymentRequestRegex = /^(bitcoin|dogecoin):(bc1|[13]|D)[a-zA-HJ-NP-Z0-9]{25,}$/; var walletPrivateKeyRegex = /^[0-9A-Za-z]+$/; function scanWalletQrCode(callback) { scanBarcode(function (result) { if (walletPubKeyRegex.test(result)) { callback(result); } else { app.dialog.alert("That doesn't look like a valid wallet address.", "Error"); return; } }, function () { app.dialog.alert("Something went wrong and we can't scan right now.", "Error"); }); } function scanPrivateKeyQrCode(callback) { scanBarcode(function (result) { if (walletPrivateKeyRegex.test(result)) { callback(result); } else { app.dialog.alert("That doesn't look like a valid wallet address.", "Error"); return; } }, function () { app.dialog.alert("Something went wrong and we can't scan right now.", "Error"); }); } /** * Create and sign a crypto transaction. * * @param {type} bitcoreLib Bitcore, Litecore, Dogecore, etc. * @param {type} privateKeyString Private key from wallet QR code * @param {type} sourceAddress Sender's wallet address * @param {type} destinationAddress Recipient's wallet address * @param {Array} utxos Unspent transaction inputs, as array. See createUtxo() * @param {type} outputSatoshis Amount to send to recipient's wallet * @returns {string} Hex of serialized transaction, suitable for broadcast via Bitcoin Core or an API. */ function createSignedTransaction(bitcoreLib, privateKeyString, sourceAddress, destinationAddress, utxos, outputSatoshis) { var privateKey = new bitcoreLib.PrivateKey(privateKeyString); var transaction = new bitcoreLib.Transaction() .from(utxos) .to(destinationAddress, outputSatoshis) .change(sourceAddress) .sign(privateKey); return transaction.serialize(); } /** * Create a UTXO. * * @param {type} sourceAddress Sender's wallet address * @param {type} txHash From UTXO (unspent output) * @param {type} txOutputIndex From UTXO (unspent output) * @param {type} script From UTXO (unspent output) * @param {type} inputSatoshis From UTXO (unspent output) * @returns {createUtxo.utxo} */ function createUtxo(sourceAddress, txHash, txOutputIndex, script, inputSatoshis) { var utxo = { "txId": txHash, "outputIndex": txOutputIndex, "address": sourceAddress, "script": script, "satoshis": inputSatoshis }; return utxo; } /** * Get unspent outputs for a wallet address. * @param {string} walletaddress * @param {function} successCallback Passes object with {utxos: [{txHash,txOutputIndex,script,value}], currency: "DOGE", label: "Dogecoin"} * @param {function} errorCallback Passes string error message suitable for display * @returns {undefined} */ function getUTXOData(walletaddress, successCallback, errorCallback) { apirequest(SETTINGS.apis.getutxo, { walletaddress: walletaddress }, function (resp) { if (resp.status == "OK") { successCallback({ utxos: resp.unspent_outputs, currency: resp.currency, label: resp.label }); } else { errorCallback(resp.msg); } }, function (errorData) { try { var error = $.parseJSON(errorData.responseText); if (error && typeof error.msg != 'undefined') { errorCallback(resp.msg); sendErrorReport("Crypto", "Couldn't get UTXO data", error.msg); } else { errorCallback("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe."); sendErrorReport("Crypto", "Couldn't get UTXO data", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } } catch (ex) { errorCallback("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe."); sendErrorReport("Crypto", "Couldn't get UTXO data", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } }); } function sendCoins(privatekey, fromaddress, toaddress, amount) { var progressdialog = app.dialog.progress("Querying blockchain...", 25); getUTXOData(fromaddress, function (success) { progressdialog.setProgress(50); progressdialog.setText("Creating transaction..."); if (success.utxos.length == 0) { app.dialog.close(); app.dialog.alert("Your wallet has no available funds (ZERO_LENGTH_UTXO).", "Error"); return; } var utxos = []; for (var i = 0; i < success.utxos.length; i++) { utxos.push(createUtxo(fromaddress, success.utxos[i].txHash, success.utxos[i].txOutputIndex, success.utxos[i].script, success.utxos[i].value)); } var bitcore = null; var satoshis = amount * 100000000; switch (success.currency) { case "DOGE": bitcore = require("bitcore-lib-doge"); break; case "BTC": bitcore = require("bitcore-lib"); break; default: app.dialog.close(); app.dialog.alert("This app version doesn't support " + success.currency + ".", "Error"); return; } var txdata = createSignedTransaction(bitcore, privatekey, fromaddress, toaddress, utxos, satoshis); progressdialog.setProgress(75); progressdialog.setText("Sending payment..."); apirequest(SETTINGS.apis.broadcasttransaction, { transactiondata: txdata, currency: success.currency }, function (resp) { if (resp.status == "OK") { app.dialog.close(); app.dialog.alert("Sent " + amount + " " + success.currency + " to " + toaddress.substring(0, 5) + "..." + toaddress.substring(toaddress.length - 5, 999), "Success!"); $('#walletPrivateKey').val(""); // clear private key input box return; } else { app.dialog.close(); } }, function (errorData) { app.dialog.close(); try { var error = $.parseJSON(errorData.responseText); if (error && typeof error.msg != 'undefined') { app.dialog.alert(error.msg, "Error"); sendErrorReport("Crypto", "Couldn't broadcast transaction", error.msg); } else { app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error"); sendErrorReport("Crypto", "Couldn't broadcast transaction", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } } catch (ex) { app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error"); sendErrorReport("Crypto", "Couldn't broadcast transaction", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } }); }, function (error) { app.dialog.close(); app.dialog.alert(error, "Error"); }); } function walletGUISendCoins() { if (!walletPubKeyRegex.test($('#walletFromAddress').val())) { app.dialog.alert("Your wallet address doesn't look right. Check it and try again.", "Error"); return; } if (isNaN($('#transactionAmount').val()) || $('#transactionAmount').val() < 0.00000001) { app.dialog.alert("The amount to send doesn't look right. Check it and try again.", "Error"); return; } // Remove payment request URL stuff if ($('#walletToAddress').val().startsWith("bitcoin:")) { $('#walletToAddress').val($('#walletToAddress').val().replace("bitcoin:", "")); } if ($('#walletToAddress').val().startsWith("dogecoin:")) { $('#walletToAddress').val($('#walletToAddress').val().replace("dogecoin:", "")); } if (!walletPubKeyRegex.test($('#walletToAddress').val())) { app.dialog.alert("The recipient's wallet address doesn't look right. Check it and try again.", "Error"); return; } sendCoins($('#walletPrivateKey').val(), $('#walletFromAddress').val(), $('#walletToAddress').val(), parseFloat($('#transactionAmount').val())); } function displayWalletBalance(address) { if (!walletPubKeyRegex.test(address)) { app.dialog.alert("That doesn't look like a valid wallet address.", "Error"); return; } app.dialog.preloader("Loading..."); apirequest(SETTINGS.apis.walletbalance, { walletaddress: address }, function (resp) { app.dialog.close(); if (resp.status == "OK") { $("#walletBalancePopup #walletBalanceAmount").text(resp.balance + " " + resp.currency); $("#walletBalancePopup #walletFiatAmount").text(resp.usdvalue); $("#walletBalancePopup #walletCurrency").text(resp.label); $("#walletBalancePopup #walletBalanceAttribution").text(resp.attribution); $("#walletBalancePopup #walletBalanceLogo").attr("src", "./assets/images/crypto/" + resp.currency + ".svg"); app.popup.open("#walletBalancePopup"); } else { app.dialog.alert(resp.msg, "Error"); } }, function (error) { app.dialog.close(); try { var error = $.parseJSON(error.responseText); if (error && typeof error.msg != 'undefined') { app.dialog.alert(error.msg, "Error"); sendErrorReport("Crypto", "Couldn't get wallet balance", error.msg); } else { app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error"); sendErrorReport("Crypto", "Couldn't get wallet balance", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } } catch (ex) { app.dialog.alert("There's a server or network problem. Check your Internet connection or try again later. Your funds are safe.", "Error"); sendErrorReport("Crypto", "Couldn't get wallet balance", "Server/network problem: " + xhr.status + ": " + xhr.statusText); } }); } /** * Setup an input for specifying amount to send in USD, with conversion to crypto. * @param {string} walletAddress Detects cryptocurrency from wallet address * @returns {undefined} */ function setupFiatConversion(walletAddress) { apirequest(SETTINGS.apis.walletbalance, { walletaddress: walletAddress }, function (resp) { if (resp.status != "OK") { return; } if (resp.exchangerates.usd == -1) { return; } $("#cryptoFiatInputItem").css("display", ""); $("#cryptoAmountSendCurrencyLabel").text(resp.currency); $("#cryptoAmountSendFiatLabel").text("$"); $("#transactionAmountFiat").data("exchange-rate", resp.exchangerates.usd); $("#transactionAmountFiat").data("cryptocurrency", resp.currency); }); } /** * Hides the fiat conversion input box. * @returns {undefined} */ function unsetupFiatConversion() { $("#cryptoFiatInputItem").css("display", "none"); $("#cryptoAmountSendCurrencyLabel").text(""); $("#transactionAmountFiat").removeData("exchange-rate"); $("#transactionAmountFiat").removeData("cryptocurrency"); } $("#app").on("input change paste keyup", "#transactionAmountFiat", function () { var fiatamount = parseFloat($("#transactionAmountFiat").val()); var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate")); $("#transactionAmount").val((fiatamount / exchangerate).toFixed(8)); $("#transactionAmountFiat").val(fiatamount.toFixed(2)); }); $("#app").on("paste blur", "#walletFromAddress", function () { if (walletPubKeyRegex.test($("#walletFromAddress").val())) { setupFiatConversion($("#walletFromAddress").val()); } else { unsetupFiatConversion(); } }); $("#app").on("input change paste keyup", "#transactionAmount", function () { if ($("#cryptoFiatInputItem").css("display") == "none") { return; } var amount = parseFloat($("#transactionAmount").val()); var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate")); $("#transactionAmountFiat").val((amount * exchangerate).toFixed(2)); $("#transactionAmount").val(amount.toFixed(8)); });