Add Bitcoin/Dogecoin paper wallet spending (#9)
This commit is contained in:
parent
c173912190
commit
f44a3c8479
1
www/assets/js/bitcore-lib-doge.min.js
vendored
Normal file
1
www/assets/js/bitcore-lib-doge.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
www/assets/js/bitcore-lib.min.js
vendored
Normal file
1
www/assets/js/bitcore-lib.min.js
vendored
Normal file
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> 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> 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> 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> 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> 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…
x
Reference in New Issue
Block a user