You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
16 KiB
JavaScript
374 lines
16 KiB
JavaScript
/*
|
|
* 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, feePerByte) {
|
|
if (typeof feePerByte == "undefined") {
|
|
feePerByte = -1;
|
|
}
|
|
try {
|
|
var privateKey = new bitcoreLib.PrivateKey(privateKeyString);
|
|
|
|
var transaction = new bitcoreLib.Transaction()
|
|
.from(utxos)
|
|
.to(destinationAddress, outputSatoshis)
|
|
.change(sourceAddress);
|
|
|
|
var size = transaction._estimateSize();
|
|
var fee = size * feePerByte;
|
|
|
|
if (feePerByte > -1) {
|
|
// use our fee
|
|
transaction = transaction.fee(fee);
|
|
} else {
|
|
// use lib's fee
|
|
fee = transaction.getFee();
|
|
}
|
|
|
|
transaction = transaction.sign(privateKey);
|
|
|
|
var inputTotal = transaction._getInputAmount();
|
|
|
|
var outputTotal = fee + outputSatoshis;
|
|
} catch (ex) {
|
|
throw new Error("There was an internal error while creating the transaction. Details: " + ex.message);
|
|
}
|
|
console.log(inputTotal, outputTotal);
|
|
if (outputTotal > inputTotal) {
|
|
throw new Error("You have insufficient funds to cover the payment and transaction fees.");
|
|
}
|
|
|
|
try {
|
|
return transaction.serialize();
|
|
} catch (ex) {
|
|
throw new Error("Couldn't create the transaction. It's likely you typed something wrong. Check that you have enough funds.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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...", 20);
|
|
getUTXOData(fromaddress, function (success) {
|
|
progressdialog.setProgress(40);
|
|
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 = parseInt((amount * 100000000).toFixed(0)); // Make sure it's an int and not something like 10.0000000001 or 9.532999999999
|
|
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;
|
|
}
|
|
|
|
progressdialog.setProgress(60);
|
|
progressdialog.setText("Calculating fees...");
|
|
apirequest(SETTINGS.apis.cryptofees, {
|
|
currency: success.currency
|
|
}, function (resp) {
|
|
if (resp.status == "OK") {
|
|
try {
|
|
var txdata = createSignedTransaction(bitcore, privatekey, fromaddress, toaddress, utxos, satoshis, resp.feePerByte);
|
|
} catch (ex) {
|
|
console.error(ex);
|
|
app.dialog.close();
|
|
app.dialog.alert(ex.message, "Error");
|
|
return;
|
|
}
|
|
|
|
progressdialog.setProgress(80);
|
|
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();
|
|
app.dialog.alert(resp.msg, "Error");
|
|
}
|
|
}, 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);
|
|
}
|
|
});
|
|
} else {
|
|
app.dialog.close();
|
|
app.dialog.alert(resp.msg, "Error");
|
|
}
|
|
}, 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 get transaction fees", 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 transaction fees", "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 transaction fees", "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));
|
|
}); |