diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -55,7 +55,7 @@ // parsing bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); -QString formatBitcoinURI(const SendCoinsRecipient &info); +QString formatBitcoinURI(const Config &cfg, const SendCoinsRecipient &info); // Returns true if given address+amount meets "dust" definition bool isDust(const QString &address, const Amount amount); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -10,6 +10,7 @@ #include "qvalidatedlineedit.h" #include "walletmodel.h" +#include "cashaddr.h" #include "config.h" #include "dstencode.h" #include "init.h" @@ -165,12 +166,23 @@ widget->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } +static bool IsCashAddrEncoded(const QUrl &uri) { + const std::string addr = (uri.scheme() + ":" + uri.path()).toStdString(); + auto decoded = cashaddr::Decode(addr); + return !decoded.first.empty(); +} + bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no bitcoincash: URI if (!uri.isValid() || uri.scheme() != URI_SCHEME) return false; SendCoinsRecipient rv; - rv.address = uri.path(); + if (IsCashAddrEncoded(uri)) { + rv.address = uri.scheme() + ":" + uri.path(); + } else { + // strip out uri scheme for base58 encoded addresses + rv.address = uri.path(); + } // Trim any following forward slash which may have been added by the OS if (rv.address.endsWith("/")) { rv.address.truncate(rv.address.length() - 1); @@ -232,8 +244,12 @@ return parseBitcoinURI(uriInstance, out); } -QString formatBitcoinURI(const SendCoinsRecipient &info) { - QString ret = (URI_SCHEME + ":%1").arg(info.address); +QString formatBitcoinURI(const Config &cfg, const SendCoinsRecipient &info) { + QString ret = info.address; + if (!cfg.UseCashAddrEncoding()) { + // prefix address with uri scheme for base58 encoded addresses. + ret = (URI_SCHEME + ":%1").arg(ret); + } int paramCount = 0; if (info.amount) { diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -279,7 +279,7 @@ const RecentRequestsTableModel *const submodel = model->getRecentRequestsTableModel(); const QString uri = - GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient); + GUIUtil::formatBitcoinURI(*cfg, submodel->entry(sel.row()).recipient); GUIUtil::setClipboard(uri); } diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -114,7 +114,7 @@ if (target.isEmpty()) target = info.address; setWindowTitle(tr("Request payment to %1").arg(target)); - QString uri = GUIUtil::formatBitcoinURI(info); + QString uri = GUIUtil::formatBitcoinURI(*cfg, info); ui->btnSaveAs->setEnabled(false); QString html; html += ""; @@ -185,7 +185,7 @@ } void ReceiveRequestDialog::on_btnCopyURI_clicked() { - GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(info)); + GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(*cfg, info)); } void ReceiveRequestDialog::on_btnCopyAddress_clicked() { diff --git a/src/qt/test/uritests.h b/src/qt/test/uritests.h --- a/src/qt/test/uritests.h +++ b/src/qt/test/uritests.h @@ -12,7 +12,9 @@ Q_OBJECT private Q_SLOTS: - void uriTests(); + void uriTestsBase58(); + void uriTestsCashAddr(); + void uriTestFormatURI(); }; #endif // BITCOIN_QT_TEST_URITESTS_H diff --git a/src/qt/test/uritests.cpp b/src/qt/test/uritests.cpp --- a/src/qt/test/uritests.cpp +++ b/src/qt/test/uritests.cpp @@ -4,12 +4,13 @@ #include "uritests.h" +#include "config.h" #include "guiutil.h" #include "walletmodel.h" #include -void URITests::uriTests() { +void URITests::uriTestsBase58() { SendCoinsRecipient rv; QUrl uri; uri.setUrl(QString( @@ -80,3 +81,119 @@ "000.0&label=Wikipedia Example")); QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); } + +void URITests::uriTestsCashAddr() { + SendCoinsRecipient rv; + QUrl uri; + uri.setUrl(QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "req-dontexist=")); + QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?dontexist=")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 0); + + uri.setUrl( + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?label=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString("Wikipedia Example Address")); + QVERIFY(rv.amount == 0); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=0.001")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 100000); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1.001")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 100100000); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=100&" + "label=Wikipedia Example")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.amount == 10000000000LL); + QVERIFY(rv.label == QString("Wikipedia Example")); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?message=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + + QVERIFY( + GUIUtil::parseBitcoinURI("bitcoincash://" + "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "message=Wikipedia Example Address", + &rv)); + QVERIFY(rv.address == + QString("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?req-message=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1," + "000&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); + + uri.setUrl(QString( + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1," + "000.0&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); +} + +namespace { +class UriTestConfig : public DummyConfig { +public: + UriTestConfig(bool useCashAddr) : useCashAddr(useCashAddr) {} + bool UseCashAddrEncoding() const override { return useCashAddr; } + +private: + bool useCashAddr; +}; + +} // anon ns + +void URITests::uriTestFormatURI() { + { + UriTestConfig cfg(true); + SendCoinsRecipient r; + r.address = "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; + r.message = "test"; + QString uri = GUIUtil::formatBitcoinURI(cfg, r); + QVERIFY(uri == "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "message=test"); + } + + { + UriTestConfig cfg(false); + SendCoinsRecipient r; + r.address = "175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"; + r.message = "test"; + QString uri = GUIUtil::formatBitcoinURI(cfg, r); + QVERIFY(uri == + "bitcoincash:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=test"); + } +}