Support crypto payment request URIs, add request crypto QR generator

master
Skylar Ittner 2 years ago
parent 771bb9edeb
commit 2167d7c206

@ -5,9 +5,52 @@
*/ */
var walletPubKeyRegex = /^(bc1|[13]|D)[a-zA-HJ-NP-Z0-9]{25,}$/; 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 paymentRequestRegex = /^(?:bitcoin|dogecoin):([a-zA-Z0-9]{20,40})(?:\?(.*))?$/;
var walletPrivateKeyRegex = /^[0-9A-Za-z]+$/; var walletPrivateKeyRegex = /^[0-9A-Za-z]+$/;
/**
* Parse a crypto URI and extract the info in it.
* Copyright (c) 2019 Robin Linus, MIT license, https://github.com/coins/bitcoin-uri-js
* @param {type} uri
* @returns {parsePaymentURI.parsed}
*/
function parsePaymentURI(uri) {
const legalKeys = ['address', 'amount', 'value', 'message', 'send', 'tx'];
const match = paymentRequestRegex.exec(uri);
if (!match) {
return null;
}
const parsed = {uri: uri}
if (match[2]) {
const queries = match[2].split('&');
for (let i = 0; i < queries.length; i++) {
const query = queries[i].split('=');
const key = query[0];
if (query.length === 2 && legalKeys.includes(key)) {
parsed[key] = decodeURIComponent(query[1].replace(/\+/g, '%20'));
}
}
}
parsed.address = match[1];
return parsed;
}
function openWalletPage(walletaddress) {
var navuri = '/crypto/' + walletaddress;
if (typeof router.currentRoute.query.paymenturi != 'undefined') {
var parsed = parsePaymentURI(router.currentRoute.query.paymenturi);
if (parsed != null) {
navuri += '/' + parsed.address;
if (typeof parsed['amount'] != 'undefined') {
navuri += '/' + parsed.amount;
}
}
}
router.navigate(navuri);
}
function scanWalletQrCode(callback) { function scanWalletQrCode(callback) {
scanBarcode(function (result) { scanBarcode(function (result) {
if (walletPubKeyRegex.test(result)) { if (walletPubKeyRegex.test(result)) {
@ -302,17 +345,16 @@ function openWalletBalancePage( {to, resolve, reject}) {
fiatvalue: resp.usdvalue, fiatvalue: resp.usdvalue,
currencyname: resp.label, currencyname: resp.label,
attribution: resp.attribution, attribution: resp.attribution,
exchangerate: resp.exchangerates.usd,
logo: "./assets/images/crypto/" + resp.currency + ".svg", logo: "./assets/images/crypto/" + resp.currency + ".svg",
walletaddress: to.params.walletaddress walletaddress: to.params.walletaddress,
sendtoaddress: (typeof to.params.toaddress != "undefined" ? to.params.toaddress : ""),
sendtoamount: (typeof to.params.amount != "undefined" ? to.params.amount : "")
}; };
resolve({ resolve({
content: compiledPages.crypto_wallet(context) content: compiledPages.crypto_wallet(context)
}); });
$("#walletBalanceAmount").text(resp.balance + " " + resp.currency);
$("#walletFiatAmount").text(resp.usdvalue);
$("#walletCurrency").text(resp.label);
$("#walletBalanceAttribution").text(resp.attribution);
$("#walletBalanceLogo").attr("src", "./assets/images/crypto/" + resp.currency + ".svg");
} else { } else {
reject(); reject();
app.dialog.alert(resp.msg, "Error"); app.dialog.alert(resp.msg, "Error");
@ -377,9 +419,45 @@ function setupFiatConversion(walletAddress) {
var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate")); var exchangerate = parseFloat($("#transactionAmountFiat").data("exchange-rate"));
$("#transactionAmount").val((fiatamount / exchangerate).toFixed(8)); $("#transactionAmount").val((fiatamount / exchangerate).toFixed(8));
}); });
if ($("#transactionAmount").val() != "") {
// Update the fiat conversion calculation if there's an amount prefilled
$("#transactionAmount").trigger("input");
}
}); });
} }
function setupReceiveFiatConversion() {
var exchangerate = $("#receiveAmountFiat").data("exchangerate");
var fiatlabel = $("#receiveAmountFiat").data("currencylabel");
if (exchangerate == -1) {
return;
}
$("#cryptoAmountReceiveFiatLI").css("display", "");
$("#cryptoAmountReceiveFiatLabel").text(fiatlabel);
$("#receiveAmount").off("input change paste keyup");
$("#receiveAmountFiat").off("input change paste keyup");
$("#receiveAmount").on("input change paste keyup", function () {
if ($("#cryptoAmountReceiveFiatLI").css("display") == "none") {
return;
}
var amount = parseFloat($("#receiveAmount").val());
var exchangerate = parseFloat($("#receiveAmountFiat").data("exchangerate"));
$("#receiveAmountFiat").val((amount * exchangerate).toFixed(2));
});
$("#receiveAmountFiat").on("input change paste keyup", function () {
var fiatamount = parseFloat($("#receiveAmountFiat").val());
var exchangerate = parseFloat($("#receiveAmountFiat").data("exchangerate"));
$("#receiveAmount").val((fiatamount / exchangerate).toFixed(8));
});
if ($("#receiveAmount").val() != "") {
// Update the fiat conversion calculation if there's an amount prefilled
$("#receiveAmount").trigger("input");
}
}
/** /**
* Hides the fiat conversion input box. * Hides the fiat conversion input box.
* @returns {undefined} * @returns {undefined}
@ -393,6 +471,24 @@ function unsetupFiatConversion() {
$("#transactionAmountFiat").off("input change paste keyup"); $("#transactionAmountFiat").off("input change paste keyup");
} }
function showPaymentRequestQRCode() {
var paymenturi = "";
switch ($("#receiveAmount").data("currency")) {
case "DOGE":
paymenturi = "dogecoin:";
break;
case "BTC":
paymenturi = "bitcoin:";
break;
}
paymenturi += $('#walletAddress').text();
if ($("#receiveAmount").val() > 0) {
paymenturi += "?amount=" + $("#receiveAmount").val();
}
$("#paymentRequestQRCodeContainer").html("");
new QRCode(document.getElementById("paymentRequestQRCodeContainer"), paymenturi);
}
$("#app").on("click", "#sendCryptoOpenPopupBtn", function () { $("#app").on("click", "#sendCryptoOpenPopupBtn", function () {
if (walletPubKeyRegex.test($("#walletAddress").text())) { if (walletPubKeyRegex.test($("#walletAddress").text())) {
setupFiatConversion($("#walletAddress").text()); setupFiatConversion($("#walletAddress").text());

@ -25,7 +25,7 @@ function openGenericBarcodeScanner() {
action = "track"; action = "track";
} }
} else if (paymentRequestRegex.test(result)) { } else if (paymentRequestRegex.test(result)) {
code = result; code = encodeURIComponent(result);
action = "sendcrypto"; action = "sendcrypto";
} else if (walletPubKeyRegex.test(result)) { } else if (walletPubKeyRegex.test(result)) {
code = result; code = result;
@ -46,7 +46,7 @@ function openGenericBarcodeScanner() {
router.navigate("/crypto/" + code); router.navigate("/crypto/" + code);
break; break;
case "sendcrypto": case "sendcrypto":
router.navigate("/crypto"); router.navigate("/crypto?paymenturi=" + code);
//app.dialog.alert("Not implemented."); //app.dialog.alert("Not implemented.");
break; break;
default: default:

File diff suppressed because one or more lines are too long

@ -44,6 +44,7 @@
<script src="node_modules/template7/dist/template7.min.js"></script> <script src="node_modules/template7/dist/template7.min.js"></script>
<script src="node_modules/jquery/dist/jquery.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="node_modules/maplibre-gl/dist/maplibre-gl.js"></script>
<script src="assets/js/qrcode.min.js"></script>
<script src="assets/js/bitcore-lib.min.js"></script> <script src="assets/js/bitcore-lib.min.js"></script>
<script src="assets/js/bitcore-lib-doge.min.js"></script> <script src="assets/js/bitcore-lib-doge.min.js"></script>

@ -20,21 +20,47 @@
<div class="block"> <div class="block">
<p>This program is licensed under the Mozilla Public License 2.0. <p>This program is licensed under the Mozilla Public License 2.0.
To get the source code, visit https://source.netsyms.com/Netsyms/HelenaExpressApp. To get the source code, visit https://source.netsyms.com/Netsyms/HelenaExpressApp.
<hr> <hr>
When viewing tracking results for a UPS package, the following copyright notice applies to the tracking data shown: When viewing tracking results for a UPS package, the following copyright notice applies to the tracking data shown:
<br> <br>
© 2021 United Parcel Service of America, Inc. All Rights Reserved. Confidential and Proprietary. © 2022 United Parcel Service of America, Inc. All Rights Reserved. Confidential and Proprietary.
The use, disclosure, reproduction, modification, transfer, or transmittal of this work for any purpose in any form The use, disclosure, reproduction, modification, transfer, or transmittal of this work for any purpose in any form
or by any means without the written permission of United Parcel Service is strictly prohibited. or by any means without the written permission of United Parcel Service is strictly prohibited.
<hr> <hr>
This application relies on and is bundled with third-party code. This application relies on and is bundled with third-party code.
See below for the their licenses and where to find source code. See below for the their licenses and where to find source code.
</div> </div>
<div class="block"> <div class="block">
<h2>Code and Libraries</h2> <h2>Code and Libraries</h2>
<pre style="white-space: pre-line; overflow-wrap: break-word;"> <pre style="white-space: pre-line; overflow-wrap: break-word;">
{{credits}} {{credits}}
-----
The following software may be included in this product: QRCode.js A copy of the source code may be downloaded from https://github.com/davidshimjs/qrcodejs. This software contains the following license and notice below:
The MIT License (MIT)
Copyright (c) 2012 davidshimjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre> </pre>
</div> </div>

@ -35,9 +35,7 @@
</div> </div>
<div class="col-100 medium-40 large-30"> <div class="col-100 medium-40 large-30">
<div class="block text-align-center"> <div class="block text-align-center">
<div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (code) { <div class="button hapticbtn button-fill" onclick="scanWalletQrCode(openWalletPage);"><i class="fa-solid fa-qrcode"></i> Scan Wallet</div>
router.navigate('/crypto/' + code);
});"><i class="fa-solid fa-qrcode"></i> &nbsp; Scan Wallet</div>
</div> </div>
<div class="block text-align-center"> <div class="block text-align-center">
<a href="#" onclick="$('#wallet-address-manual-entry').removeClass('display-none');$('a[name=wallet-address-manual-entry-anchor]').get(0).scrollIntoView();">Can't scan? <span class="taptext">Tap</span><span class="clicktext">Click</span> here.</a> <a href="#" onclick="$('#wallet-address-manual-entry').removeClass('display-none');$('a[name=wallet-address-manual-entry-anchor]').get(0).scrollIntoView();">Can't scan? <span class="taptext">Tap</span><span class="clicktext">Click</span> here.</a>
@ -57,7 +55,7 @@
</div> </div>
</li> </li>
<li class="item-content"> <li class="item-content">
<div class="button button-outline hapticbtn" onclick="router.navigate('/crypto/' + $('#walletPubKeyManualEntry').val())">Open Wallet</div> <div class="button button-outline hapticbtn" onclick="openWalletPage($('#walletPubKeyManualEntry').val());">Open Wallet</div>
</li> </li>
</ul> </ul>
</div> </div>

@ -31,7 +31,10 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-100 medium-50 large-30"> <div class="col-100 medium-50 large-30">
<div class="block"> <div class="block">
<div class="button hapticbtn button-fill popup-open" data-popup="#sendCryptoPopup" id="sendCryptoOpenPopupBtn"><i class="fa-solid fa-paper-plane"></i> &nbsp; Send Crypto</div> <div class="button hapticbtn button-fill popup-open" data-popup="#sendCryptoPopup" id="sendCryptoOpenPopupBtn"><i class="fa-solid fa-inbox-out"></i> Send</div>
</div>
<div class="block">
<div class="button hapticbtn button-fill popup-open" data-popup="#receiveCryptoPopup" id="receiveCryptoOpenPopupBtn"><i class="fa-solid fa-inbox-in"></i> Receive</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,101 +47,163 @@
</div> </div>
<div class="popup" id="sendCryptoPopup"> <div class="popup" id="sendCryptoPopup">
<div class="card"> <div class="navbar">
<div class="card-header">Send Crypto</div> <div class="navbar-bg"></div>
<div class="card-content card-content-padding"> <div class="navbar-inner">
<div class="list margin-bottom-half"> <div class="left">
<ul> <a class="link popup-close" href="#">
<li class="item-divider">Step 1</li> <i class="icon icon-back"></i>
<li class="item-content"> <span class="if-not-md">Close</span>
<div class="item-inner"> </a>
Scan your private key. The private key unlocks your wallet and authorizes the transfer. </div>
</div> <div class="title">Send Crypto</div>
</li> </div>
<li class="item-content item-input"> </div>
<div class="item-inner">
<div class="item-input-wrap"> <div class="list margin-bottom-half">
<input type="text" id="walletPrivateKey" placeholder="6JJRxyW..." /> <ul>
<span class="input-clear-button"></span> <li class="item-divider">Step 1</li>
</div> <li class="item-content">
</div> <div class="item-inner">
</li> Scan your private key. The private key unlocks your wallet and authorizes the transfer.
<li class="item-content"> </div>
<div class="button hapticbtn button-fill" onclick="scanPrivateKeyQrCode(function (d) { </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) {
$('#walletPrivateKey').val(d); $('#walletPrivateKey').val(d);
});"><i class="fa-solid fa-key"></i> &nbsp; Scan Private Key });"><i class="fa-solid fa-key"></i> Scan Private Key
</div> </div>
</li> </li>
<li class="item-divider">Step 2</li> <li class="item-divider">Step 2</li>
<li class="item-content"> <li class="item-content">
<div class="item-inner"> <div class="item-inner">
Scan or paste the recipient's wallet address. Scan or paste the recipient's wallet address.
The money will be sent here. Important: the recipient must be expecting the The money will be sent here. Important: the recipient must be expecting the
same cryptocurrency your wallet uses. Otherwise the money will same cryptocurrency your wallet uses. Otherwise the money will
be lost forever. be lost forever.
</div> </div>
</li> </li>
<li class="item-content item-input"> <li class="item-content item-input">
<div class="item-inner"> <div class="item-inner">
<div class="item-input-wrap"> <div class="item-input-wrap">
<input type="text" id="walletToAddress" placeholder="1X68a3n1..." /> <input type="text" id="walletToAddress" placeholder="1X68a3n1..." value="{{sendtoaddress}}" />
<span class="input-clear-button"></span> <span class="input-clear-button"></span>
</div> </div>
</div> </div>
</li> </li>
<li class="item-content"> <li class="item-content">
<div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (d) { <div class="button hapticbtn button-fill" onclick="scanWalletQrCode(function (d) {
$('#walletToAddress').val(d); var parsed = parsePaymentURI(d);
});"><i class="fa-solid fa-inbox-in"></i> &nbsp; Scan Recipient's Wallet if (parsed == null) {
</div> $('#walletToAddress').val(d);
</li> } else {
$('#walletToAddress').val(parsed.address);
if (typeof parsed['amount'] != 'undefined') {
$('#transactionAmount').val(parsed.amount);
}
}
});"><i class="fa-solid fa-inbox-in"></i> Scan Recipient's Wallet
</div>
</li>
<li class="item-divider">Step 3</li> <li class="item-divider">Step 3</li>
<li class="item-content"> <li class="item-content">
<div class="item-inner"> <div class="item-inner">
Enter the amount to send. Enter the amount to send.
</div> </div>
</li> </li>
<li class="item-content item-input"> <li class="item-content item-input">
<div class="item-inner"> <div class="item-inner">
<div class="item-title item-label" id="cryptoAmountSendCurrencyLabel"></div> <div class="item-title item-label" id="cryptoAmountSendCurrencyLabel"></div>
<div class="item-input-wrap"> <div class="item-input-wrap">
<input type="number" id="transactionAmount" step="0.00000001" min="0.00000001" max="999999.99999999"/> <input type="number" id="transactionAmount" step="0.00000001" min="0.00000001" max="999999.99999999" value="{{sendtoamount}}"/>
<span class="input-clear-button"></span> <span class="input-clear-button"></span>
</div> </div>
</div> </div>
</li> </li>
<li class="item-content item-input" id="cryptoFiatInputItem" style="display: none;"> <li class="item-content item-input" id="cryptoFiatInputItem" style="display: none;">
<div class="item-inner"> <div class="item-inner">
<div class="item-title item-label" id="cryptoAmountSendFiatLabel"></div> <div class="item-title item-label" id="cryptoAmountSendFiatLabel"></div>
<div class="item-input-wrap"> <div class="item-input-wrap">
<input type="number" id="transactionAmountFiat" step="0.01" min="0.01" max="9999.99"/> <input type="number" id="transactionAmountFiat" step="0.01" min="0.01" max="9999.99"/>
<span class="input-clear-button"></span> <span class="input-clear-button"></span>
</div> </div>
</div> </div>
</li> </li>
<li class="item-divider">Step 4</li> <li class="item-divider">Step 4</li>
<li class="item-content"> <li class="item-content">
<div class="item-inner"> <div class="item-inner">
<div><span class="taptext">Tap</span><span class="clicktext">Click</span> the button to send the transaction.</div> <div><span class="taptext">Tap</span><span class="clicktext">Click</span> the button to send the transaction.</div>
</div> </div>
</li> </li>
<li class="item-content"> <li class="item-content">
<div class="button hapticbtn button-fill" onclick="walletGUISendCoins()"> <div class="button hapticbtn button-fill" onclick="walletGUISendCoins()">
<i class="fa-solid fa-paper-plane"></i> &nbsp; Send Transaction <i class="fa-solid fa-paper-plane"></i> Send Transaction
</div> </div>
</li> </li>
<li class="item-content"> <li class="item-content">
<div class="button hapticbtn popup-close" onclick="$('#walletPrivateKey').val('');" > <div class="button hapticbtn popup-close" onclick="$('#walletPrivateKey').val('');" >
<i class="fa-solid fa-xmark"></i> &nbsp; Cancel <i class="fa-solid fa-xmark"></i> Cancel
</div> </div>
</li> </li>
</ul> </ul>
</div>
</div>
<div class="popup" id="receiveCryptoPopup">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="left">
<a class="link popup-close" href="#">
<i class="icon icon-back"></i>
<span class="if-not-md">Close</span>
</a>
</div> </div>
<div class="title">Request Crypto</div>
</div> </div>
</div> </div>
<div class="margin">
Enter the amount to request.
</div>
<div class="list margin-bottom-half margin-top-half">
<ul>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-title item-label" id="cryptoAmountReceiveCurrencyLabel">{{currencyunit}}</div>
<div class="item-input-wrap">
<input type="number" id="receiveAmount" step="0.00000001" min="0.00000001" max="999999.99999999" data-currency="{{currencyunit}}" />
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content item-input" id="cryptoAmountReceiveFiatLI" style="display: none;">
<div class="item-inner">
<div class="item-title item-label" id="cryptoAmountReceiveFiatLabel"></div>
<div class="item-input-wrap">
<input type="number" id="receiveAmountFiat" step="0.01" min="0.01" max="9999.99" data-exchangerate="{{exchangerate}}" data-currencylabel="$"/>
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content">
<div class="button hapticbtn button-fill" onclick="showPaymentRequestQRCode()">
<i class="fa-solid fa-qrcode"></i> Show Payment Request Code
</div>
</li>
</ul>
</div>
<div id="paymentRequestQRCodeContainer" class="block display-flex justify-content-center"></div>
</div> </div>
</div> </div>

@ -54,7 +54,7 @@ var routes = [
slideshow: [ slideshow: [
{ {
image: "assets/images/crypto/slides/intro.svg", image: "assets/images/crypto/slides/intro.svg",
text: "Bitcoin and cryptocurrency can be complicated. We've made it simple." text: "Bitcoin and cryptocurrency can be complicated. We've made it simple. Swipe left for more."
}, },
{ {
image: "assets/images/crypto/slides/vault.svg", image: "assets/images/crypto/slides/vault.svg",
@ -89,10 +89,37 @@ var routes = [
} }
} }
}, },
{
path: '/crypto/:walletaddress/:toaddress',
async: openWalletBalancePage,
name: 'crypto_wallet',
on: {
pageAfterIn: function () {
$("#sendCryptoOpenPopupBtn").click();
setupReceiveFiatConversion();
}
}
},
{
path: '/crypto/:walletaddress/:toaddress/:amount',
async: openWalletBalancePage,
name: 'crypto_wallet',
on: {
pageAfterIn: function () {
$("#sendCryptoOpenPopupBtn").click();
setupReceiveFiatConversion();
}
}
},
{ {
path: '/crypto/:walletaddress', path: '/crypto/:walletaddress',
async: openWalletBalancePage, async: openWalletBalancePage,
name: 'crypto_wallet' name: 'crypto_wallet',
on: {
pageAfterIn: function () {
setupReceiveFiatConversion();
}
}
}, },
{ {
path: '/home', path: '/home',

Loading…
Cancel
Save