Merge pull request 'Exchanges: Remove XMR.to' (#316) from tobtoht/feather:rip_to into master

Reviewed-on: https://git.wownero.com/feather/feather/pulls/316
This commit is contained in:
tobtoht 2021-02-01 17:03:30 +00:00
commit e3f6b73ab2
24 changed files with 2 additions and 1863 deletions

View file

@ -10,7 +10,6 @@ set(VERSION_REVISION "0")
set(VERSION "beta-3") set(VERSION "beta-3")
option(FETCH_DEPS "Download dependencies if they are not found" ON) option(FETCH_DEPS "Download dependencies if they are not found" ON)
option(XMRTO "Include Xmr.To module" ON)
option(XMRIG "Include XMRig module" ON) option(XMRIG "Include XMRig module" ON)
option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF) option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF)

View file

@ -41,7 +41,6 @@ via the `CMAKE_PREFIX_PATH` definition. For me this is:
There are some Monero/Feather related options/definitions that you may pass: There are some Monero/Feather related options/definitions that you may pass:
- `-DXMRTO=OFF` - disable Xmr.To feature
- `-DXMRIG=OFF` - disable XMRig feature - `-DXMRIG=OFF` - disable XMRig feature
- `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather - `-DTOR_BIN=/path/to/tor` - Embed a Tor executable inside Feather
- `-DDONATE_BEG=OFF` - disable the dreaded donate requests - `-DDONATE_BEG=OFF` - disable the dreaded donate requests

View file

@ -30,7 +30,6 @@ CMAKEFLAGS = \
-DARCH=x86_64 \ -DARCH=x86_64 \
-DBUILD_64=On \ -DBUILD_64=On \
-DBUILD_TESTS=Off \ -DBUILD_TESTS=Off \
-DXMRTO=On \
-DXMRIG=On \ -DXMRIG=On \
-DTOR_BIN=Off \ -DTOR_BIN=Off \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=11 \

View file

@ -122,10 +122,6 @@ if(DONATE_BEG)
target_compile_definitions(feather PRIVATE DONATE_BEG=1) target_compile_definitions(feather PRIVATE DONATE_BEG=1)
endif() endif()
if(XMRTO)
target_compile_definitions(feather PRIVATE HAS_XMRTO=1)
endif()
if(TOR_BIN) if(TOR_BIN)
target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1) target_compile_definitions(feather PRIVATE HAS_TOR_BIN=1)
endif() endif()

View file

@ -132,11 +132,6 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
// fiat/crypto lookup // fiat/crypto lookup
AppContext::prices = new Prices(); AppContext::prices = new Prices();
// xmr.to
#ifdef HAS_XMRTO
this->XMRTo = new XmrTo(this);
#endif
// XMRig // XMRig
#ifdef HAS_XMRIG #ifdef HAS_XMRIG
this->XMRig = new XmRig(this->configDirectory, this); this->XMRig = new XmRig(this->configDirectory, this);
@ -171,7 +166,6 @@ void AppContext::initTor() {
if (m_wsUrl.host().endsWith(".onion")) if (m_wsUrl.host().endsWith(".onion"))
this->ws->webSocket.setProxy(*networkProxy); this->ws->webSocket.setProxy(*networkProxy);
} }
} }
void AppContext::initWS() { void AppContext::initWS() {
@ -199,13 +193,6 @@ void AppContext::onSweepOutput(const QString &keyImage, QString address, bool ch
this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority); this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority);
} }
void AppContext::onCreateTransaction(XmrToOrder *order) {
// tx creation via xmr.to
const QString description = QString("XmrTo order %1").arg(order->uuid);
quint64 amount = WalletManager::amountFromDouble(order->incoming_amount_total);
this->onCreateTransaction(order->receiving_subaddress, amount, description, false);
}
void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) { void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) {
// tx creation // tx creation
this->tmpTxDescription = description; this->tmpTxDescription = description;
@ -434,12 +421,6 @@ void AppContext::onWSMessage(const QJsonObject &msg) {
QJsonObject fiat_rates = msg.value("data").toObject(); QJsonObject fiat_rates = msg.value("data").toObject();
AppContext::prices->fiatPricesReceived(fiat_rates); AppContext::prices->fiatPricesReceived(fiat_rates);
} }
#if defined(HAS_XMRTO)
else if(cmd == "xmrto_rates") {
auto xmr_rates = msg.value("data").toObject();
this->XMRTo->onRatesReceived(xmr_rates);
}
#endif
else if(cmd == "reddit") { else if(cmd == "reddit") {
QJsonArray reddit_data = msg.value("data").toArray(); QJsonArray reddit_data = msg.value("data").toArray();
this->onWSReddit(reddit_data); this->onWSReddit(reddit_data);

View file

@ -14,7 +14,6 @@
#include "utils/prices.h" #include "utils/prices.h"
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/tor.h" #include "utils/tor.h"
#include "utils/xmrto.h"
#include "utils/xmrig.h" #include "utils/xmrig.h"
#include "utils/wsclient.h" #include "utils/wsclient.h"
#include "utils/txfiathistory.h" #include "utils/txfiathistory.h"
@ -81,7 +80,6 @@ public:
Tor *tor{}; Tor *tor{};
WSClient *ws; WSClient *ws;
XmrTo *XMRTo;
XmRig *XMRig; XmRig *XMRig;
Nodes *nodes; Nodes *nodes;
static Prices *prices; static Prices *prices;
@ -114,7 +112,6 @@ public:
public slots: public slots:
void onOpenWallet(const QString& path, const QString &password); void onOpenWallet(const QString& path, const QString &password);
void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all); void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void onCreateTransaction(XmrToOrder *order);
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description); void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address); void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const; void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const;

View file

@ -34,6 +34,7 @@
<file>assets/images/cutexmrfox.png</file> <file>assets/images/cutexmrfox.png</file>
<file>assets/images/edit.png</file> <file>assets/images/edit.png</file>
<file>assets/images/exchange.png</file> <file>assets/images/exchange.png</file>
<file>assets/images/exchange_white.png</file>
<file>assets/images/expired.png</file> <file>assets/images/expired.png</file>
<file>assets/images/expired_icon.png</file> <file>assets/images/expired_icon.png</file>
<file>assets/images/eye1.png</file> <file>assets/images/eye1.png</file>
@ -97,8 +98,6 @@
<file>assets/images/unpaid.png</file> <file>assets/images/unpaid.png</file>
<file>assets/images/update.png</file> <file>assets/images/update.png</file>
<file>assets/images/warning.png</file> <file>assets/images/warning.png</file>
<file>assets/images/xmrto_big.png</file>
<file>assets/images/xmrto.png</file>
<file>assets/images/xmrig.ico</file> <file>assets/images/xmrig.ico</file>
<file>assets/images/xmrig.svg</file> <file>assets/images/xmrig.svg</file>
<file>assets/images/zoom.png</file> <file>assets/images/zoom.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,000 B

View file

@ -1,35 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "xmrtoinfodialog.h"
#include "ui_xmrtoinfodialog.h"
XmrToInfoDialog::XmrToInfoDialog(XmrToOrder *oInfo, QWidget *parent)
: QDialog(parent)
, ui(new Ui::XmrToInfoDialog)
, m_oInfo(oInfo)
{
ui->setupUi(this);
ui->status->setText(XmrTo::stateMap[(OrderState) oInfo->state]);
ui->xmrto_id->setText(!oInfo->uuid.isEmpty() ? oInfo->uuid : "");
ui->error_code->setText(!oInfo->errorCode.isEmpty() ? oInfo->errorCode : "");
ui->error_msg->setText(!oInfo->errorMsg.isEmpty() ? oInfo->errorMsg : "");
ui->xmr_amount->setText(QString::number(oInfo->incoming_amount_total));
ui->btc_amount->setText(QString::number(oInfo->btc_amount));
ui->rate->setText(oInfo->incoming_price_btc > 0 ? QString::number(oInfo->incoming_price_btc) : "");
ui->xmr_txid->setText(oInfo->xmr_txid);
ui->xmr_address->setText(oInfo->receiving_subaddress);
ui->btc_txid->setText(oInfo->btc_txid);
ui->btc_address->setText(oInfo->btc_dest_address);
this->adjustSize();
}
XmrToInfoDialog::~XmrToInfoDialog() {
delete ui;
}

View file

@ -1,28 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_XMRTOINFODIALOG_H
#define FEATHER_XMRTOINFODIALOG_H
#include <QDialog>
#include "utils/xmrto.h"
namespace Ui {
class XmrToInfoDialog;
}
class XmrToInfoDialog : public QDialog
{
Q_OBJECT
public:
explicit XmrToInfoDialog(XmrToOrder *oInfo, QWidget *parent = nullptr);
~XmrToInfoDialog() override;
private:
Ui::XmrToInfoDialog *ui;
XmrToOrder *m_oInfo;
};
#endif //FEATHER_XMRTOINFODIALOG_H

View file

@ -1,256 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>XmrToInfoDialog</class>
<widget class="QWidget" name="XmrToInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>689</width>
<height>581</height>
</rect>
</property>
<property name="windowTitle">
<string>Xmr.to Order</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Status:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>XMR.to ID:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Error code:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="status">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="xmrto_id">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="error_code">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>XMR amount:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>BTC amount:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Rate:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="xmr_amount">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="btc_amount">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="rate">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="error_msg">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>XMR.to address:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="xmr_address">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>XMR txid:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="xmr_txid">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>BTC address:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="btc_address">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>BTC txid:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="btc_txid">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Copy support template</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -97,22 +97,6 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo); connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo);
connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning(this, "https://featherwallet.org"); }); connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning(this, "https://featherwallet.org"); });
#if defined(HAS_XMRTO)
// xmr.to connects/widget
connect(ui->xmrToWidget, &XMRToWidget::viewOrder, m_ctx->XMRTo, &XmrTo::onViewOrder);
connect(ui->xmrToWidget, &XMRToWidget::getRates, m_ctx->XMRTo, &XmrTo::onGetRates);
connect(ui->xmrToWidget, &XMRToWidget::createOrder, m_ctx->XMRTo, &XmrTo::createOrder);
connect(m_ctx->XMRTo, &XmrTo::ratesUpdated, ui->xmrToWidget, &XMRToWidget::onRatesUpdated);
connect(m_ctx->XMRTo, &XmrTo::connectionError, ui->xmrToWidget, &XMRToWidget::onConnectionError);
connect(m_ctx->XMRTo, &XmrTo::connectionSuccess, ui->xmrToWidget, &XMRToWidget::onConnectionSuccess);
connect(m_ctx, &AppContext::balanceUpdated, ui->xmrToWidget, &XMRToWidget::onBalanceUpdated);
connect(m_ctx->XMRTo, &XmrTo::openURL, this, [=](const QString &url){ Utils::externalLinkWarning(this, url); });
connect(m_ctx, &AppContext::walletClosed, ui->xmrToWidget, &XMRToWidget::onWalletClosed);
ui->xmrToWidget->setHistoryModel(m_ctx->XMRTo->tableModel);
#else
ui->tabExchanges->setTabVisible(0, false);
#endif
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
// system tray // system tray
m_trayIcon = new QSystemTrayIcon(QIcon(":/assets/images/appicons/64x64.png")); m_trayIcon = new QSystemTrayIcon(QIcon(":/assets/images/appicons/64x64.png"));
@ -225,10 +209,6 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_ctx, &AppContext::initiateTransaction, ui->sendWidget, &SendWidget::onInitiateTransaction); connect(m_ctx, &AppContext::initiateTransaction, ui->sendWidget, &SendWidget::onInitiateTransaction);
connect(m_ctx, &AppContext::endTransaction, ui->sendWidget, &SendWidget::onEndTransaction); connect(m_ctx, &AppContext::endTransaction, ui->sendWidget, &SendWidget::onEndTransaction);
// XMR.to
connect(m_ctx, &AppContext::initiateTransaction, ui->xmrToWidget, &XMRToWidget::onInitiateTransaction);
connect(m_ctx, &AppContext::endTransaction, ui->xmrToWidget, &XMRToWidget::onEndTransaction);
connect(m_ctx, &AppContext::initiateTransaction, [this]{ connect(m_ctx, &AppContext::initiateTransaction, [this]{
m_statusDots = 0; m_statusDots = 0;
m_constructingTransaction = true; m_constructingTransaction = true;
@ -421,13 +401,8 @@ void MainWindow::initMenu() {
m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc); m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc"); m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
#if defined(HAS_XMRTO)
connect(ui->actionShow_Exchange, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Exchange"] = new ToggleTab(ui->tabExchange, "Exchange", "Exchange", ui->actionShow_Exchange, Config::showTabExchange);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Exchange, "Exchange");
#else
ui->actionShow_Exchange->setVisible(false); ui->actionShow_Exchange->setVisible(false);
#endif ui->tabWidget->setTabVisible(Tabs::EXCHANGES, false);
#if defined(HAS_XMRIG) #if defined(HAS_XMRIG)
connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map)); connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));

View file

@ -293,39 +293,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QTabWidget" name="tabExchanges">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabXMRto">
<attribute name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/xmrto.png</normaloff>:/assets/images/xmrto.png</iconset>
</attribute>
<attribute name="title">
<string>XMR.to</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="XMRToWidget" name="xmrToWidget" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabXmrRig"> <widget class="QWidget" name="tabXmrRig">
@ -764,12 +731,6 @@
<extends>QWidget</extends> <extends>QWidget</extends>
<header>historywidget.h</header> <header>historywidget.h</header>
</customwidget> </customwidget>
<customwidget>
<class>XMRToWidget</class>
<extends>QWidget</extends>
<header>xmrtowidget.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>CalcWidget</class> <class>CalcWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>

View file

@ -1,107 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "XmrToModel.h"
#include "model/ModelUtils.h"
#include "utils/xmrto.h"
#include "utils/ColorScheme.h"
XmrToModel::XmrToModel(QList<XmrToOrder*> *orders, QObject *parent)
: QAbstractTableModel(parent),
orders(orders)
{
}
void XmrToModel::update() {
beginResetModel();
endResetModel();
}
int XmrToModel::rowCount(const QModelIndex &) const {
return this->orders->count();
}
int XmrToModel::columnCount(const QModelIndex &) const {
return COUNT;
}
QVariant XmrToModel::data(const QModelIndex &index, int role) const {
const int _row = index.row();
const int _col = index.column();
const auto order = this->orders->at(_row);
if (role == Qt::DisplayRole){
switch(index.column()){
case Status:
{
QString status = XmrTo::stateMap[(OrderState) order->state];
if (order->state == OrderState::Status_OrderUnpaid)
return QString("%1 (%2)").arg(status, QString::number(order->countdown));
return status;
}
case ID:
return !order->uuid.isEmpty() ? order->uuid.split("-")[1] : "-";
case Destination:
return ModelUtils::displayAddress(order->btc_dest_address, 1);
case Conversion:
if(order->state <= OrderState::Status_OrderToBeCreated)
return "";
return QString("%1 XMR ⟶ %2 BTC").arg(QString::number(order->incoming_amount_total), QString::number(order->btc_amount));
case Rate:
return order->incoming_price_btc ? QString::number(order->incoming_price_btc, 'f', 6) : "";
case ErrorMsg:
if(order->errorMsg.isEmpty()) return "";
return order->errorMsg;
default: return {};
}
}
else if(role == Qt::BackgroundRole) {
if (_col == 0) {
if (order->state == OrderState::Status_OrderPaid || order->state == OrderState::Status_OrderPaidUnconfirmed)
return QBrush(ColorScheme::GREEN.asColor(true));
else if (order->state == OrderState::Status_OrderCreating || order->state == OrderState::Status_OrderToBeCreated)
return QBrush(ColorScheme::YELLOW.asColor(true));
else if (order->state == OrderState::Status_OrderUnpaid)
return QBrush(ColorScheme::YELLOW.asColor(true));
else if (order->state == OrderState::Status_OrderBTCSent)
return QBrush(ColorScheme::GREEN.asColor(true));
else if (order->state == OrderState::Status_OrderFailed || order->state == OrderState::Status_OrderTimedOut)
return QBrush(ColorScheme::RED.asColor(true));
}
}
else if (role == Qt::FontRole) {
switch(index.column()) {
case ID:
case Destination:
return ModelUtils::getMonospaceFont();
}
}
return QVariant();
}
QVariant XmrToModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case Status:
return QString("Status");
case ID:
return QString("ID");
case Destination:
return QString("Address");
case Conversion:
return QString("Conversion");
case Rate:
return QString("Rate");
case ErrorMsg:
return QString("Message");
default:
return QVariant();
}
}
return QVariant();
}

View file

@ -1,38 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_XMRTOMODEL_H
#define FEATHER_XMRTOMODEL_H
#include <QAbstractTableModel>
class XmrToOrder;
class XmrToModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum ModelColumn
{
Status = 0,
ID,
Conversion,
Rate,
Destination,
ErrorMsg,
COUNT
};
XmrToModel(QList<XmrToOrder*> *orders, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QList<XmrToOrder*> *orders;
public slots:
void update();
};
#endif //FEATHER_XMRTOMODEL_H

View file

@ -1,110 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "xmrto.h"
#include "appcontext.h"
QMap<OrderState, QString> XmrTo::stateMap;
XmrTo::XmrTo(AppContext *ctx, QObject *parent) :
QObject(parent),
m_ctx(ctx) {
m_baseUrl = m_ctx->networkType == NetworkType::Type::MAINNET ? "https://xmr.to" : "https://test.xmr.to";
m_netTor = new UtilsNetworking(this->m_ctx->network);
m_netClear = new UtilsNetworking(this->m_ctx->networkClearnet);
m_apiTor = new XmrToApi(this, m_netTor, m_baseUrl);
m_apiClear = new XmrToApi(this, m_netClear, m_baseUrl);
connect(m_apiTor, &XmrToApi::ApiResponse, this, &XmrTo::onApiResponse);
connect(m_apiClear, &XmrToApi::ApiResponse, this, &XmrTo::onApiResponse);
connect(this, &XmrTo::orderPaymentRequired, this->m_ctx, QOverload<XmrToOrder*>::of(&AppContext::onCreateTransaction));
XmrTo::stateMap[OrderState::Status_Idle] = "IDLE";
XmrTo::stateMap[OrderState::Status_OrderCreating] = "CREATING";
XmrTo::stateMap[OrderState::Status_OrderUnpaid] = "UNPAID";
XmrTo::stateMap[OrderState::Status_OrderToBeCreated] = "TO_BE_CREATED";
XmrTo::stateMap[OrderState::Status_OrderUnderPaid] = "UNDERPAID";
XmrTo::stateMap[OrderState::Status_OrderPaidUnconfirmed] = "PAID_UNCONFIRMED";
XmrTo::stateMap[OrderState::Status_OrderPaid] = "PAID";
XmrTo::stateMap[OrderState::Status_OrderBTCSent] = "BTC_SENT";
XmrTo::stateMap[OrderState::Status_OrderTimedOut] = "TIMED_OUT";
XmrTo::stateMap[OrderState::Status_OrderFailed] = "FAILED";
XmrTo::stateMap[OrderState::Status_OrderXMRSent] = "XMR_SENT";
this->tableModel = new XmrToModel(&this->orders, this);
}
void XmrTo::createOrder(double amount, const QString &currency, const QString &btcAddress) {
// ^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$
XmrToOrder *order;
order = new XmrToOrder(this->m_ctx, m_netTor, m_baseUrl, false, &this->rates, this);
connect(order, &XmrToOrder::orderFailed, this, &XmrTo::orderFailed);
connect(order, &XmrToOrder::orderPaid, this, &XmrTo::orderPaid);
connect(order, &XmrToOrder::orderPaidUnconfirmed, this, &XmrTo::orderPaidUnconfirmed);
connect(order, &XmrToOrder::orderPaymentRequired, this, &XmrTo::orderPaymentRequired);
connect(order, &XmrToOrder::orderChanged, this->tableModel, &XmrToModel::update);
order->create(amount, currency, btcAddress);
this->orders.append(order);
tableModel->update();
}
void XmrTo::onApiResponse(const XmrToResponse &resp) {
if (!resp.ok) {
this->onApiFailure(resp);
return;
}
emit connectionSuccess();
if (resp.endpoint == Endpoint::RATES) {
onRatesReceived(resp.obj);
}
}
void XmrTo::onApiFailure(const XmrToResponse &resp) {
emit connectionError(resp.message);
}
void XmrTo::onGetRates() {
m_apiTor->getRates();
}
void XmrTo::onRatesReceived(const QJsonObject &doc) {
this->rates.price = doc.value("price").toString().toDouble();
this->rates.ln_lower_limit = doc.value("ln_lower_limit").toString().toDouble();
this->rates.ln_upper_limit = doc.value("ln_upper_limit").toString().toDouble();
this->rates.lower_limit = doc.value("lower_limit").toString().toDouble();
this->rates.upper_limit = doc.value("upper_limit").toString().toDouble();
this->rates.zero_conf_enabled = doc.value("zero_conf_enabled").toBool();
this->rates.zero_conf_max_amount = doc.value("zero_conf_enabled").toString().toDouble();
emit ratesUpdated(rates);
}
void XmrTo::onNetworkChanged(bool clearnet) {
m_api = clearnet ? m_apiClear : m_apiTor;
}
void XmrTo::onWalletClosed() {
// @TODO: cleanup
for(const auto &order: this->orders)
order->deleteLater();
this->tableModel->update();
}
void XmrTo::onWalletOpened() {
// @TODO: read past XMR.To orders, start pending ones
}
void XmrTo::onViewOrder(const QString &orderId) {
QString url = QString("%1/nojs/status/%2").arg(this->m_baseUrl).arg(orderId);
emit openURL(url);
}

View file

@ -1,63 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_XMRTOCONVERT_H
#define FEATHER_XMRTOCONVERT_H
#include <QObject>
#include "model/XmrToModel.h"
#include "utils/xmrtoorder.h"
#include "utils/xmrtoapi.h"
class AppContext;
class XmrTo: public QObject {
Q_OBJECT
public:
explicit XmrTo(AppContext *ctx, QObject *parent = nullptr);
Q_ENUM(OrderState);
XmrToModel *tableModel;
static QMap<OrderState, QString> stateMap;
XmrToRates rates;
QList<XmrToOrder*> orders;
public slots:
void createOrder(double amount, const QString &currency, const QString &btcAddress);
void onGetRates();
void onRatesReceived(const QJsonObject &doc);
void onViewOrder(const QString &orderId);
void onNetworkChanged(bool clearnet);
void onWalletOpened();
void onWalletClosed();
private slots:
void onApiResponse(const XmrToResponse &doc);
signals:
void orderPaymentRequired(XmrToOrder *order);
void orderPaidUnconfirmed(XmrToOrder *order);
void orderPaid(XmrToOrder *order);
void orderFailed(XmrToOrder *order);
void ratesUpdated(XmrToRates rates);
void openURL(const QString &url);
void connectionError(const QString &err);
void connectionSuccess();
private:
void onApiFailure(const XmrToResponse &doc);
QString m_baseUrl;
AppContext *m_ctx;
int m_orderTimeout = 900; // https://xmrto-api.readthedocs.io/en/latest/introduction.html#various-parameters
UtilsNetworking *m_netTor;
UtilsNetworking *m_netClear;
XmrToApi *m_api;
XmrToApi *m_apiTor;
XmrToApi *m_apiClear;
};
#endif //FEATHER_XMRTOCONVERT_H

View file

@ -1,76 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "xmrtoapi.h"
XmrToApi::XmrToApi(QObject *parent, UtilsNetworking *network, QString baseUrl)
: QObject(parent)
, m_network(network)
, m_baseUrl(std::move(baseUrl))
{
}
void XmrToApi::getRates() {
QString url = QString("%1/api/v3/xmr2btc/order_parameter_query/").arg(this->m_baseUrl);
QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&XmrToApi::onResponse, this, reply, Endpoint::RATES));
}
void XmrToApi::createOrder(double amount, const QString &amount_currency, const QString &dest_address) {
QJsonObject order;
order["amount"] = amount;
order["amount_currency"] = amount_currency;
order["btc_dest_address"] = dest_address;
QString url = QString("%1/api/v3/xmr2btc/order_create/").arg(m_baseUrl);
QNetworkReply *reply = m_network->postJson(url, order);
connect(reply, &QNetworkReply::finished, std::bind(&XmrToApi::onResponse, this, reply, Endpoint::ORDER_CREATE));
}
void XmrToApi::getOrderStatus(const QString &uuid) {
QJsonObject order;
order["uuid"] = uuid;
QString url = QString("%1/api/v3/xmr2btc/order_status_query/").arg(m_baseUrl);
QNetworkReply *reply = m_network->postJson(url, order);
connect(reply, &QNetworkReply::finished, std::bind(&XmrToApi::onResponse, this, reply, Endpoint::ORDER_STATUS));
}
void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) {
const auto ok = reply->error() == QNetworkReply::NoError;
const auto err = reply->errorString();
QByteArray data = reply->readAll();
QJsonObject obj;
if (!data.isEmpty() && Utils::validateJSON(data)) {
auto doc = QJsonDocument::fromJson(data);
obj = doc.object();
}
else if (!ok) {
emit ApiResponse(XmrToResponse(false, endpoint, err));
return;
}
else {
emit ApiResponse(XmrToResponse(false, endpoint, "Invalid response from XMR.to"));
return;
}
XmrToError xmrto_err = XmrToApi::getApiError(obj);
if (!xmrto_err.code.isEmpty()) {
emit ApiResponse(XmrToResponse(false, endpoint, m_errorMap.contains(xmrto_err.code) ? m_errorMap[xmrto_err.code] : "", xmrto_err));
return;
}
reply->deleteLater();
emit ApiResponse(XmrToResponse(true, endpoint, "", obj));
}
XmrToError XmrToApi::getApiError(const QJsonObject &obj) {
if (!obj.contains("error"))
return XmrToError();
QString code = obj.value("error").toString();
QString msg = obj.value("error_msg").toString();
return XmrToError(code, msg);
}

View file

@ -1,87 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_XMRTOAPI_H
#define FEATHER_XMRTOAPI_H
#include <QObject>
#include <utility>
#include "utils/networking.h"
enum Endpoint {
RATES = 0,
ORDER_CREATE,
ORDER_STATUS
};
struct XmrToError {
explicit XmrToError(QString code = "", QString msg = "")
: code(std::move(code)), msg(std::move(msg)) {};
QString code;
QString msg;
};
struct XmrToResponse {
explicit XmrToResponse(bool ok, Endpoint endpoint, QString message, XmrToError error = XmrToError(), QJsonObject obj = {})
: ok(ok), endpoint(endpoint), message(std::move(message)), error(std::move(error)), obj(std::move(obj)) {};
explicit XmrToResponse(bool ok, Endpoint endpoint, QString message, QJsonObject obj)
: ok(ok), endpoint(endpoint), message(std::move(message)), obj(std::move(obj)) {};
bool ok;
Endpoint endpoint;
QString message;
XmrToError error = XmrToError();
QJsonObject obj;
};
class XmrToApi : public QObject {
Q_OBJECT
public:
explicit XmrToApi(QObject *parent, UtilsNetworking *network, QString baseUrl = "https://xmr.to");
void getRates();
void createOrder(double amount, const QString &amount_currency, const QString &dest_address);
void getOrderStatus(const QString &uuid);
signals:
void ApiResponse(XmrToResponse resp);
private slots:
void onResponse(QNetworkReply *reply, Endpoint endpoint);
private:
static XmrToError getApiError(const QJsonObject &obj);
QString m_baseUrl;
UtilsNetworking *m_network;
// https://xmrto-api.readthedocs.io/en/latest/introduction.html#list-of-all-error-codes
const QMap<QString, QString> m_errorMap = {
{"XMRTO-ERROR-001", "internal services not available, try again later."},
{"XMRTO-ERROR-002", "malformed bitcoin address, check address validity."},
{"XMRTO-ERROR-003", "invalid bitcoin amount, check amount data type."},
{"XMRTO-ERROR-004", "bitcoin amount out of bounds, check min and max amount."},
{"XMRTO-ERROR-005", "unexpected validation error, contact support."},
{"XMRTO-ERROR-006", "requested order not found, check order UUID."},
{"XMRTO-ERROR-007", "third party service not available, try again later."},
{"XMRTO-ERROR-008", "insufficient funds available, try again later."},
{"XMRTO-ERROR-009", "invalid request, check request parameters."},
{"XMRTO-ERROR-010", "payment protocol failed, invalid or outdated data served by URL."},
{"XMRTO-ERROR-011", "malformed payment protocol url, URL is malformed or cannot be contacted."},
{"XMRTO-ERROR-012", "too many requests, try less often."},
{"XMRTO-ERROR-013", "access forbidden."},
{"XMRTO-ERROR-014", "service is not available in your region."},
{"XMRTO-ERROR-015", "invalid monero amount, check amount data type."},
{"XMRTO-ERROR-016", "invalid currency, check available currency options."},
{"XMRTO-ERROR-017", "malformed lightning network invoice, provide a correct invoice for the main network."},
{"XMRTO-ERROR-018", "lightning payment unlikely to succeed, check first if xmr.to has routes available."},
{"XMRTO-ERROR-019", "lightning invoice preimage already known, dont use the same invoice more than once."}
};
};
#endif //FEATHER_XMRTOAPI_H

View file

@ -1,274 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "xmrtoorder.h"
#include "libwalletqt/Wallet.h"
#include "appcontext.h"
#include "globals.h"
XmrToOrder::XmrToOrder(AppContext *ctx, UtilsNetworking *network, QString baseUrl, bool clearnet, XmrToRates *rates, QObject *parent) :
QObject(parent),
m_ctx(ctx),
m_network(network),
m_baseUrl(std::move(baseUrl)),
m_rates(rates),
m_clearnet(clearnet) {
this->state = OrderState::Status_Idle;
m_baseUrl = m_ctx->networkType == NetworkType::Type::MAINNET ? "https://xmr.to" : "https://test.xmr.to";
m_api = new XmrToApi(this, network, m_baseUrl);
connect(m_api, &XmrToApi::ApiResponse, this, &XmrToOrder::onApiResponse);
connect(m_ctx, &AppContext::transactionCommitted, this, &XmrToOrder::onTransactionCommitted);
connect(m_ctx, &AppContext::createTransactionCancelled, this, &XmrToOrder::onTransactionCancelled);
}
void XmrToOrder::onTransactionCancelled(const QVector<QString> &address, double amount) {
// listener for all cancelled transactions - will try to match the exact amount to this order.
if(this->incoming_amount_total != amount || this->receiving_subaddress != address[0]) return;
this->errorMsg = "TX cancelled by user";
this->changeState(OrderState::Status_OrderFailed);
this->stop();
}
void XmrToOrder::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
// listener for all outgoing transactions - will try to match the exact amount to this order.
if(this->state == OrderState::Status_OrderUnpaid){
if(tx->amount() / globals::cdiv == this->incoming_amount_total) {
if(!status) {
this->errorMsg = "TX failed to commit";
this->changeState(OrderState::Status_OrderFailed);
this->stop();
return;
}
this->xmr_txid = txid.at(0);
this->m_paymentSent = true;
this->changeState(OrderState::Status_OrderXMRSent);
}
}
}
void XmrToOrder::onApiFailure(const XmrToResponse &resp) {
this->errorCode = resp.error.code;
this->errorMsg = resp.message;
switch (resp.endpoint) {
case ORDER_CREATE:
this->onCreatedError();
break;
case ORDER_STATUS:
this->onCheckedError(resp.error);
break;
default:
return;
}
}
void XmrToOrder::onApiResponse(const XmrToResponse& resp) {
if (!resp.ok) {
this->onApiFailure(resp);
return;
}
switch (resp.endpoint) {
case ORDER_CREATE:
this->onCreated(resp.obj);
break;
case ORDER_STATUS:
this->onChecked(resp.obj);
break;
default:
return;
}
}
void XmrToOrder::create(double amount, const QString &currency, const QString &btcAddress) {
if(this->m_ctx->currentWallet == nullptr) {
this->errorMsg = "No wallet opened";
this->changeState(OrderState::Status_OrderFailed);
return;
}
m_api->createOrder(amount, currency, btcAddress);
this->changeState(OrderState::Status_OrderCreating);
}
void XmrToOrder::onCreatedError() {
this->changeState(OrderState::Status_OrderFailed);
}
void XmrToOrder::onCreated(const QJsonObject &object) {
if(!object.contains("state"))
this->errorMsg = "Could not parse 'state' from JSON response";
if(object.value("state").toString() != "TO_BE_CREATED")
this->errorMsg = "unknown state from response, should be \"TO_BE_CREATED\"";
if(!this->errorMsg.isEmpty()) {
this->changeState(OrderState::Status_OrderFailed);
return;
}
if(m_created) return;
m_created = true;
this->btc_amount = object.value("btc_amount").toDouble();
this->btc_dest_address = object.value("btc_dest_address").toString();
this->uses_lightning = object.value("uses_lightning").toBool();
this->uuid = object.value("uuid").toString();
m_checkTimer.start(1000*5);
m_countdownTimer.start(1000);
connect(&m_checkTimer, &QTimer::timeout, this, &XmrToOrder::check);
connect(&m_countdownTimer, &QTimer::timeout, this, &XmrToOrder::onCountdown);
this->changeState(OrderState::Status_OrderToBeCreated);
this->check();
}
void XmrToOrder::check() {
if(this->m_ctx->currentWallet == nullptr)
return;
m_api->getOrderStatus(this->uuid);
}
void XmrToOrder::onCheckedError(const XmrToError& err) {
if (!err.code.isEmpty())
this->changeState(OrderState::Status_OrderFailed);
m_checkFailures += 1;
if(m_checkFailures > 15){
this->errorMsg = "Too many failed attempts";
this->changeState(OrderState::Status_OrderFailed);
}
}
void XmrToOrder::onChecked(const QJsonObject &object) {
if(object.contains("btc_amount"))
this->btc_amount = object.value("btc_amount").toString().toDouble();
if(object.contains("btc_dest_address"))
this->btc_dest_address = object.value("btc_dest_address").toString();
if(object.contains("seconds_till_timeout")) {
this->seconds_till_timeout = object.value("seconds_till_timeout").toInt();
this->countdown = this->seconds_till_timeout;
}
if(object.contains("created_at"))
this->created_at = object.value("created_at").toString();
if(object.contains("expires_at"))
this->expires_at = object.value("expires_at").toString();
if(object.contains("incoming_amount_total"))
this->incoming_amount_total = object.value("incoming_amount_total").toString().toDouble();
if(object.contains("remaining_amount_incoming"))
this->remaining_amount_incoming = object.value("remaining_amount_incoming").toString().toDouble();
if(object.contains("incoming_price_btc"))
{
qDebug() << object.value("incoming_price_btc").toString();
this->incoming_price_btc = object.value("incoming_price_btc").toString().toDouble();
}
if(object.contains("receiving_subaddress"))
this->receiving_subaddress = object.value("receiving_subaddress").toString();
if(object.contains("payments")) {
// detect btc txid, xmr.to api can output several - we'll just grab the first #yolo
auto payments = object.value("payments").toArray();
for(const auto &payment: payments){
auto obj = payment.toObject();
if(obj.contains("tx_id")) {
this->btc_txid = obj.value("tx_id").toString();
break;
}
}
}
this->changeState(object.value("state").toString());
}
void XmrToOrder::changeState(const QString &_state) {
for(const auto &key: XmrTo::stateMap.keys()) {
const auto &val = XmrTo::stateMap[key];
if(_state == val){
this->changeState(key);
return;
}
}
}
void XmrToOrder::changeState(OrderState _state) {
if(this->m_ctx->currentWallet == nullptr)
return;
if(_state == OrderState::Status_OrderUnderPaid && m_paymentSent) {
this->state = OrderState::Status_OrderXMRSent;
emit orderChanged();
return;
}
if(_state == this->state) return;
switch(_state){
case OrderState::Status_Idle:
break;
case OrderState::Status_OrderCreating:
break;
case OrderState::Status_OrderToBeCreated:
break;
case OrderState::Status_OrderUnderPaid:
emit orderFailed(this);
this->stop();
break;
case OrderState::Status_OrderUnpaid:
// need to send Monero
if(!m_paymentRequested) {
auto unlocked_balance = m_ctx->currentWallet->unlockedBalance() / globals::cdiv;
if (this->incoming_amount_total >= unlocked_balance) {
this->state = OrderState::Status_OrderFailed;
emit orderFailed(this);
this->stop();
break;
}
m_paymentRequested = true;
emit orderPaymentRequired(this);
}
break;
case OrderState::Status_OrderFailed:
emit orderFailed(this);
this->stop();
break;
case OrderState::Status_OrderPaidUnconfirmed:
emit orderPaidUnconfirmed(this);
break;
case OrderState::Status_OrderPaid:
emit orderPaid(this);
break;
case OrderState::Status_OrderTimedOut:
emit orderFailed(this);
this->stop();
break;
case OrderState::Status_OrderBTCSent:
emit orderPaid(this);
this->stop();
break;
default:
break;
}
this->state = _state;
emit orderChanged();
}
void XmrToOrder::onCountdown() {
if(this->countdown <= 0) return;
this->countdown -= 1;
emit orderChanged();
}
void XmrToOrder::stop(){
this->m_checkTimer.stop();
this->m_countdownTimer.stop();
}
XmrToOrder::~XmrToOrder(){
this->stop();
this->disconnect();
this->m_network->deleteLater();
}

View file

@ -1,106 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_XMRTOORDER_H
#define FEATHER_XMRTOORDER_H
#include <QObject>
#include "utils/networking.h"
#include "PendingTransaction.h"
#include "utils/xmrtoapi.h"
enum OrderState {
Status_Idle,
Status_OrderCreating,
Status_OrderToBeCreated,
Status_OrderUnpaid,
Status_OrderXMRSent,
Status_OrderUnderPaid,
Status_OrderPaidUnconfirmed,
Status_OrderPaid,
Status_OrderBTCSent,
Status_OrderTimedOut,
Status_OrderFailed
};
struct XmrToRates {
double price;
double upper_limit;
double lower_limit;
double ln_upper_limit;
double ln_lower_limit;
double zero_conf_max_amount;
bool zero_conf_enabled;
};
class XmrToOrder : public QObject {
Q_OBJECT
public:
explicit XmrToOrder(AppContext *ctx, UtilsNetworking *network, QString baseUrl, bool clearnet, XmrToRates *rates, QObject *parent = nullptr);
void create(double btcAmount, const QString &currency, const QString &btcAddress);
void changeState(OrderState state);
void changeState(const QString &state);
void stop();
int state;
int countdown = -1; // seconds remaining calculated from `seconds_till_timeout`
QString uuid;
QString errorMsg;
QString errorCode;
double btc_amount = 0;
QString btc_dest_address;
QString btc_txid;
QString xmr_txid;
bool uses_lightning = false;
QString receiving_subaddress;
QString created_at;
QString expires_at;
int seconds_till_timeout = -1;
double incoming_amount_total = 0; // amount_in_incoming_currency_for_this_order_as_string
double remaining_amount_incoming; // amount_in_incoming_currency_that_the_user_must_still_send_as_string
double incoming_price_btc = 0; // price_of_1_incoming_in_btc_currency_as_offered_by_service
public slots:
void onCountdown();
void onTransactionCancelled(const QVector<QString> &address, double amount);
void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
void onCreatedError();
void onChecked(const QJsonObject &object);
void onCheckedError(const XmrToError& err);
void check();
private:
bool m_created = false;
QString m_baseUrl;
QTimer m_checkTimer;
QTimer m_countdownTimer;
int m_checkFailures = 0;
bool m_clearnet;
bool m_paymentSent = false;
bool m_paymentRequested = false;
UtilsNetworking *m_network;
AppContext *m_ctx;
XmrToRates *m_rates;
XmrToApi *m_api;
~XmrToOrder();
signals:
void orderChanged();
void orderPaymentRequired(XmrToOrder *order);
void orderPaid(XmrToOrder *order);
void orderPaidUnconfirmed(XmrToOrder *order);
void orderFailed(XmrToOrder *order);
private slots:
void onCreated(const QJsonObject &object);
void onApiFailure(const XmrToResponse &resp);
void onApiResponse(const XmrToResponse &resp);
};
#endif //FEATHER_XMRTOORDER_H

View file

@ -1,193 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "xmrtowidget.h"
#include "ui_xmrtowidget.h"
#include "dialog/xmrtoinfodialog.h"
#include "mainwindow.h"
#include "globals.h"
#include <QMessageBox>
XMRToWidget::XMRToWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::XMRToWidget)
{
ui->setupUi(this);
m_ctx = MainWindow::getContext();
QString amount_rx = R"(^\d*\.\d*$)";
QRegExp rx;
rx.setPattern(amount_rx);
QValidator *validator = new QRegExpValidator(rx, this);
ui->lineAmount->setValidator(validator);
// xmrto logo (c) binaryFate et. al. :-D
QPixmap p(":assets/images/xmrto_big.png");
ui->logo->setPixmap(p.scaled(112, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->ratesLayout->hide();
// context menu
m_contextMenu = new QMenu();
m_showDetailsAction = m_contextMenu->addAction("Details");
m_viewOnXmrToAction = m_contextMenu->addAction("View order on XMR.to");
m_viewOnXmrToAction->setIcon(QIcon(":/assets/images/xmrto.png"));
connect(m_showDetailsAction, &QAction::triggered, this, &XMRToWidget::showInfoDialog);
connect(m_viewOnXmrToAction, &QAction::triggered, [&](){
QModelIndex index = ui->historyTable->currentIndex();
XmrToOrder *order = this->tableModel->orders->at(index.row());
emit viewOrder(order->uuid);
});
// connects
connect(ui->btnGetRates, &QPushButton::pressed, this, &XMRToWidget::onGetRates);
connect(ui->lineAmount, &QLineEdit::textChanged, this, &XMRToWidget::updateConversionLabel);
connect(ui->comboBox_currency, &QComboBox::currentTextChanged, this, &XMRToWidget::updateConversionLabel);
connect(ui->torCheckBox, &QCheckBox::stateChanged, this, &XMRToWidget::onTorCheckBoxToggled);
connect(ui->btnCreate, &QPushButton::clicked, this, &XMRToWidget::onCreateOrder);
ui->historyTable->header()->setStretchLastSection(true);
ui->historyTable->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->historyTable->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->historyTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->historyTable, &QTreeView::customContextMenuRequested, [&](const QPoint & point){
QModelIndex index = ui->historyTable->indexAt(point);
if (index.isValid()) {
m_contextMenu->popup(ui->historyTable->viewport()->mapToGlobal(point));
}
});
if (m_ctx->isTails || m_ctx->isWhonix) {
ui->torCheckBox->setDisabled(true);
}
connect(ui->historyTable, &QTreeView::doubleClicked, this, &XMRToWidget::showInfoDialog);
}
void XMRToWidget::setHistoryModel(XmrToModel *model) {
this->tableModel = model;
this->ui->historyTable->setModel(model);
}
void XMRToWidget::onBalanceUpdated(quint64 balance, quint64 spendable) {
this->m_unlockedBalance = spendable / globals::cdiv;
}
void XMRToWidget::onWalletClosed() {
ui->lineAddress->clear();
ui->lineAmount->clear();
ui->xmrLabelEstimate->setText("0.00 XMR");
}
void XMRToWidget::onCreateOrder() {
// @TODO: regex verify
auto amount = ui->lineAmount->text();
if(amount.isEmpty()) {
QMessageBox::warning(this, "Cannot create XMR.To order", "Invalid amount");
return;
}
double amount_num = amount.toDouble();
QString amount_cur = (ui->comboBox_currency->currentIndex() == curr::BTC) ? "BTC" : "XMR";
double amount_xmr = amount_num;
if (ui->comboBox_currency->currentIndex() == curr::BTC) {
amount_xmr = AppContext::prices->convert("BTC", "XMR", amount_num);
}
auto available = m_unlockedBalance;
if(amount_xmr > available){
QMessageBox::warning(this, "Cannot create XMR.To order", "Not enough Monero to create order.");
return;
}
ui->btnGetRates->setEnabled(false);
ui->btnCreate->setEnabled(false);
auto btc_address = ui->lineAddress->text();
emit createOrder(amount_num, amount_cur, btc_address);
QTimer::singleShot(2000, [=] {
ui->lineAmount->clear();
ui->lineAddress->clear();
ui->btnGetRates->setEnabled(true);
});
}
void XMRToWidget::onTorCheckBoxToggled(int state) {
ui->btnGetRates->setEnabled(true);
ui->btnCreate->setEnabled(false);
emit networkChanged(!state);
}
void XMRToWidget::updateConversionLabel() {
QString amount = ui->lineAmount->text();
int curIndex = ui->comboBox_currency->currentIndex();
QString symbolFrom = (curIndex == curr::XMR) ? "XMR" : "BTC";
QString symbolTo = (curIndex == curr::XMR) ? "BTC" : "XMR";
if(amount.isEmpty()) {
ui->xmrLabelEstimate->setText(QString("0.00 %1").arg(symbolTo));
return;
}
auto amount_num = amount.toDouble();
auto amount_converted = AppContext::prices->convert(symbolFrom, symbolTo, amount_num);
auto amount_converted_str = QString::number(amount_converted, 'f', 2);
auto fiat_cur = config()->get(Config::preferredFiatCurrency).toString();
auto amount_fiat = AppContext::prices->convert(symbolFrom, fiat_cur, amount_num);
auto amount_fiat_str = QString::number(amount_fiat, 'f', 2);
ui->xmrLabelEstimate->setText(QString("%1 %2, %3 %4").arg(amount_converted_str, symbolTo, amount_fiat_str, fiat_cur));
}
void XMRToWidget::onGetRates() {
ui->btnGetRates->setEnabled(false);
ui->btnCreate->setEnabled(false);
emit getRates();
}
void XMRToWidget::onConnectionError(QString msg) {
ui->btnGetRates->setEnabled(true);
ui->btnCreate->setEnabled(false);
msg = QString("%1\n\n%2").arg(msg).arg(m_regionBlockMessage);
QMessageBox::warning(this, "XMR.To Connection Error", msg);
}
void XMRToWidget::onConnectionSuccess() {
ui->btnGetRates->setEnabled(true);
ui->btnCreate->setEnabled(true);
}
void XMRToWidget::onRatesUpdated(XmrToRates rates) {
ui->label_rate->setText(QString("%1 BTC").arg(QString::number(rates.price)));
ui->label_minimum->setText(QString("%1 BTC").arg(QString::number(rates.lower_limit)));
ui->label_maximum->setText(QString("%1 BTC").arg(QString::number(rates.upper_limit)));
if(!m_ratesDisplayed) {
ui->ratesLayout->setVisible(true);
m_ratesDisplayed = true;
}
}
void XMRToWidget::onInitiateTransaction() {
ui->btnCreate->setEnabled(false);
}
void XMRToWidget::onEndTransaction() {
ui->btnCreate->setEnabled(true);
}
void XMRToWidget::showInfoDialog() {
QModelIndex index = ui->historyTable->currentIndex();
XmrToOrder *order = this->tableModel->orders->at(index.row());
auto *dialog = new XmrToInfoDialog(order, this);
dialog->exec();
dialog->deleteLater();
}
XMRToWidget::~XMRToWidget() {
delete ui;
}

View file

@ -1,67 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef XMRTOWIDGET_H
#define XMRTOWIDGET_H
#include <QWidget>
#include "widgets/tickerwidget.h"
#include "utils/xmrto.h"
#include "appcontext.h"
namespace Ui {
class XMRToWidget;
}
class XMRToWidget : public QWidget
{
Q_OBJECT
public:
explicit XMRToWidget(QWidget *parent = nullptr);
void setHistoryModel(XmrToModel *model);
~XMRToWidget() override;
public slots:
void onWalletClosed();
void onGetRates();
void onConnectionError(QString msg);
void onConnectionSuccess();
void onRatesUpdated(XmrToRates rates);
void onTorCheckBoxToggled(int state);
void onCreateOrder();
void onBalanceUpdated(quint64 balance, quint64 spendable);
void updateConversionLabel();
void onInitiateTransaction();
void onEndTransaction();
signals:
void getRates();
void networkChanged(bool clearnet);
void createOrder(double btnAmount, QString currency, QString btnAddress);
void viewOrder(const QString &orderId);
private:
void showInfoDialog();
QMap<QString, TickerWidget*> m_tickerWidgets;
QMenu *m_contextMenu;
QAction *m_viewOnXmrToAction;
QAction *m_showDetailsAction;
Ui::XMRToWidget *ui;
AppContext *m_ctx;
bool m_ratesDisplayed = false;
const QString m_regionBlockMessage = "Beware that XMR.To region blocks certain IPs, which can be problematic in combination with Tor. "
"Wait a few minutes for the circuit to switch, or disable the option to relay over Tor if the problem persists.";
double m_unlockedBalance = 0;
XmrToModel *tableModel;
enum curr {
BTC = 0,
XMR
};
};
#endif

View file

@ -1,327 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>XMRToWidget</class>
<widget class="QWidget" name="XMRToWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>896</width>
<height>472</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="torCheckBox">
<property name="text">
<string>Relay over Tor</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnGetRates">
<property name="text">
<string>Get Rates</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCreate">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Create Order</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Amount</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="lineAmount">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_currency">
<item>
<property name="text">
<string>BTC</string>
</property>
</item>
<item>
<property name="text">
<string>XMR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="xmrLabelEstimate">
<property name="text">
<string>0.00 XMR</string>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineAddress">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>BTC address</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="logo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>169</width>
<height>48</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Pay to</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="ratesLayout" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="tickerLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Rate (1 XMR)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_rate">
<property name="text">
<string>Rate</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Minimum</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_minimum">
<property name="text">
<string>Minimum</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Maximum</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_maximum">
<property name="text">
<string>Maximum</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTreeView" name="historyTable">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>