Add Bitcoin/Dogecoin paper wallet spending (#9)

master
Skylar Ittner 2 years ago
parent c173912190
commit f44a3c8479

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -5,6 +5,8 @@
*/
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) {
@ -19,6 +21,182 @@ function scanWalletQrCode(callback) {
});
}
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 {type} txHash From UXTO (unspent output)
* @param {type} txOutputIndex From UXTO (unspent output)
* @param {type} script From UXTO (unspent output)
* @param {type} inputSatoshis From UXTO (unspent output)
* @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, txHash, txOutputIndex, script, inputSatoshis, outputSatoshis) {
var privateKey = new bitcoreLib.PrivateKey(privateKeyString);
var utxo = {
"txId": txHash,
"outputIndex": txOutputIndex,
"address": sourceAddress,
"script": script,
"satoshis": inputSatoshis
};
var transaction = new bitcoreLib.Transaction()
.from(utxo)
.to(destinationAddress, outputSatoshis)
.change(sourceAddress)
.sign(privateKey);
return transaction.serialize();
}
/**
* Get unspent outputs for a wallet address.
* @param {string} walletaddress
* @param {function} successCallback Passes object with {uxtos: [{txHash,txOutputIndex,script,value}], currency: "DOGE", label: "Dogecoin"}
* @param {function} errorCallback Passes string error message suitable for display
* @returns {undefined}
*/
function getUXTOData(walletaddress, successCallback, errorCallback) {
apirequest(SETTINGS.apis.getuxto, {
walletaddress: walletaddress
}, function (resp) {
if (resp.status == "OK") {
successCallback({
uxtos: 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 UXTO 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 UXTO 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 UXTO data", "Server/network problem: " + xhr.status + ": " + xhr.statusText);
}
});
}
function sendCoins(privatekey, fromaddress, toaddress, satoshis) {
var progressdialog = app.dialog.progress("Querying blockchain...", 25);
getUXTOData(fromaddress, function (success) {
progressdialog.setProgress(50);
progressdialog.setText("Creating transaction...");
if (success.uxtos.length == 0) {
app.dialog.close();
app.dialog.alert("Your wallet has no available funds (ZERO_LENGTH_UXTO).", "Error");
return;
} else if (success.uxtos.length > 1) {
app.dialog.close();
app.dialog.alert("For technical reasons, your wallet isn't compatible with this app right now. You can still sweep this wallet into an alternative app to spend it. (MULTIPLE_UXTO)", "Error");
return;
}
var bitcore = null;
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,
success.uxtos[0].txHash, success.uxtos[0].txOutputIndex, success.uxtos[0].script,
success.uxtos[0].value, 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 " + (satoshis / 100000000) + " " + success.currency + " to " + toaddress, "Success!");
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(), $('#transactionAmount').val() * 100000000);
}
function displayWalletBalance(address) {
if (!walletPubKeyRegex.test(address)) {
app.dialog.alert("That doesn't look like a valid wallet address.", "Error");

@ -24,6 +24,9 @@ function openGenericBarcodeScanner() {
code = result.split("#")[1];
action = "track";
}
} else if (paymentRequestRegex.test(result)) {
code = result;
action = "sendcrypto";
} else if (walletPubKeyRegex.test(result)) {
code = result;
action = "cryptowallet";
@ -42,6 +45,9 @@ function openGenericBarcodeScanner() {
case "cryptowallet":
router.navigate("/crypto/balance/" + code);
break;
case "sendcrypto":
app.dialog.alert("Not implemented.");
break;
default:
app.dialog.alert("This app can't understand what's in that barcode.", "Error");
return;

@ -45,6 +45,8 @@
<script src="node_modules/template7/dist/template7.min.js"></script>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/maplibre-gl/dist/maplibre-gl.js"></script>
<script src="assets/js/bitcore-lib.min.js"></script>
<script src="assets/js/bitcore-lib-doge.min.js"></script>
<script src="settings.js"></script>

@ -20,7 +20,7 @@
<div class="page-content">
<div class="row justify-content-center">
<div class="col-100 medium-90 xlarge-75 margin-horizontal">
<div class="col-100 medium-50 xlarge-50 margin-horizontal">
<div class="card">
<div class="card-header">Check Wallet Balance</div>
<div class="card-content card-content-padding">
@ -46,6 +46,123 @@
</div>
</div>
</div>
<div class="col-100 medium-50 xlarge-50 margin-horizontal">
<div class="card">
<div class="card-header">Send Crypto</div>
<div class="card-content card-content-padding">
Spend your Helena Express paper wallet.
</div>
<div class="card-content card-content-padding">
<div class="list margin-bottom-half">
<ul>
<li class="item-divider">Step 1</li>
<li class="item-content">
<div class="item-inner">
Scan or type your wallet address. This tells the network where to take the money from.
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" id="walletFromAddress" placeholder="1X68a3n1..." />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (d) {
$('#walletFromAddress').val(d);
});"><i class="fa-solid fa-inbox-out"></i> &nbsp; Scan Your Wallet Address
</div>
</li>
<li class="item-divider">Step 2</li>
<li class="item-content">
<div class="item-inner">
Scan or type your private key. The private key unlocks your wallet and authorizes the transfer.
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" id="walletPrivateKey" placeholder="6JJRxyW..." />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="scanPrivateKeyQrCode(function (d) {
$('#walletToAddress').val(d);
});"><i class="fa-solid fa-key"></i> &nbsp; Scan Your Private Key
</div>
</li>
<li class="item-divider">Step 3</li>
<li class="item-content">
<div class="item-inner">
Scan or paste the recipient's wallet address.
The money will be sent here. Important: the recipient must be expecting the
same cryptocurrency your wallet uses. Otherwise the money will
be lost forever.
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" id="walletToAddress" placeholder="1X68a3n1..." />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (d) {
$('#walletToAddress').val(d);
});"><i class="fa-solid fa-inbox-in"></i> &nbsp; Scan Recipient's Wallet
</div>
</li>
<li class="item-divider">Step 4</li>
<li class="item-content">
<div class="item-inner">
Enter the amount to send, in cryptocurrency (not in dollars).
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="number" id="transactionAmount" step="0.00000001" min="0.00000001" max="999999.99999999"/>
<span class="input-clear-button"></span>
</div>
</div>
</li>
<!--
TODO: add conversion tool here
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (d) {
$('#walletToAddress').val(d);
});"><i class="fa-solid fa-inbox-in"></i> &nbsp; Scan Recipient's Wallet
</div>
</li> -->
<li class="item-divider">Step 5</li>
<li class="item-content">
<div class="item-inner">
<div><span class="taptext">Tap</span><span class="clicktext">Click</span> the button to send the transaction.</div>
</div>
</li>
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="walletGUISendCoins()">
<i class="fa-solid fa-paper-plane"></i> &nbsp; Send Transaction
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>

@ -6,26 +6,42 @@
var SETTINGS = {
apis: {
// Tracking
track: "http://localhost/helena.express/apis/track/",
trackmultiple: "http://localhost/helena.express/apis/trackmultiple/",
// Shipping rates
rates: "http://localhost/helena.express/apis/rates/",
// Get appointment iframe URL
appointmentredirect: "http://localhost/helena.express/apis/appointmentredirect/",
// Request home pickup
requestpickup: "http://localhost/helena.express/apis/requestpickup/",
// Drop and Send
dropandsendlocations: "http://localhost/helena.express/apis/dropandsend/locations/",
dropandsendpickup: "http://localhost/helena.express/apis/dropandsend/requestpickup/",
// Fetch account data
getaccountinfo: "http://localhost/helena.express/apis/account/getinfo/",
// Fetch tracking numbers associated with account
gettrackingnumbers: "http://localhost/helena.express/apis/account/gettrackingnumbers/",
// Account login/registration/onboarding endpoints
authorstartverify: "http://localhost/helena.express/apis/account/authorstartverify/",
verifyauthcode: "http://localhost/helena.express/apis/account/verifyauthcode/",
accountregister: "http://localhost/helena.express/apis/account/register/",
// Setup saved payment method
redirecttopaymentsetup: "http://localhost/helena.express/apis/account/redirecttopaymentsetup/",
finishpaymentsetup: "http://localhost/helena.express/apis/account/finishpaymentsetup/",
// Send a telegram message
sendtelegram: "http://localhost/helena.express/apis/telegram",
// Fetch shop items
shopitems: "http://localhost/helena.express/apis/shop/items",
// Create a shop order
shopbuy: "http://localhost/helena.express/apis/shop/buy",
// Get receipts linked with account
getreceipts: "http://localhost/helena.express/apis/account/getreceipts",
getreceipt: "http://localhost/helena.express/apis/account/getreceipt",
walletbalance: "http://localhost/helena.express/apis/walletbalance"
// Crypto: check balance and send transactions
walletbalance: "http://localhost/helena.express/apis/crypto/walletbalance",
getuxto: "http://localhost/helena.express/apis/crypto/getuxto",
broadcasttransaction: "http://localhost/helena.express/apis/crypto/broadcasttransaction"
},
stripe_pubkey: "pk_test_51J6qFXCa1Fboir5UzPO3LCiMsVNiFP2lq4wR0dEcjJJVzAaJ3uRggDekZPB3qeYpMD3ayIYHKyD5sSn0IFLlEXMW001LqrvGSH",
branding: {

Loading…
Cancel
Save