Remove light_client
This commit is contained in:
parent
751d68789a
commit
39220456ce
41 changed files with 0 additions and 3565 deletions
|
|
@ -4,8 +4,3 @@ add_subdirectory( witness_node )
|
|||
add_subdirectory( delayed_node )
|
||||
add_subdirectory( js_operation_serializer )
|
||||
add_subdirectory( size_checker )
|
||||
|
||||
set(BUILD_QT_GUI FALSE CACHE BOOL "Build the Qt-based light client GUI")
|
||||
if(BUILD_QT_GUI)
|
||||
# add_subdirectory(light_client)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Balance.hpp"
|
||||
#include "ChainDataModel.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
void Account::setAccountObject(const graphene::chain::account_object& obj)
|
||||
{
|
||||
auto oldName = m_account.name;
|
||||
auto oldMemoKey = memoKey();
|
||||
|
||||
m_account = obj;
|
||||
if (oldName != m_account.name)
|
||||
Q_EMIT nameChanged();
|
||||
if (oldMemoKey != memoKey())
|
||||
Q_EMIT memoKeyChanged();
|
||||
|
||||
if (!m_loaded) {
|
||||
m_loaded = true;
|
||||
Q_EMIT loaded();
|
||||
qDebug() << name() << "loaded.";
|
||||
}
|
||||
}
|
||||
|
||||
QString Account::memoKey() const
|
||||
{
|
||||
return toQString(m_account.options.memo_key);
|
||||
}
|
||||
|
||||
QQmlListProperty<Balance> Account::balances()
|
||||
{
|
||||
auto count = [](QQmlListProperty<Balance>* list) {
|
||||
return reinterpret_cast<Account*>(list->data)->m_balances.size();
|
||||
};
|
||||
auto at = [](QQmlListProperty<Balance>* list, int index) {
|
||||
return reinterpret_cast<Account*>(list->data)->m_balances[index];
|
||||
};
|
||||
|
||||
return QQmlListProperty<Balance>(this, this, count, at);
|
||||
}
|
||||
|
||||
double Account::getActiveControl(Wallet* w, int depth)const
|
||||
{
|
||||
if (depth >= GRAPHENE_MAX_SIG_CHECK_DEPTH) return 0;
|
||||
if (m_account.active.num_auths() == 0) return 0;
|
||||
if (m_account.active.weight_threshold == 0) return 0;
|
||||
|
||||
uint64_t weight = 0;
|
||||
for (auto& key : m_account.active.key_auths)
|
||||
{
|
||||
if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
|
||||
}
|
||||
|
||||
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
|
||||
for (auto& acnt : m_account.active.account_auths)
|
||||
{
|
||||
Account* account = model->getAccount(acnt.first.instance.value);
|
||||
if (!account->m_loaded) {
|
||||
QEventLoop el;
|
||||
connect(account, &Account::loaded, &el, &QEventLoop::quit);
|
||||
QTimer::singleShot(1000, &el, SLOT(quit()));
|
||||
el.exec();
|
||||
if (!account->m_loaded)
|
||||
// We don't have this account loaded yet... Oh well, move along
|
||||
continue;
|
||||
}
|
||||
if (account->getActiveControl(w, depth + 1) >= 1.0)
|
||||
weight += acnt.second;
|
||||
}
|
||||
|
||||
return double(weight) / double(m_account.active.weight_threshold);
|
||||
}
|
||||
|
||||
double Account::getOwnerControl(Wallet* w)const
|
||||
{
|
||||
if (m_account.owner.num_auths() == 0) return 0;
|
||||
if (m_account.owner.weight_threshold == 0) return 0;
|
||||
uint64_t weight = 0;
|
||||
for (auto& key : m_account.owner.key_auths)
|
||||
{
|
||||
if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
|
||||
}
|
||||
|
||||
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
|
||||
for (auto& acnt : m_account.owner.account_auths)
|
||||
{
|
||||
Account* account = model->getAccount(acnt.first.instance.value);
|
||||
if (!account->m_loaded)
|
||||
// We don't have this account loaded yet... Oh well, move along
|
||||
continue;
|
||||
if (account->getActiveControl(w) >= 1.0)
|
||||
weight += acnt.second;
|
||||
}
|
||||
|
||||
return double(weight) / double(m_account.owner.weight_threshold);
|
||||
}
|
||||
|
||||
void Account::update(const graphene::chain::account_balance_object& balance)
|
||||
{
|
||||
auto balanceItr = std::find_if(m_balances.begin(), m_balances.end(),
|
||||
[&balance](Balance* b) { return b->type()->id() == balance.asset_type.instance.value; });
|
||||
|
||||
if (balanceItr != m_balances.end()) {
|
||||
ilog("Updating ${a}'s balance: ${b}", ("a", name().toStdString())("b", balance));
|
||||
(*balanceItr)->update(balance);
|
||||
Q_EMIT balancesChanged();
|
||||
} else {
|
||||
ilog("Adding to ${a}'s new balance: ${b}", ("a", name().toStdString())("b", balance));
|
||||
Balance* newBalance = new Balance;
|
||||
newBalance->setParent(this);
|
||||
auto model = qobject_cast<ChainDataModel*>(parent());
|
||||
newBalance->setProperty("type", QVariant::fromValue(model->getAsset(balance.asset_type.instance.value)));
|
||||
newBalance->setProperty("amount", QVariant::fromValue(balance.balance.value));
|
||||
m_balances.append(newBalance);
|
||||
Q_EMIT balancesChanged();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include "GrapheneObject.hpp"
|
||||
|
||||
#include <QQmlListProperty>
|
||||
#include <graphene/app/full_account.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
class account_balance_object;
|
||||
}}
|
||||
|
||||
using graphene::chain::account_object;
|
||||
using graphene::chain::account_balance_object;
|
||||
|
||||
class Balance;
|
||||
class Wallet;
|
||||
|
||||
class Account : public GrapheneObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged)
|
||||
Q_PROPERTY(QString memoKey READ memoKey NOTIFY memoKeyChanged)
|
||||
Q_PROPERTY(bool isLoaded MEMBER m_loaded NOTIFY loaded)
|
||||
|
||||
account_object m_account;
|
||||
QList<Balance*> m_balances;
|
||||
bool m_loaded = false;
|
||||
|
||||
public:
|
||||
Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr)
|
||||
: GrapheneObject(id, parent)
|
||||
{
|
||||
m_account.name = name.toStdString();
|
||||
}
|
||||
void setAccountObject(const account_object& obj);
|
||||
const account_object& accountObject()const {
|
||||
return m_account;
|
||||
}
|
||||
|
||||
QString name()const { return QString::fromStdString(m_account.name); }
|
||||
QString memoKey()const;
|
||||
QQmlListProperty<Balance> balances();
|
||||
|
||||
void setBalances(QList<Balance*> balances) {
|
||||
if (balances != m_balances) {
|
||||
m_balances = balances;
|
||||
Q_EMIT balancesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void update(const account_balance_object& balance);
|
||||
|
||||
/**
|
||||
* Anything greater than 1.0 means full authority.
|
||||
* Anything between (0 and 1.0) means partial authority
|
||||
* 0 means no authority.
|
||||
*
|
||||
* @return the percent of direct control the wallet has over the account.
|
||||
*/
|
||||
Q_INVOKABLE double getOwnerControl(Wallet* w)const;
|
||||
Q_INVOKABLE double getActiveControl(Wallet* w , int depth = 0)const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void balancesChanged();
|
||||
void memoKeyChanged();
|
||||
void loaded();
|
||||
};
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Asset.hpp"
|
||||
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
QString Asset::formatAmount(qint64 amount) const
|
||||
{
|
||||
graphene::chain::asset_object ao;
|
||||
ao.precision = m_precision;
|
||||
return QString::fromStdString(ao.amount_to_string(amount));
|
||||
}
|
||||
|
||||
void Asset::update(const graphene::chain::asset_object& asset)
|
||||
{
|
||||
if (asset.id.instance() != id())
|
||||
setProperty("id", QVariant::fromValue(asset.id.instance()));
|
||||
if (asset.symbol != m_symbol.toStdString()) {
|
||||
m_symbol = QString::fromStdString(asset.symbol);
|
||||
Q_EMIT symbolChanged();
|
||||
}
|
||||
if (asset.precision != m_precision) {
|
||||
m_precision = asset.precision;
|
||||
Q_EMIT precisionChanged();
|
||||
}
|
||||
|
||||
if (asset.options.core_exchange_rate != coreExchangeRate)
|
||||
coreExchangeRate = asset.options.core_exchange_rate;
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GrapheneObject.hpp"
|
||||
|
||||
#include "graphene/chain/protocol/asset.hpp"
|
||||
|
||||
class Asset : public GrapheneObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString symbol MEMBER m_symbol READ symbol NOTIFY symbolChanged)
|
||||
Q_PROPERTY(quint32 precision MEMBER m_precision NOTIFY precisionChanged)
|
||||
|
||||
QString m_symbol;
|
||||
quint32 m_precision;
|
||||
|
||||
graphene::chain::price coreExchangeRate;
|
||||
|
||||
public:
|
||||
Asset(ObjectId id = -1, QString symbol = QString(), quint32 precision = 0, QObject* parent = nullptr)
|
||||
: GrapheneObject(id, parent), m_symbol(symbol), m_precision(precision)
|
||||
{}
|
||||
|
||||
QString symbol() const {
|
||||
return m_symbol;
|
||||
}
|
||||
|
||||
quint64 precisionPower() const {
|
||||
quint64 power = 1;
|
||||
for (int i = 0; i < m_precision; ++i)
|
||||
power *= 10;
|
||||
return power;
|
||||
}
|
||||
/// Given an amount like 123401, return "1234.01"
|
||||
Q_INVOKABLE QString formatAmount(qint64 amount) const;
|
||||
|
||||
void update(const graphene::chain::asset_object& asset);
|
||||
|
||||
Q_SIGNALS:
|
||||
void symbolChanged();
|
||||
void precisionChanged();
|
||||
};
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Balance.hpp"
|
||||
#include "Asset.hpp"
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
qreal Balance::amountReal() const {
|
||||
return amount / qreal(m_type->precisionPower());
|
||||
}
|
||||
|
||||
void Balance::update(const graphene::chain::account_balance_object& update)
|
||||
{
|
||||
if (update.balance != amount) {
|
||||
amount = update.balance.value;
|
||||
emit amountChanged();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include "GrapheneObject.hpp"
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
class account_balance_object;
|
||||
}}
|
||||
|
||||
class Asset;
|
||||
class Balance : public GrapheneObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(Asset* type MEMBER m_type READ type NOTIFY typeChanged)
|
||||
Q_PROPERTY(qint64 amount MEMBER amount NOTIFY amountChanged)
|
||||
|
||||
Asset* m_type;
|
||||
qint64 amount;
|
||||
|
||||
public:
|
||||
// This ultimately needs to be replaced with a string equivalent
|
||||
Q_INVOKABLE qreal amountReal() const;
|
||||
|
||||
Asset* type()const {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
void update(const graphene::chain::account_balance_object& update);
|
||||
|
||||
Q_SIGNALS:
|
||||
void typeChanged();
|
||||
void amountChanged();
|
||||
};
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/mem_fun.hpp>
|
||||
|
||||
using boost::multi_index_container;
|
||||
using boost::multi_index::indexed_by;
|
||||
using boost::multi_index::hashed_unique;
|
||||
using boost::multi_index::tag;
|
||||
using boost::multi_index::const_mem_fun;
|
||||
using boost::multi_index::ordered_unique;
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
cmake_minimum_required(VERSION 2.8.11)
|
||||
|
||||
project(light_client)
|
||||
|
||||
# Find includes in corresponding build directories
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5Core)
|
||||
find_package(Qt5Widgets)
|
||||
find_package(Qt5Quick)
|
||||
|
||||
file(GLOB QML qml/*)
|
||||
|
||||
# Skip building QRC in debug mode, since we access the QML files directly on disk in debug mode
|
||||
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
qt5_add_resources(QML_QRC qml/qml.qrc)
|
||||
endif()
|
||||
|
||||
add_executable(light_client
|
||||
Wallet.cpp
|
||||
ChainDataModel.cpp
|
||||
Operations.cpp
|
||||
GrapheneApplication.cpp
|
||||
GrapheneObject.cpp
|
||||
Asset.cpp
|
||||
Account.cpp
|
||||
Balance.cpp
|
||||
Transaction.cpp
|
||||
main.cpp ${QML_QRC} ${QML})
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.0)
|
||||
add_dependencies(light_client gen_qrc)
|
||||
endif()
|
||||
|
||||
target_link_libraries(light_client PRIVATE Qt5::Core Qt5::Widgets Qt5::Quick graphene_chain graphene_egenesis_brief graphene_utilities fc graphene_app )
|
||||
|
||||
install( TARGETS
|
||||
light_client
|
||||
|
||||
RUNTIME DESTINATION bin
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "ChainDataModel.hpp"
|
||||
#include "Balance.hpp"
|
||||
#include "Operations.hpp"
|
||||
#include "Transaction.hpp"
|
||||
|
||||
#include <graphene/app/api.hpp>
|
||||
#include <graphene/chain/protocol/protocol.hpp>
|
||||
|
||||
#include <fc/rpc/websocket_api.hpp>
|
||||
|
||||
#include <QMetaObject>
|
||||
|
||||
using namespace graphene::app;
|
||||
|
||||
template<typename T>
|
||||
QString idToString(T id) {
|
||||
return QString("%1.%2.%3").arg(T::space_id).arg(T::type_id).arg(ObjectId(id.instance));
|
||||
}
|
||||
QString idToString(graphene::db::object_id_type id) {
|
||||
return QString("%1.%2.%3").arg(id.space(), id.type(), ObjectId(id.instance()));
|
||||
}
|
||||
|
||||
ChainDataModel::ChainDataModel(fc::thread& t, QObject* parent)
|
||||
:QObject(parent),m_rpc_thread(&t){}
|
||||
|
||||
void ChainDataModel::setDatabaseAPI(fc::api<database_api> dbapi) {
|
||||
m_db_api = dbapi;
|
||||
m_rpc_thread->async([this] {
|
||||
m_global_properties = m_db_api->get_global_properties();
|
||||
m_db_api->subscribe_to_objects([this](const variant& v) {
|
||||
m_global_properties = v.as<global_property_object>();
|
||||
}, {m_global_properties.id});
|
||||
|
||||
m_dynamic_global_properties = m_db_api->get_dynamic_global_properties();
|
||||
m_db_api->subscribe_to_objects([this](const variant& d) {
|
||||
m_dynamic_global_properties = d.as<dynamic_global_property_object>();
|
||||
}, {m_dynamic_global_properties.id});
|
||||
|
||||
m_chain_properties = m_db_api->get_chain_properties();
|
||||
});
|
||||
}
|
||||
|
||||
void ChainDataModel::setNetworkAPI(fc::api<network_broadcast_api> napi)
|
||||
{
|
||||
m_net_api = napi;
|
||||
}
|
||||
|
||||
void ChainDataModel::broadcast(Transaction* transaction)
|
||||
{
|
||||
try {
|
||||
m_net_api->broadcast_transaction_with_callback([transaction](const fc::variant&) {
|
||||
transaction->setStatus(Transaction::Complete);
|
||||
}, transaction->internalTransaction());
|
||||
transaction->setStatus(Transaction::Pending);
|
||||
} catch (const fc::exception& e) {
|
||||
transaction->setStatus(Transaction::Failed);
|
||||
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Asset* ChainDataModel::getAsset(ObjectId id)
|
||||
{
|
||||
auto& by_id_idx = m_assets.get<by_id>();
|
||||
auto itr = by_id_idx.find(id);
|
||||
if (itr == by_id_idx.end())
|
||||
{
|
||||
auto result = m_assets.insert(new Asset(id, QString::number(--m_account_query_num), 0, this));
|
||||
assert(result.second);
|
||||
|
||||
// Run in RPC thread
|
||||
m_rpc_thread->async([this,id,result]{ getAssetImpl(idToString(asset_id_type(id)), &*result.first); });
|
||||
return *result.first;
|
||||
}
|
||||
return *itr;
|
||||
}
|
||||
|
||||
Asset* ChainDataModel::getAsset(QString symbol)
|
||||
{
|
||||
auto& by_symbol_idx = m_assets.get<by_symbol_name>();
|
||||
auto itr = by_symbol_idx.find(symbol);
|
||||
if (itr == by_symbol_idx.end())
|
||||
{
|
||||
auto result = m_assets.insert(new Asset(--m_account_query_num, symbol, 0, this));
|
||||
assert(result.second);
|
||||
|
||||
// Run in RPC thread
|
||||
m_rpc_thread->async([this,symbol,result](){ getAssetImpl(symbol, &*result.first); });
|
||||
return *result.first;
|
||||
}
|
||||
return *itr;
|
||||
}
|
||||
|
||||
QDateTime ChainDataModel::chainTime() const {
|
||||
return QDateTime::fromTime_t(m_dynamic_global_properties.time.sec_since_epoch());
|
||||
}
|
||||
|
||||
void ChainDataModel::processUpdatedObject(const fc::variant& update)
|
||||
{
|
||||
if (update.is_null())
|
||||
return;
|
||||
if (&fc::thread::current() == m_rpc_thread)
|
||||
{
|
||||
ilog("Proxying object update to app thread.");
|
||||
Q_EMIT queueExecute([this,update]{processUpdatedObject(update);});
|
||||
return;
|
||||
}
|
||||
|
||||
idump((update));
|
||||
try {
|
||||
auto id = update.as<variant_object>()["id"].as<object_id_type>();
|
||||
if (id.space() == protocol_ids) {
|
||||
switch (id.type()) {
|
||||
default:
|
||||
wlog("Update procedure for ${update} is not yet implemented.", ("update", update));
|
||||
break;
|
||||
}
|
||||
} else if (id.space() == implementation_ids) {
|
||||
switch (id.type()) {
|
||||
case impl_account_balance_object_type: {
|
||||
account_balance_object balance = update.as<account_balance_object>();
|
||||
auto owner = m_accounts.find(balance.owner.instance.value);
|
||||
if (owner != m_accounts.end())
|
||||
(*owner)->update(balance);
|
||||
else
|
||||
elog("Got unexpected balance update:\n${u}\nfor an account I don't have.",
|
||||
("u", update));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
wlog("Update procedure for ${update} is not yet implemented.", ("update", update));
|
||||
break;
|
||||
}
|
||||
} else
|
||||
wlog("Update procedure for ${update} is not yet implemented.", ("update", update));
|
||||
} catch (const fc::exception& e) {
|
||||
elog("Caught exception while updating object: ${e}", ("e", e.to_detail_string()));
|
||||
}
|
||||
}
|
||||
|
||||
void ChainDataModel::getAssetImpl(QString assetIdentifier, Asset* const * assetInContainer)
|
||||
{
|
||||
try {
|
||||
ilog("Fetching asset ${asset}", ("asset", assetIdentifier.toStdString()));
|
||||
auto result = m_db_api->lookup_asset_symbols({assetIdentifier.toStdString()});
|
||||
|
||||
// Run in main thread
|
||||
Q_EMIT queueExecute([this,result,assetInContainer](){
|
||||
ilog("Processing result ${r}", ("r", result));
|
||||
auto itr = m_assets.iterator_to(*assetInContainer);
|
||||
|
||||
if (result.size() == 0 || !result.front()) {
|
||||
(*itr)->deleteLater();
|
||||
m_assets.erase(itr);
|
||||
} else {
|
||||
m_assets.modify(itr,
|
||||
[=](Asset* a){
|
||||
a->setProperty("symbol", QString::fromStdString(result.front()->symbol));
|
||||
a->setProperty("id", ObjectId(result.front()->id.instance()));
|
||||
a->setProperty("precision", result.front()->precision);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch ( const fc::exception& e )
|
||||
{
|
||||
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
void ChainDataModel::getAccountImpl(QString accountIdentifier, Account* const * accountInContainer)
|
||||
{
|
||||
try {
|
||||
ilog("Fetching account ${acct}", ("acct", accountIdentifier.toStdString()));
|
||||
auto result = m_db_api->get_full_accounts([this](const fc::variant& v) {
|
||||
vector<variant> updates = v.as<vector<variant>>();
|
||||
for (const variant& update : updates) {
|
||||
if (update.is_object())
|
||||
processUpdatedObject(update);
|
||||
else
|
||||
elog("Handling object deletions is not yet implemented: ${update}", ("update", update));
|
||||
}
|
||||
// TODO: replace true on the next line with a smarter decision as to whether we need status updates or not
|
||||
}, {accountIdentifier.toStdString()}, true);
|
||||
fc::optional<full_account> accountPackage;
|
||||
|
||||
if (result.count(accountIdentifier.toStdString())) {
|
||||
accountPackage = result.at(accountIdentifier.toStdString());
|
||||
|
||||
// Fetch all necessary assets
|
||||
QList<asset_id_type> assetsToFetch;
|
||||
QList<Asset* const *> assetPlaceholders;
|
||||
assetsToFetch.reserve(accountPackage->balances.size());
|
||||
// Get list of asset IDs the account has a balance in
|
||||
std::transform(accountPackage->balances.begin(), accountPackage->balances.end(), std::back_inserter(assetsToFetch),
|
||||
[](const account_balance_object& b) { return b.asset_type; });
|
||||
auto function = [this,&assetsToFetch,&assetPlaceholders] {
|
||||
auto itr = assetsToFetch.begin();
|
||||
const auto& assets_by_id = m_assets.get<by_id>();
|
||||
// Filter out assets I already have, create placeholders for the ones I don't.
|
||||
while (itr != assetsToFetch.end()) {
|
||||
if (assets_by_id.count(itr->instance))
|
||||
itr = assetsToFetch.erase(itr);
|
||||
else {
|
||||
assetPlaceholders.push_back(&*m_assets.insert(new Asset(itr->instance, QString(), 0, this)).first);
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
};
|
||||
QMetaObject::invokeMethod(parent(), "execute", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const std::function<void()>&, function));
|
||||
assert(assetsToFetch.size() == assetPlaceholders.size());
|
||||
|
||||
// Blocking call to fetch and complete initialization for all the assets
|
||||
for (int i = 0; i < assetsToFetch.size(); ++i)
|
||||
getAssetImpl(idToString(assetsToFetch[i]), assetPlaceholders[i]);
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
Q_EMIT queueExecute([this,accountPackage,accountInContainer](){
|
||||
ilog("Processing result ${r}", ("r", accountPackage));
|
||||
auto itr = m_accounts.iterator_to(*accountInContainer);
|
||||
|
||||
if (!accountPackage.valid()) {
|
||||
(*itr)->deleteLater();
|
||||
m_accounts.erase(itr);
|
||||
} else {
|
||||
m_accounts.modify(itr, [this,&accountPackage](Account* a){
|
||||
a->setProperty("id", ObjectId(accountPackage->account.id.instance()));
|
||||
a->setAccountObject(accountPackage->account);
|
||||
|
||||
// Set balances
|
||||
QList<Balance*> balances;
|
||||
std::transform(accountPackage->balances.begin(), accountPackage->balances.end(), std::back_inserter(balances),
|
||||
[this](const account_balance_object& b) {
|
||||
Balance* bal = new Balance;
|
||||
bal->setParent(this);
|
||||
bal->setProperty("amount", QVariant::fromValue(b.balance.value));
|
||||
bal->setProperty("type", QVariant::fromValue(getAsset(ObjectId(b.asset_type.instance))));
|
||||
return bal;
|
||||
});
|
||||
a->setBalances(balances);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (const fc::exception& e)
|
||||
{
|
||||
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Account* ChainDataModel::getAccount(ObjectId id)
|
||||
{
|
||||
auto& by_id_idx = m_accounts.get<by_id>();
|
||||
auto itr = by_id_idx.find(id);
|
||||
if( itr == by_id_idx.end() )
|
||||
{
|
||||
auto tmp = new Account(id, tr("Account #%1").arg(--m_account_query_num), this);
|
||||
auto result = m_accounts.insert(tmp);
|
||||
assert(result.second);
|
||||
|
||||
// Run in RPC thread
|
||||
m_rpc_thread->async([this, id, result]{getAccountImpl(idToString(account_id_type(id)), &*result.first);});
|
||||
return *result.first;
|
||||
}
|
||||
return *itr;
|
||||
}
|
||||
|
||||
Account* ChainDataModel::getAccount(QString name)
|
||||
{
|
||||
auto& by_name_idx = m_accounts.get<by_account_name>();
|
||||
auto itr = by_name_idx.find(name);
|
||||
if( itr == by_name_idx.end() )
|
||||
{
|
||||
auto tmp = new Account(--m_account_query_num, name, this);
|
||||
auto result = m_accounts.insert(tmp);
|
||||
assert(result.second);
|
||||
|
||||
// Run in RPC thread
|
||||
m_rpc_thread->async([this, name, result]{getAccountImpl(name, &*result.first);});
|
||||
return *result.first;
|
||||
}
|
||||
return *itr;
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <fc/network/http/websocket.hpp>
|
||||
#include <graphene/app/api.hpp>
|
||||
|
||||
#include "BoostMultiIndex.hpp"
|
||||
#include "Asset.hpp"
|
||||
#include "Account.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
using graphene::chain::by_id;
|
||||
|
||||
namespace fc {
|
||||
class thread;
|
||||
}
|
||||
|
||||
struct by_symbol_name;
|
||||
typedef multi_index_container<
|
||||
Asset*,
|
||||
indexed_by<
|
||||
hashed_unique< tag<by_id>, const_mem_fun<GrapheneObject, ObjectId, &GrapheneObject::id > >,
|
||||
ordered_unique< tag<by_symbol_name>, const_mem_fun<Asset, QString, &Asset::symbol> >
|
||||
>
|
||||
> asset_multi_index_type;
|
||||
|
||||
struct by_account_name;
|
||||
typedef multi_index_container<
|
||||
Account*,
|
||||
indexed_by<
|
||||
hashed_unique< tag<by_id>, const_mem_fun<GrapheneObject, ObjectId, &GrapheneObject::id > >,
|
||||
ordered_unique< tag<by_account_name>, const_mem_fun<Account, QString, &Account::name> >
|
||||
>
|
||||
> account_multi_index_type;
|
||||
|
||||
class Transaction;
|
||||
class ChainDataModel : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QDateTime chainTime READ chainTime NOTIFY blockReceived)
|
||||
|
||||
void processUpdatedObject(const fc::variant& update);
|
||||
|
||||
void getAssetImpl(QString assetIdentifier, Asset* const * assetInContainer);
|
||||
void getAccountImpl(QString accountIdentifier, Account* const * accountInContainer);
|
||||
|
||||
public:
|
||||
Q_INVOKABLE Account* getAccount(ObjectId id);
|
||||
Q_INVOKABLE Account* getAccount(QString name);
|
||||
Q_INVOKABLE Asset* getAsset(ObjectId id);
|
||||
Q_INVOKABLE Asset* getAsset(QString symbol);
|
||||
|
||||
QDateTime chainTime() const;
|
||||
|
||||
ChainDataModel(){}
|
||||
ChainDataModel(fc::thread& t, QObject* parent = nullptr);
|
||||
|
||||
void setDatabaseAPI(fc::api<graphene::app::database_api> dbapi);
|
||||
void setNetworkAPI(fc::api<graphene::app::network_broadcast_api> napi);
|
||||
|
||||
const graphene::chain::global_property_object& global_properties() const { return m_global_properties; }
|
||||
const graphene::chain::dynamic_global_property_object& dynamic_global_properties() const { return m_dynamic_global_properties; }
|
||||
const graphene::chain::chain_property_object& chain_properties() const { return m_chain_properties; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void broadcast(Transaction* transaction);
|
||||
|
||||
Q_SIGNALS:
|
||||
void queueExecute(const std::function<void()>&);
|
||||
void exceptionThrown(QString message);
|
||||
void blockReceived();
|
||||
|
||||
private:
|
||||
fc::thread* m_rpc_thread = nullptr;
|
||||
std::string m_api_url;
|
||||
fc::api<graphene::app::database_api> m_db_api;
|
||||
fc::api<graphene::app::network_broadcast_api> m_net_api;
|
||||
|
||||
graphene::chain::global_property_object m_global_properties;
|
||||
graphene::chain::dynamic_global_property_object m_dynamic_global_properties;
|
||||
graphene::chain::chain_property_object m_chain_properties;
|
||||
|
||||
ObjectId m_account_query_num = -1;
|
||||
account_multi_index_type m_accounts;
|
||||
asset_multi_index_type m_assets;
|
||||
};
|
||||
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "GrapheneApplication.hpp"
|
||||
#include "ChainDataModel.hpp"
|
||||
#include "Wallet.hpp"
|
||||
#include "Operations.hpp"
|
||||
#include "Transaction.hpp"
|
||||
|
||||
#include <graphene/app/api.hpp>
|
||||
|
||||
#include <fc/rpc/websocket_api.hpp>
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
using graphene::app::login_api;
|
||||
using graphene::app::database_api;
|
||||
|
||||
GrapheneApplication::GrapheneApplication(QObject* parent)
|
||||
:QObject(parent),m_thread("app")
|
||||
{
|
||||
connect(this, &GrapheneApplication::queueExecute,
|
||||
this, &GrapheneApplication::execute);
|
||||
|
||||
m_model = new ChainDataModel(m_thread, this);
|
||||
m_operationBuilder = new OperationBuilder(*m_model, this);
|
||||
m_wallet = new Wallet(this);
|
||||
|
||||
connect(m_model, &ChainDataModel::queueExecute,
|
||||
this, &GrapheneApplication::execute);
|
||||
|
||||
connect(m_model, &ChainDataModel::exceptionThrown,
|
||||
this, &GrapheneApplication::exceptionThrown);
|
||||
}
|
||||
|
||||
GrapheneApplication::~GrapheneApplication()
|
||||
{
|
||||
}
|
||||
|
||||
void GrapheneApplication::setIsConnected(bool v)
|
||||
{
|
||||
if (v != m_isConnected)
|
||||
{
|
||||
m_isConnected = v;
|
||||
Q_EMIT isConnectedChanged(m_isConnected);
|
||||
}
|
||||
}
|
||||
|
||||
void GrapheneApplication::start(QString apiurl, QString user, QString pass)
|
||||
{
|
||||
if (!m_thread.is_current())
|
||||
{
|
||||
m_done = m_thread.async([=](){ return start(apiurl, user, pass); });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
m_client = std::make_shared<fc::http::websocket_client>();
|
||||
ilog("connecting...${s}", ("s",apiurl.toStdString()));
|
||||
auto con = m_client->connect(apiurl.toStdString());
|
||||
m_connectionClosed = con->closed.connect([this]{queueExecute([this]{setIsConnected(false);});});
|
||||
auto apic = std::make_shared<fc::rpc::websocket_api_connection>(*con);
|
||||
auto remote_api = apic->get_remote_api<login_api>(1);
|
||||
auto db_api = apic->get_remote_api<database_api>(0);
|
||||
if (!remote_api->login(user.toStdString(), pass.toStdString()))
|
||||
{
|
||||
elog("login failed");
|
||||
Q_EMIT loginFailed();
|
||||
return;
|
||||
}
|
||||
auto net_api = remote_api->network_broadcast();
|
||||
|
||||
ilog("connecting...");
|
||||
queueExecute([=](){
|
||||
m_model->setDatabaseAPI(db_api);
|
||||
m_model->setNetworkAPI(net_api);
|
||||
});
|
||||
|
||||
queueExecute([=](){ setIsConnected(true); });
|
||||
} catch (const fc::exception& e)
|
||||
{
|
||||
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
QString GrapheneApplication::defaultDataPath()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
}
|
||||
|
||||
Transaction* GrapheneApplication::createTransaction() const
|
||||
{
|
||||
return new Transaction;
|
||||
}
|
||||
|
||||
void GrapheneApplication::signTransaction(Transaction* transaction) const
|
||||
{
|
||||
if (transaction == nullptr) return;
|
||||
|
||||
auto getActiveAuth = [this](graphene::chain::account_id_type id) {
|
||||
return &model()->getAccount(id.instance.value)->accountObject().active;
|
||||
};
|
||||
auto getOwnerAuth = [this](graphene::chain::account_id_type id) {
|
||||
return &model()->getAccount(id.instance.value)->accountObject().owner;
|
||||
};
|
||||
|
||||
auto& chainId = model()->chain_properties().chain_id;
|
||||
auto& trx = transaction->internalTransaction();
|
||||
trx.set_reference_block(model()->dynamic_global_properties().head_block_id);
|
||||
flat_set<public_key_type> pubKeys = wallet()->getAvailablePrivateKeys();
|
||||
auto requiredKeys = trx.get_required_signatures(chainId, pubKeys, getActiveAuth, getOwnerAuth);
|
||||
trx.signatures = wallet()->signDigest(trx.digest(), requiredKeys);
|
||||
idump((trx));
|
||||
}
|
||||
|
||||
|
||||
Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const
|
||||
{
|
||||
func();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <fc/thread/thread.hpp>
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
|
||||
namespace fc { namespace http {
|
||||
class websocket_client;
|
||||
}}
|
||||
|
||||
class ChainDataModel;
|
||||
class OperationBuilder;
|
||||
class OperationBase;
|
||||
class Transaction;
|
||||
class Wallet;
|
||||
class GrapheneApplication : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(ChainDataModel* model READ model CONSTANT)
|
||||
Q_PROPERTY(OperationBuilder* operationBuilder READ operationBuilder CONSTANT)
|
||||
Q_PROPERTY(Wallet* wallet READ wallet CONSTANT)
|
||||
Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged)
|
||||
|
||||
|
||||
fc::thread m_thread;
|
||||
ChainDataModel* m_model = nullptr;
|
||||
Wallet* m_wallet = nullptr;
|
||||
OperationBuilder* m_operationBuilder = nullptr;
|
||||
bool m_isConnected = false;
|
||||
|
||||
boost::signals2::scoped_connection m_connectionClosed;
|
||||
|
||||
std::shared_ptr<fc::http::websocket_client> m_client;
|
||||
fc::future<void> m_done;
|
||||
|
||||
void setIsConnected(bool v);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void execute(const std::function<void()>&)const;
|
||||
|
||||
public:
|
||||
GrapheneApplication(QObject* parent = nullptr);
|
||||
~GrapheneApplication();
|
||||
|
||||
Wallet* wallet()const { return m_wallet; }
|
||||
|
||||
ChainDataModel* model() const {
|
||||
return m_model;
|
||||
}
|
||||
OperationBuilder* operationBuilder() const {
|
||||
return m_operationBuilder;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void start(QString apiUrl,
|
||||
QString user,
|
||||
QString pass);
|
||||
|
||||
bool isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
Q_INVOKABLE static QString defaultDataPath();
|
||||
|
||||
/// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction.
|
||||
Q_INVOKABLE Transaction* createTransaction() const;
|
||||
Q_INVOKABLE void signTransaction(Transaction* transaction) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void exceptionThrown(QString message);
|
||||
void loginFailed();
|
||||
void isConnectedChanged(bool isConnected);
|
||||
void queueExecute(const std::function<void()>&);
|
||||
};
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "GrapheneObject.hpp"
|
||||
|
||||
// This space intentionally left blank
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using ObjectId = qint64;
|
||||
Q_DECLARE_METATYPE(ObjectId)
|
||||
|
||||
Q_DECLARE_METATYPE(std::function<void()>)
|
||||
|
||||
class GrapheneObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(ObjectId id MEMBER m_id READ id NOTIFY idChanged)
|
||||
|
||||
ObjectId m_id;
|
||||
|
||||
public:
|
||||
GrapheneObject(ObjectId id = -1, QObject* parent = nullptr)
|
||||
: QObject(parent), m_id(id)
|
||||
{}
|
||||
|
||||
ObjectId id() const {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void idChanged();
|
||||
};
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Operations.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
TransferOperation* OperationBuilder::transfer(ObjectId sender, ObjectId receiver, qint64 amount,
|
||||
ObjectId amountType, QString memo, ObjectId feeType)
|
||||
{
|
||||
try {
|
||||
TransferOperation* op = new TransferOperation;
|
||||
op->setSender(sender);
|
||||
op->setReceiver(receiver);
|
||||
op->setAmount(amount);
|
||||
op->setAmountType(amountType);
|
||||
op->setMemo(memo);
|
||||
op->setFeeType(feeType);
|
||||
auto feeParameters = model.global_properties().parameters.current_fees->get<graphene::chain::transfer_operation>();
|
||||
op->setFee(op->operation().calculate_fee(feeParameters).value);
|
||||
return op;
|
||||
} catch (const fc::exception& e) {
|
||||
qDebug() << e.to_detail_string().c_str();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QString TransferOperation::memo() const {
|
||||
if (!m_op.memo)
|
||||
return QString::null;
|
||||
if (memoIsEncrypted())
|
||||
return tr("Encrypted Memo");
|
||||
QString memo = QString::fromStdString(m_op.memo->get_message({}, {}));
|
||||
while (memo.endsWith(QChar('\0')))
|
||||
memo.chop(1);
|
||||
return memo;
|
||||
}
|
||||
|
||||
bool TransferOperation::memoIsEncrypted() const
|
||||
{
|
||||
if (!m_op.memo)
|
||||
return false;
|
||||
if (m_op.memo->message.empty())
|
||||
return false;
|
||||
if (m_op.memo->from == public_key_type() && m_op.memo->to == public_key_type())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransferOperation::canEncryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
if (!m_op.memo) return false;
|
||||
auto pub = model->getAccount(sender())->memoKey();
|
||||
if (!wallet->hasPrivateKey(pub)) return false;
|
||||
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||
}
|
||||
|
||||
bool TransferOperation::canDecryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
if (!m_op.memo) return false;
|
||||
auto pub = model->getAccount(receiver())->memoKey();
|
||||
if (!wallet->hasPrivateKey(pub)) return false;
|
||||
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||
}
|
||||
|
||||
QString TransferOperation::decryptedMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
fc::ecc::private_key privateKey;
|
||||
fc::ecc::public_key publicKey;
|
||||
|
||||
if (canEncryptMemo(wallet, model)) {
|
||||
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||
publicKey = m_op.memo->to;
|
||||
} else if (canDecryptMemo(wallet, model)) {
|
||||
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(receiver())->memoKey()).toStdString());
|
||||
publicKey = m_op.memo->from;
|
||||
} else return QString::null;
|
||||
|
||||
return QString::fromStdString(m_op.memo->get_message(privateKey, publicKey));
|
||||
}
|
||||
|
||||
void TransferOperation::setMemo(QString memo) {
|
||||
if (memo == this->memo())
|
||||
return;
|
||||
if (!m_op.memo)
|
||||
m_op.memo = graphene::chain::memo_data();
|
||||
while (memo.size() % 32)
|
||||
memo.append('\0');
|
||||
m_op.memo->set_message({}, {}, memo.toStdString());
|
||||
Q_EMIT memoChanged();
|
||||
}
|
||||
|
||||
void TransferOperation::encryptMemo(Wallet* wallet, ChainDataModel* model)
|
||||
{
|
||||
if (!canEncryptMemo(wallet, model)) return;
|
||||
auto privateKey = graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||
if (!privateKey) return;
|
||||
m_op.memo->set_message(*privateKey, public_key_type(model->getAccount(receiver())->memoKey().toStdString()), memo().toStdString());
|
||||
Q_EMIT memoChanged();
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ChainDataModel.hpp"
|
||||
|
||||
#include <graphene/chain/protocol/transfer.hpp>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtQml>
|
||||
|
||||
class OperationBase : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(OperationType operationType READ operationType CONSTANT STORED false)
|
||||
|
||||
public:
|
||||
enum OperationType {
|
||||
TransferOperationType = graphene::chain::operation::tag<graphene::chain::transfer_operation>::value
|
||||
};
|
||||
Q_ENUM(OperationType);
|
||||
|
||||
OperationBase(QObject* parent = nullptr)
|
||||
: QObject(parent) {}
|
||||
virtual ~OperationBase() {}
|
||||
|
||||
virtual OperationType operationType() const = 0;
|
||||
virtual graphene::chain::operation genericOperation() const = 0;
|
||||
};
|
||||
QML_DECLARE_INTERFACE(OperationBase)
|
||||
|
||||
class TransferOperation : public OperationBase {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(qint64 fee READ fee WRITE setFee NOTIFY feeChanged)
|
||||
Q_PROPERTY(ObjectId feeType READ feeType WRITE setFeeType NOTIFY feeTypeChanged)
|
||||
Q_PROPERTY(ObjectId sender READ sender WRITE setSender NOTIFY senderChanged)
|
||||
Q_PROPERTY(ObjectId receiver READ receiver WRITE setReceiver NOTIFY receiverChanged)
|
||||
Q_PROPERTY(qint64 amount READ amount WRITE setAmount NOTIFY amountChanged)
|
||||
Q_PROPERTY(ObjectId amountType READ amountType WRITE setAmountType NOTIFY amountTypeChanged)
|
||||
Q_PROPERTY(QString memo READ memo WRITE setMemo NOTIFY memoChanged)
|
||||
Q_PROPERTY(bool memoIsEncrypted READ memoIsEncrypted NOTIFY memoChanged)
|
||||
|
||||
graphene::chain::transfer_operation m_op;
|
||||
|
||||
public:
|
||||
TransferOperation(){}
|
||||
TransferOperation(const graphene::chain::transfer_operation& op)
|
||||
: m_op(op) {}
|
||||
|
||||
virtual OperationBase::OperationType operationType() const override {
|
||||
return OperationBase::TransferOperationType;
|
||||
}
|
||||
virtual graphene::chain::operation genericOperation() const override {
|
||||
return m_op;
|
||||
}
|
||||
|
||||
qint64 fee() const { return m_op.fee.amount.value; }
|
||||
ObjectId feeType() const { return m_op.fee.asset_id.instance.value; }
|
||||
ObjectId sender() const { return m_op.from.instance.value; }
|
||||
ObjectId receiver() const { return m_op.to.instance.value; }
|
||||
qint64 amount() const { return m_op.amount.amount.value; }
|
||||
ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
|
||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||
/// performed by calling encryptMemo()
|
||||
QString memo() const;
|
||||
|
||||
bool memoIsEncrypted()const;
|
||||
Q_INVOKABLE bool canEncryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
Q_INVOKABLE bool canDecryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
Q_INVOKABLE QString decryptedMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
|
||||
const graphene::chain::transfer_operation& operation() const { return m_op; }
|
||||
graphene::chain::transfer_operation& operation() { return m_op; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void setFee(qint64 arg) {
|
||||
if (arg == fee())
|
||||
return;
|
||||
m_op.fee.amount = arg;
|
||||
Q_EMIT feeChanged();
|
||||
}
|
||||
void setFeeType(ObjectId arg) {
|
||||
if (arg == feeType())
|
||||
return;
|
||||
m_op.fee.asset_id = arg;
|
||||
Q_EMIT feeTypeChanged();
|
||||
}
|
||||
void setSender(ObjectId arg) {
|
||||
if (arg == sender())
|
||||
return;
|
||||
m_op.from = arg;
|
||||
Q_EMIT senderChanged();
|
||||
}
|
||||
void setReceiver(ObjectId arg) {
|
||||
if (arg == receiver())
|
||||
return;
|
||||
m_op.to = arg;
|
||||
Q_EMIT receiverChanged();
|
||||
}
|
||||
void setAmount(qint64 arg) {
|
||||
if (arg == amount())
|
||||
return;
|
||||
m_op.amount.amount = arg;
|
||||
Q_EMIT amountChanged();
|
||||
}
|
||||
void setAmountType(ObjectId arg) {
|
||||
if (arg == amountType())
|
||||
return;
|
||||
m_op.amount.asset_id = arg;
|
||||
Q_EMIT amountTypeChanged();
|
||||
}
|
||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||
/// performed by calling encryptMemo()
|
||||
void setMemo(QString memo);
|
||||
void encryptMemo(Wallet* wallet, ChainDataModel* model);
|
||||
|
||||
Q_SIGNALS:
|
||||
void feeChanged();
|
||||
void feeTypeChanged();
|
||||
void senderChanged();
|
||||
void receiverChanged();
|
||||
void amountChanged();
|
||||
void amountTypeChanged();
|
||||
void memoChanged();
|
||||
};
|
||||
QML_DECLARE_TYPE(TransferOperation)
|
||||
|
||||
/**
|
||||
* @brief The OperationBuilder class creates operations which are inspectable by the GUI
|
||||
*
|
||||
* @note All operations returned by OperationBuilder are heap allocated on-demand and do not have parents. The caller
|
||||
* must take ownership of these objects to prevent them from leaking.
|
||||
*/
|
||||
class OperationBuilder : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
ChainDataModel& model;
|
||||
|
||||
public:
|
||||
OperationBuilder(ChainDataModel& model, QObject* parent = nullptr)
|
||||
: QObject(parent), model(model){}
|
||||
|
||||
Q_INVOKABLE TransferOperation* transfer(ObjectId sender, ObjectId receiver, qint64 amount,
|
||||
ObjectId amountType, QString memo, ObjectId feeType);
|
||||
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
== Graphene Client GUI ==
|
||||
|
||||
This is a Qt-based native GUI client for Graphene blockchains.
|
||||
|
||||
To build this GUI, run cmake with -DBUILD_QT_GUI=ON
|
||||
|
||||
This GUI depends on Qt 5.5 or later. If you do not have Qt 5.5 installed
|
||||
in the canonical location on your OS (or if your OS does not have a
|
||||
canonical location for libraries), you can specify the Qt path by running
|
||||
cmake with -DCMAKE_PREFIX_PATH=/path/to/Qt/5.5/gcc_64 as appropriate for
|
||||
your environment.
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Transaction.hpp"
|
||||
#include "Operations.hpp"
|
||||
|
||||
#include <fc/reflect/typename.hpp>
|
||||
|
||||
struct OperationConverter {
|
||||
using result_type = OperationBase*;
|
||||
|
||||
OperationBase* operator()(const graphene::chain::transfer_operation& op) const {
|
||||
auto ret = new TransferOperation(op);
|
||||
QObject::connect(ret, &QObject::destroyed, []{qDebug("Cleaned up operation");});
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename Op>
|
||||
OperationBase* operator()(const Op&) const {
|
||||
elog("NYI: OperationConverter for ${type}", ("type", fc::get_typename<Op>::name()));
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
QString Transaction::statusString() const
|
||||
{
|
||||
return QMetaEnum::fromType<Status>().valueToKey(status());
|
||||
}
|
||||
|
||||
QQmlListProperty<OperationBase> Transaction::operations()
|
||||
{
|
||||
auto append = [](QQmlListProperty<OperationBase>* list, OperationBase* op) {
|
||||
static_cast<Transaction*>(list->data)->appendOperation(op);
|
||||
};
|
||||
auto count = [](QQmlListProperty<OperationBase>* list) {
|
||||
return static_cast<Transaction*>(list->data)->operationCount();
|
||||
};
|
||||
auto at = [](QQmlListProperty<OperationBase>* list, int index) {
|
||||
return static_cast<Transaction*>(list->data)->operationAt(index);
|
||||
};
|
||||
auto clear = [](QQmlListProperty<OperationBase>* list) {
|
||||
static_cast<Transaction*>(list->data)->clearOperations();
|
||||
};
|
||||
|
||||
return QQmlListProperty<OperationBase>(this, this, append, count, at, clear);
|
||||
}
|
||||
|
||||
OperationBase* Transaction::operationAt(int index) const {
|
||||
return m_transaction.operations[index].visit(OperationConverter());
|
||||
}
|
||||
|
||||
void Transaction::appendOperation(OperationBase* op)
|
||||
{
|
||||
if (op == nullptr)
|
||||
{
|
||||
qWarning("Unable to append null operation to transaction");
|
||||
return;
|
||||
}
|
||||
op->setParent(this);
|
||||
m_transaction.operations.push_back(op->genericOperation());
|
||||
Q_EMIT operationsChanged();
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <graphene/chain/protocol/transaction.hpp>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QQmlListProperty>
|
||||
#include <QDebug>
|
||||
|
||||
class OperationBase;
|
||||
class Transaction : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Status { Unbroadcasted, Pending, Complete, Failed };
|
||||
Q_ENUM(Status);
|
||||
|
||||
Status status() const { return m_status; }
|
||||
QString statusString() const;
|
||||
QQmlListProperty<OperationBase> operations();
|
||||
|
||||
OperationBase* operationAt(int index) const;
|
||||
int operationCount() const {
|
||||
return m_transaction.operations.size();
|
||||
}
|
||||
|
||||
graphene::chain::signed_transaction& internalTransaction() {
|
||||
return m_transaction;
|
||||
}
|
||||
|
||||
QDateTime expiration() const
|
||||
{
|
||||
return QDateTime::fromTime_t(m_transaction.expiration.sec_since_epoch());
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void setStatus(Status status)
|
||||
{
|
||||
if (status == m_status)
|
||||
return;
|
||||
|
||||
m_status = status;
|
||||
qDebug() << status;
|
||||
emit statusChanged(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append the operation to the transaction
|
||||
* @param op The operation to append. This Transaction will take ownership of the operation.
|
||||
*/
|
||||
void appendOperation(OperationBase* op);
|
||||
void clearOperations() {
|
||||
m_transaction.operations.clear();
|
||||
Q_EMIT operationsChanged();
|
||||
}
|
||||
|
||||
void setExpiration(QDateTime expiration)
|
||||
{
|
||||
fc::time_point_sec exp(expiration.toTime_t());
|
||||
if (exp == m_transaction.expiration)
|
||||
return;
|
||||
|
||||
m_transaction.expiration = exp;
|
||||
emit expirationChanged(expiration);
|
||||
}
|
||||
|
||||
signals:
|
||||
void statusChanged(Status status);
|
||||
void operationsChanged();
|
||||
|
||||
void expirationChanged(QDateTime expiration);
|
||||
|
||||
private:
|
||||
Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged)
|
||||
Q_PROPERTY(QString statusString READ statusString NOTIFY statusChanged STORED false)
|
||||
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
|
||||
Q_PROPERTY(QDateTime expiration READ expiration WRITE setExpiration NOTIFY expirationChanged)
|
||||
|
||||
Status m_status = Unbroadcasted;
|
||||
graphene::chain::signed_transaction m_transaction;
|
||||
};
|
||||
|
|
@ -1,480 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include "Transaction.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
||||
#include <fc/crypto/aes.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
QString toQString(public_key_type k) { return QString::fromStdString(fc::variant(k).as_string()); }
|
||||
|
||||
Wallet::Wallet(QObject* parent)
|
||||
:QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Wallet::~Wallet()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool Wallet::open(QString file_path)
|
||||
{
|
||||
fc::path p(file_path.toStdString());
|
||||
if( !fc::exists(p) )
|
||||
{
|
||||
ilog("Unable to open wallet file '${f}'; it does not exist", ("f",p));
|
||||
return false;
|
||||
}
|
||||
|
||||
_data = fc::json::from_file(p).as<wallet_file>();
|
||||
|
||||
for( const auto& key : _data.encrypted_private_keys )
|
||||
{
|
||||
if( key.second.label != string() )
|
||||
_label_to_key[QString::fromStdString(key.second.label)] = toQString(key.first);
|
||||
if( key.second.encrypted_private_key.size() )
|
||||
_available_private_keys.insert(key.first);
|
||||
}
|
||||
_wallet_file_path = p;
|
||||
|
||||
Q_EMIT isOpenChanged(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::isOpen()const
|
||||
{
|
||||
return _wallet_file_path != fc::path();
|
||||
}
|
||||
|
||||
bool Wallet::close()
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
save();
|
||||
_wallet_file_path = fc::path();
|
||||
Q_EMIT isOpenChanged(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Wallet::save()
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
/// TODO: backup existing wallet file first.
|
||||
fc::json::save_to_file(_data, _wallet_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Wallet::saveAs(QString file_path)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
fc::path p(file_path.toStdString());
|
||||
if( fc::exists(p) ) return false;
|
||||
fc::json::save_to_file(_data, _wallet_file_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::create(QString file_path, QString password, QString brain_key)
|
||||
{
|
||||
if( isOpen() ) return false;
|
||||
if( password == QString() ) return false;
|
||||
|
||||
fc::path p(file_path.toStdString());
|
||||
if( fc::exists(p) )
|
||||
{
|
||||
ilog("Unable to create wallet file '${f}' because a file with that name already exists.", ("f",p));
|
||||
return false;
|
||||
}
|
||||
|
||||
if( brain_key == QString() )
|
||||
{
|
||||
auto key = fc::ecc::private_key::generate().get_secret();
|
||||
brain_key.fromStdString( fc::variant(key).as_string() );
|
||||
}
|
||||
auto brainkey = brain_key.toStdString();
|
||||
auto pw_str = password.toStdString();
|
||||
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
_decrypted_master_key = fc::sha512::hash(fc::ecc::private_key::generate().get_secret());
|
||||
_data.master_key_digest = fc::sha512::hash(_decrypted_master_key);
|
||||
_data.encrypted_master_key = fc::aes_encrypt(password_hash, fc::raw::pack(_decrypted_master_key));
|
||||
|
||||
_data.brain_key_digest = fc::sha512::hash(brainkey.c_str(), brainkey.size());
|
||||
_data.encrypted_brain_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(brainkey));
|
||||
|
||||
QFileInfo(file_path).absoluteDir().mkpath(".");
|
||||
fc::json::save_to_file(_data, p);
|
||||
_wallet_file_path = p;
|
||||
ilog("Created wallet file '${f}'", ("f", p));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::loadBrainKey(QString brain_key)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
|
||||
if( brain_key == QString() ) return false;
|
||||
auto brainkey = brain_key.toStdString();
|
||||
|
||||
if( _data.brain_key_digest != fc::sha512::hash(brainkey.c_str(), brainkey.size()) )
|
||||
return false;
|
||||
|
||||
_brain_key = brain_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::purgeBrainKey()
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
_data.encrypted_brain_key.resize(0);
|
||||
_brain_key = QString();
|
||||
return save();
|
||||
}
|
||||
|
||||
bool Wallet::hasBrainKey()const
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
return _brain_key != QString() || _data.encrypted_brain_key.size();
|
||||
}
|
||||
|
||||
QString Wallet::getBrainKey()
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
||||
if( _brain_key != QString() )
|
||||
return _brain_key;
|
||||
|
||||
auto dec_brain_key = fc::aes_decrypt(_decrypted_master_key, _data.encrypted_brain_key);
|
||||
auto dec_brain_key_str = fc::raw::unpack<string>(dec_brain_key);
|
||||
_brain_key.fromStdString(dec_brain_key_str);
|
||||
return _brain_key;
|
||||
}
|
||||
|
||||
bool Wallet::isLocked()const
|
||||
{
|
||||
if( !isOpen() ) return true;
|
||||
return _decrypted_master_key == fc::sha512();
|
||||
}
|
||||
bool Wallet::unlock(QString password)
|
||||
{
|
||||
try {
|
||||
if( !isLocked() ) return true;
|
||||
auto pw_str = password.toStdString();
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
auto plain_txt = fc::aes_decrypt(password_hash, _data.encrypted_master_key);
|
||||
_decrypted_master_key = fc::raw::unpack<fc::sha512>(plain_txt);
|
||||
if( _data.master_key_digest != fc::sha512::hash(_decrypted_master_key) )
|
||||
_decrypted_master_key = fc::sha512();
|
||||
|
||||
Q_EMIT isLockedChanged(isLocked());
|
||||
return !isLocked();
|
||||
} catch (const fc::exception& e) {
|
||||
elog(e.to_detail_string());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Wallet::lock()
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
_brain_key = QString();
|
||||
_decrypted_master_key = fc::sha512();
|
||||
|
||||
Q_EMIT isLockedChanged(isLocked());
|
||||
return true;
|
||||
}
|
||||
bool Wallet::changePassword(QString new_password)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto pw_str = new_password.toStdString();
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
_data.encrypted_master_key = fc::aes_encrypt(password_hash, fc::raw::pack(_decrypted_master_key));
|
||||
|
||||
save();
|
||||
|
||||
return true;
|
||||
}
|
||||
bool Wallet::hasPrivateKey(QString pubkey, bool include_with_brain_key)
|
||||
{
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find(pub);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return false;
|
||||
if( itr->second.encrypted_private_key.size() )
|
||||
return true;
|
||||
if( include_with_brain_key && itr->second.brain_sequence >= 0 )
|
||||
{
|
||||
if( !itr->second.owner )
|
||||
return true;
|
||||
return hasPrivateKey(toQString(*itr->second.owner), include_with_brain_key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Wallet::getPrivateKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find(pub);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return QString();
|
||||
if( itr->second.encrypted_private_key.size() == 0 )
|
||||
return QString();
|
||||
auto plain = fc::aes_decrypt(_decrypted_master_key, itr->second.encrypted_private_key);
|
||||
return QString::fromStdString(fc::raw::unpack<std::string>(plain));
|
||||
}
|
||||
|
||||
QString Wallet::getPublicKey(QString wif_private_key)const
|
||||
{
|
||||
auto priv = graphene::utilities::wif_to_key(wif_private_key.toStdString());
|
||||
if( !priv ) return QString();
|
||||
|
||||
auto pub = public_key_type(priv->get_public_key());
|
||||
|
||||
return QString::fromStdString(fc::variant(pub).as_string());
|
||||
}
|
||||
|
||||
QString Wallet::getActivePrivateKey(QString owner_pub_key, uint32_t seq)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
||||
auto owner_wif_private_key = getPrivateKey(owner_pub_key);
|
||||
if( owner_wif_private_key == QString() ) return QString();
|
||||
|
||||
auto seed = (owner_wif_private_key + " " + QString::number(seq)).toStdString();
|
||||
auto secret = fc::sha256::hash(fc::sha512::hash(seed.c_str(), seed.size()));
|
||||
|
||||
auto wif = graphene::utilities::key_to_wif(secret);
|
||||
auto priv_key = graphene::utilities::wif_to_key(wif);
|
||||
if( !priv_key ) return QString();
|
||||
|
||||
public_key_type active_pub_key(priv_key->get_public_key());
|
||||
_data.encrypted_private_keys[active_pub_key].encrypted_private_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(wif));
|
||||
_data.encrypted_private_keys[active_pub_key].owner = fc::variant(owner_pub_key.toStdString()).as<public_key_type>();
|
||||
_data.encrypted_private_keys[active_pub_key].brain_sequence = seq;
|
||||
_available_private_keys.insert( active_pub_key );
|
||||
|
||||
return QString::fromStdString(wif);
|
||||
}
|
||||
|
||||
QString Wallet::getOwnerPrivateKey(uint32_t seq)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
if( !hasBrainKey() ) return QString();
|
||||
|
||||
auto seed = (getBrainKey() + " " + QString::number(seq)).toStdString();
|
||||
|
||||
auto secret = fc::sha256::hash(fc::sha512::hash(seed.c_str(), seed.size()));
|
||||
|
||||
auto wif = graphene::utilities::key_to_wif(secret);
|
||||
auto priv_key = graphene::utilities::wif_to_key(wif);
|
||||
if( !priv_key ) return QString();
|
||||
|
||||
public_key_type owner_pub_key(priv_key->get_public_key());
|
||||
_data.encrypted_private_keys[owner_pub_key].encrypted_private_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(wif));
|
||||
_data.encrypted_private_keys[owner_pub_key].brain_sequence = seq;
|
||||
_available_private_keys.insert(owner_pub_key);
|
||||
|
||||
return QString::fromStdString(wif);
|
||||
}
|
||||
|
||||
QString Wallet::getActivePublicKey(QString active_pub, uint32_t seq)
|
||||
{
|
||||
return getPublicKey(getActivePrivateKey(active_pub, seq));
|
||||
}
|
||||
|
||||
QString Wallet::getOwnerPublicKey(uint32_t seq)
|
||||
{
|
||||
return getPublicKey(getOwnerPrivateKey(seq));
|
||||
}
|
||||
|
||||
|
||||
QString Wallet::getKeyLabel(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
public_key_type key = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find(key);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return QString();
|
||||
return QString::fromStdString(itr->second.label);
|
||||
}
|
||||
/**
|
||||
* The same label may not be assigned to more than one key, this method will
|
||||
* fail if a key with the same label already exists.
|
||||
*
|
||||
* @return true if the label was set
|
||||
*/
|
||||
bool Wallet::setKeyLabel(QString pubkey, QString label)
|
||||
{
|
||||
if( label == QString() ) // clear the label
|
||||
{
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
auto old_label = _data.encrypted_private_keys[pub].label;
|
||||
_data.encrypted_private_keys[pub].label = string();
|
||||
if( old_label.size() )
|
||||
_label_to_key.erase(QString::fromStdString(old_label));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto itr = _label_to_key.find(label);
|
||||
if( itr != _label_to_key.end() )
|
||||
return false;
|
||||
|
||||
_label_to_key[label] = pubkey;
|
||||
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
_data.encrypted_private_keys[pub].label = label.toStdString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QList<QPair<QString,QString>> Wallet::getAllPublicKeys(bool only_if_private)const
|
||||
{
|
||||
QList< QPair<QString,QString>> result;
|
||||
if( !isOpen() ) return result;
|
||||
|
||||
for( const auto& item : _data.encrypted_private_keys )
|
||||
{
|
||||
if( only_if_private && !item.second.encrypted_private_key.size() ) continue;
|
||||
result.push_back(qMakePair(toQString(item.first), QString::fromStdString(item.second.label)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Wallet::getPublicKey(QString label)
|
||||
{
|
||||
if( !isOpen() ) return QString::null;
|
||||
|
||||
auto itr = _label_to_key.find(label);
|
||||
if( itr != _label_to_key.end() )
|
||||
return QString::null;
|
||||
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
/** imports a public key and assigns it a label */
|
||||
bool Wallet::importPublicKey(QString pubkey, QString label)
|
||||
{
|
||||
return setKeyLabel(pubkey, label);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param wifkey a private key in (WIF) Wallet Import Format
|
||||
* @pre !isLocked()
|
||||
**/
|
||||
bool Wallet::importPrivateKey(QString wifkey, QString label)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto priv = graphene::utilities::wif_to_key(wifkey.toStdString());
|
||||
if (!priv) return false;
|
||||
|
||||
auto p = priv->get_public_key();
|
||||
auto pub = toQString(p);
|
||||
importPublicKey(pub, label);
|
||||
|
||||
_data.encrypted_private_keys[p].encrypted_private_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(wifkey.toStdString()));
|
||||
_available_private_keys.insert(p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** removes the key, its lablel and its private key */
|
||||
bool Wallet::removePublicKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
_available_private_keys.erase(pub);
|
||||
|
||||
auto itr = _data.encrypted_private_keys.find(pub);
|
||||
if( itr != _data.encrypted_private_keys.end() )
|
||||
{
|
||||
_label_to_key.erase(QString::fromStdString(itr->second.label));
|
||||
_data.encrypted_private_keys.erase(itr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** removes only the private key, keeping the public key and label
|
||||
*
|
||||
* @pre isOpen() && !isLocked()
|
||||
**/
|
||||
bool Wallet::removePrivateKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
_data.encrypted_private_keys[pub].encrypted_private_key.resize(0);
|
||||
_available_private_keys.erase(pub);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pre !isLocked()
|
||||
*/
|
||||
vector<signature_type> Wallet::signDigest(const digest_type& d, const set<public_key_type>& keys)const
|
||||
{
|
||||
vector<signature_type> result;
|
||||
if( !isOpen() ) return result;
|
||||
if( isLocked() ) return result;
|
||||
|
||||
result.reserve( keys.size() );
|
||||
|
||||
for( const auto& key : keys )
|
||||
{
|
||||
auto itr = _data.encrypted_private_keys.find(key);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return vector<signature_type>();
|
||||
if( itr->second.encrypted_private_key.size() == 0 )
|
||||
return vector<signature_type>();
|
||||
|
||||
auto plain_wif = fc::aes_decrypt(_decrypted_master_key, itr->second.encrypted_private_key);
|
||||
auto wif = fc::raw::unpack<std::string>(plain_wif);
|
||||
auto priv = graphene::utilities::wif_to_key(wif);
|
||||
if( !priv ) return vector<signature_type>();
|
||||
|
||||
result.push_back(priv->sign_compact(d));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const flat_set<public_key_type>& Wallet::getAvailablePrivateKeys()const
|
||||
{
|
||||
return _available_private_keys;
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QtQml>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::set;
|
||||
using fc::flat_set;
|
||||
using std::map;
|
||||
using graphene::chain::public_key_type;
|
||||
using graphene::chain::digest_type;
|
||||
using graphene::chain::signature_type;
|
||||
using fc::optional;
|
||||
|
||||
class Transaction;
|
||||
|
||||
QString toQString(public_key_type k);
|
||||
|
||||
struct key_data
|
||||
{
|
||||
string label; /** unique label assigned to this key */
|
||||
/** encrypted as a packed std::string containing a wif private key */
|
||||
vector<char> encrypted_private_key;
|
||||
int32_t brain_sequence = -1;
|
||||
optional<public_key_type> owner; /// if this key was derived from an owner key + sequence
|
||||
};
|
||||
|
||||
struct wallet_file
|
||||
{
|
||||
vector<char> encrypted_brain_key;
|
||||
fc::sha512 brain_key_digest;
|
||||
vector<char> encrypted_master_key;
|
||||
fc::sha512 master_key_digest;
|
||||
map<public_key_type, key_data> encrypted_private_keys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Wallet
|
||||
* @brief Securely maintains a set of labeled public and private keys
|
||||
*
|
||||
* @note this API is designed for use with QML which does not support exceptions so
|
||||
* all errors are passed via return values.
|
||||
*/
|
||||
class Wallet : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool isOpen READ isOpen NOTIFY isOpenChanged)
|
||||
Q_PROPERTY(bool isLocked READ isLocked NOTIFY isLockedChanged)
|
||||
public:
|
||||
Wallet( QObject* parent = nullptr );
|
||||
~Wallet();
|
||||
|
||||
Q_INVOKABLE bool open(QString file_path);
|
||||
Q_INVOKABLE bool close();
|
||||
bool isOpen()const;
|
||||
Q_INVOKABLE bool save();
|
||||
Q_INVOKABLE bool saveAs(QString file_path);
|
||||
Q_INVOKABLE bool create(QString file_path, QString password, QString brain_key = QString());
|
||||
|
||||
/** required to generate new owner keys */
|
||||
Q_INVOKABLE bool loadBrainKey(QString brain_key);
|
||||
|
||||
/** removes brain key to secure owner keys */
|
||||
Q_INVOKABLE bool purgeBrainKey();
|
||||
Q_INVOKABLE bool hasBrainKey()const;
|
||||
|
||||
/** @pre hasBrainKey() */
|
||||
Q_INVOKABLE QString getBrainKey();
|
||||
|
||||
bool isLocked()const;
|
||||
Q_INVOKABLE bool unlock(QString password);
|
||||
Q_INVOKABLE bool lock();
|
||||
Q_INVOKABLE bool changePassword(QString new_password);
|
||||
|
||||
/**
|
||||
* @pre !isLocked();
|
||||
* @post save()
|
||||
* @return WIF private key
|
||||
*/
|
||||
Q_INVOKABLE QString getActivePrivateKey(QString owner_public_key, uint32_t sequence);
|
||||
Q_INVOKABLE QString getActivePublicKey(QString owner_public_key, uint32_t sequence);
|
||||
|
||||
/**
|
||||
* @pre !isLocked();
|
||||
* @pre hasBrainKey();
|
||||
* @post save()
|
||||
* @return WIF private key
|
||||
*/
|
||||
Q_INVOKABLE QString getOwnerPrivateKey(uint32_t sequence);
|
||||
Q_INVOKABLE QString getOwnerPublicKey(uint32_t sequence);
|
||||
Q_INVOKABLE QString getPublicKey(QString wif_private_key)const;
|
||||
|
||||
Q_INVOKABLE QString getKeyLabel(QString pubkey);
|
||||
Q_INVOKABLE bool setKeyLabel(QString pubkey, QString label);
|
||||
Q_INVOKABLE QString getPublicKey(QString label);
|
||||
Q_INVOKABLE QString getPrivateKey(QString pubkey);
|
||||
Q_INVOKABLE bool hasPrivateKey(QString pubkey, bool include_with_brain_key = false);
|
||||
|
||||
/** imports a public key and assigns it a label */
|
||||
Q_INVOKABLE bool importPublicKey(QString pubkey, QString label = QString());
|
||||
|
||||
/**
|
||||
* @param wifkey a private key in (WIF) Wallet Import Format
|
||||
* @pre !isLocked()
|
||||
**/
|
||||
Q_INVOKABLE bool importPrivateKey(QString wifkey, QString label = QString());
|
||||
|
||||
/** removes the key, its lablel and its private key */
|
||||
Q_INVOKABLE bool removePublicKey(QString pubkey);
|
||||
|
||||
/** removes only the private key, keeping the public key and label */
|
||||
Q_INVOKABLE bool removePrivateKey(QString pubkey);
|
||||
|
||||
/**
|
||||
* @param only_if_private filter any public keys for which the wallet lacks a private key
|
||||
* @return a list of PUBLICKEY, LABEL for all known public keys
|
||||
*/
|
||||
Q_INVOKABLE QList<QPair<QString,QString>> getAllPublicKeys(bool only_if_private)const;
|
||||
|
||||
/**
|
||||
* @pre !isLocked()
|
||||
*/
|
||||
vector<signature_type> signDigest(const digest_type& d,
|
||||
const set<public_key_type>& keys)const;
|
||||
|
||||
const flat_set<public_key_type>& getAvailablePrivateKeys()const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void isLockedChanged(bool state);
|
||||
void isOpenChanged(bool state);
|
||||
|
||||
private:
|
||||
fc::path _wallet_file_path;
|
||||
wallet_file _data;
|
||||
/** used to decrypt all of the encrypted private keys */
|
||||
fc::sha512 _decrypted_master_key;
|
||||
flat_set<public_key_type> _available_private_keys;
|
||||
map<QString,QString> _label_to_key;
|
||||
QString _brain_key;
|
||||
};
|
||||
QML_DECLARE_TYPE(Wallet)
|
||||
|
||||
FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) )
|
||||
FC_REFLECT( wallet_file,
|
||||
(encrypted_brain_key)
|
||||
(brain_key_digest)
|
||||
(encrypted_master_key)
|
||||
(master_key_digest)
|
||||
(encrypted_private_keys)
|
||||
)
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Any modified source or binaries are used only with the BitShares network.
|
||||
*
|
||||
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
#include <QApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QtQml>
|
||||
|
||||
#include "GrapheneApplication.hpp"
|
||||
#include "ChainDataModel.hpp"
|
||||
#include "Transaction.hpp"
|
||||
#include "Operations.hpp"
|
||||
#include "Balance.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
class Crypto {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
Q_INVOKABLE QString sha256(QByteArray data) {
|
||||
return QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex();
|
||||
}
|
||||
};
|
||||
QML_DECLARE_TYPE(Crypto)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
QQmlDebuggingEnabler enabler;
|
||||
#endif
|
||||
fc::thread::current().set_name("main");
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationName("Graphene Client");
|
||||
app.setOrganizationDomain("cryptonomex.org");
|
||||
app.setOrganizationName("Cryptonomex, Inc.");
|
||||
|
||||
qRegisterMetaType<std::function<void()>>();
|
||||
qRegisterMetaType<ObjectId>();
|
||||
qRegisterMetaType<QList<OperationBase*>>();
|
||||
qRegisterMetaType<Transaction::Status>();
|
||||
|
||||
qmlRegisterType<Asset>("Graphene.Client", 0, 1, "Asset");
|
||||
qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance");
|
||||
qmlRegisterType<Account>("Graphene.Client", 0, 1, "Account");
|
||||
qmlRegisterType<ChainDataModel>("Graphene.Client", 0, 1, "DataModel");
|
||||
qmlRegisterType<Wallet>("Graphene.Client", 0, 1, "Wallet");
|
||||
qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication");
|
||||
qmlRegisterType<Transaction>("Graphene.Client", 0, 1, "Transaction");
|
||||
|
||||
qmlRegisterUncreatableType<OperationBase>("Graphene.Client", 0, 1, "OperationBase",
|
||||
"OperationBase is an abstract base class; cannot be created");
|
||||
qmlRegisterType<TransferOperation>("Graphene.Client", 0, 1, "TransferOperation");
|
||||
|
||||
qmlRegisterUncreatableType<OperationBuilder>("Graphene.Client", 0, 1, "OperationBuilder",
|
||||
QStringLiteral("OperationBuilder cannot be created from QML"));
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
QVariant crypto;
|
||||
crypto.setValue(Crypto());
|
||||
engine.rootContext()->setContextProperty("Crypto", crypto);
|
||||
#ifdef NDEBUG
|
||||
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
|
||||
#else
|
||||
engine.load(QUrl(QStringLiteral("qml/main.qml")));
|
||||
QFileSystemWatcher watcher;
|
||||
qDebug() << watcher.addPath("qml/");
|
||||
QObject::connect(&watcher, &QFileSystemWatcher::directoryChanged, &engine, [&](QString path) {
|
||||
qDebug() << "Changed file" << path;
|
||||
engine.clearComponentCache();
|
||||
});
|
||||
#endif
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
||||
1
programs/light_client/qml/.gitignore
vendored
1
programs/light_client/qml/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
*.depends
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
/// A component for choosing an Account from the chain
|
||||
RowLayout {
|
||||
property GrapheneApplication app
|
||||
/// The text to show in the name field when it is empty
|
||||
property alias placeholderText: accountNameField.placeholderText
|
||||
/// Index into the balances Array of the balance to show beneath the name field
|
||||
property int showBalance: -1
|
||||
|
||||
/// The Account object the user has selected
|
||||
property Account account
|
||||
/// A real in the range [0,1] representing the amount of control the wallet has over this account
|
||||
property real accountControlLevel: account && account.isLoaded? account.getActiveControl(app.wallet) : 0
|
||||
/// An Array of Balance objects held by account
|
||||
property var balances: account? Object.keys(account.balances).map(function(key){return account.balances[key]})
|
||||
.filter(function(balance){return balance.amount > 0})
|
||||
: null
|
||||
|
||||
/// Set the name field to have active focus
|
||||
function setFocus() {
|
||||
accountNameField.forceActiveFocus()
|
||||
}
|
||||
|
||||
signal balanceClicked(var balance)
|
||||
|
||||
Identicon {
|
||||
name: account && account.name == accountNameField.text? accountNameField.text : ""
|
||||
width: Scaling.cm(2)
|
||||
height: Scaling.cm(2)
|
||||
showOwnership: accountControlLevel > 0
|
||||
fullOwnership: accountControlLevel >= 1
|
||||
}
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
TextField {
|
||||
id: accountNameField
|
||||
width: parent.width
|
||||
|
||||
onEditingFinished: accountDetails.update(text)
|
||||
onTextChanged: if (account && account.name !== text) accountDetails.update("")
|
||||
}
|
||||
Text {
|
||||
id: accountDetails
|
||||
width: parent.width
|
||||
height: text? implicitHeight : 0
|
||||
onLinkActivated: if (link === "balance") balanceClicked(balances[showBalance].amountReal())
|
||||
|
||||
Behavior on height { NumberAnimation{ easing.type: Easing.InOutQuad } }
|
||||
|
||||
function update(name) {
|
||||
if (!name)
|
||||
{
|
||||
text = ""
|
||||
account = null
|
||||
return
|
||||
}
|
||||
|
||||
account = app.model.getAccount(name)
|
||||
if (account == null) {
|
||||
text = qsTr("Error fetching account.")
|
||||
} else {
|
||||
text = Qt.binding(function() {
|
||||
if (account == null)
|
||||
return qsTr("Account does not exist.")
|
||||
var text = qsTr("Account ID: %1").arg(!account.isLoaded? qsTr("Loading...")
|
||||
: account.id)
|
||||
if (showBalance >= 0) {
|
||||
var bal = balances[showBalance]
|
||||
text += "<br/>" + qsTr("Balance: <a href='balance'>%1</a> %2").arg(String(bal.amountReal()))
|
||||
.arg(bal.type.symbol)
|
||||
}
|
||||
return text
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on text {
|
||||
SequentialAnimation {
|
||||
PropertyAnimation {
|
||||
target: accountDetails
|
||||
property: "opacity"
|
||||
from: 1; to: 0
|
||||
duration: 100
|
||||
}
|
||||
PropertyAction { target: accountDetails; property: "text" }
|
||||
PropertyAnimation {
|
||||
target: accountDetails
|
||||
property: "opacity"
|
||||
from: 0; to: 1
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
/**
|
||||
* Base for all forms
|
||||
*
|
||||
* This base contains all of the properties, slots, and signals which all forms are expected to expose. It also
|
||||
* automatically lays its children out in a ColumnLayout
|
||||
*/
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
/// Reference to the GrapheneApplication object
|
||||
property GrapheneApplication app
|
||||
/// Parent should trigger this signal to notify the form that it is about to be displayed
|
||||
/// See specific form for the argument semantics
|
||||
signal display(var arg)
|
||||
/// Emitted when the form is canceled -- see specific form for the argument semantics
|
||||
signal canceled(var arg)
|
||||
/// Emitted when the form is completed -- see specific form for the argument semantics
|
||||
signal completed(var arg)
|
||||
|
||||
default property alias childItems: childLayout.data
|
||||
|
||||
ColumnLayout {
|
||||
id: childLayout
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Scaling.cm(2)
|
||||
spacing: Scaling.mm(5)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
Rectangle {
|
||||
id: greySheet
|
||||
state: "HIDDEN"
|
||||
color: Qt.rgba(0, 0, 0, showOpacity)
|
||||
|
||||
property real showOpacity: .5
|
||||
property int animationTime: 300
|
||||
|
||||
/// Emitted immediately when opening, before fade-in animation
|
||||
signal opening
|
||||
/// Emitted when opened, following fade-in animation
|
||||
signal opened
|
||||
/// Emitted immediately when closing, before fade-out animation
|
||||
signal closing
|
||||
/// Emitted when closed, following fade-out animation
|
||||
signal closed
|
||||
|
||||
function showForm(formType, params, closedCallback) {
|
||||
if (formType.status === Component.Error)
|
||||
console.log(formType.errorString())
|
||||
if (!params instanceof Object)
|
||||
params = {app: app}
|
||||
else
|
||||
params.app = app
|
||||
|
||||
var form = formType.createObject(formContainer, params)
|
||||
formContainer.data = [form]
|
||||
form.canceled.connect(function(){state = "HIDDEN"; internal.callbackArgs = []})
|
||||
form.completed.connect(function(){state = "HIDDEN"; internal.callbackArgs = arguments})
|
||||
if (closedCallback instanceof Function)
|
||||
internal.callback = closedCallback
|
||||
// Notify the form that it's about to go live
|
||||
form.display({})
|
||||
state = "SHOWN"
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: scope
|
||||
anchors.fill: parent
|
||||
|
||||
// Do not let focus leave this scope while form is open
|
||||
onFocusChanged: if (enabled && !focus) forceActiveFocus()
|
||||
|
||||
Keys.onEscapePressed: greySheet.state = "HIDDEN"
|
||||
|
||||
MouseArea {
|
||||
id: mouseTrap
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
mouse.accepted = true
|
||||
greySheet.state = "HIDDEN"
|
||||
}
|
||||
acceptedButtons: Qt.AllButtons
|
||||
}
|
||||
MouseArea {
|
||||
// This mouse area blocks clicks inside the form from reaching the mouseTrap
|
||||
anchors.fill: formContainer
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onClicked: mouse.accepted = true
|
||||
}
|
||||
Item {
|
||||
id: formContainer
|
||||
anchors.centerIn: parent
|
||||
width: parent.width / 2
|
||||
height: parent.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "HIDDEN"
|
||||
PropertyChanges {
|
||||
target: greySheet
|
||||
opacity: 0
|
||||
enabled: false
|
||||
}
|
||||
StateChangeScript {
|
||||
name: "preHidden"
|
||||
script: {
|
||||
greySheet.closing()
|
||||
}
|
||||
}
|
||||
StateChangeScript {
|
||||
name: "postHidden"
|
||||
script: {
|
||||
greySheet.closed()
|
||||
formContainer.data = []
|
||||
if (internal.callback instanceof Function)
|
||||
internal.callback.apply(this, internal.callbackArgs)
|
||||
internal.callback = undefined
|
||||
internal.callbackArgs = []
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "SHOWN"
|
||||
PropertyChanges {
|
||||
target: greySheet
|
||||
opacity: 1
|
||||
enabled: true
|
||||
}
|
||||
StateChangeScript {
|
||||
name: "preShown"
|
||||
script: {
|
||||
greySheet.opening()
|
||||
}
|
||||
}
|
||||
StateChangeScript {
|
||||
name: "postShown"
|
||||
script: {
|
||||
greySheet.opened()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "HIDDEN"
|
||||
to: "SHOWN"
|
||||
SequentialAnimation {
|
||||
ScriptAction { scriptName: "preShown" }
|
||||
PropertyAnimation {
|
||||
target: greySheet
|
||||
property: "opacity"
|
||||
duration: animationTime
|
||||
}
|
||||
ScriptAction { scriptName: "postShown" }
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "SHOWN"
|
||||
to: "HIDDEN"
|
||||
SequentialAnimation {
|
||||
ScriptAction { scriptName: "preHidden" }
|
||||
PropertyAnimation {
|
||||
target: greySheet
|
||||
property: "opacity"
|
||||
duration: animationTime
|
||||
}
|
||||
ScriptAction { scriptName: "postHidden" }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
QtObject {
|
||||
id: internal
|
||||
property var callback
|
||||
property var callbackArgs
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
Flipable {
|
||||
id: flipable
|
||||
anchors.fill: parent
|
||||
|
||||
property Component frontComponent
|
||||
property Component backComponent
|
||||
|
||||
signal display(var arg)
|
||||
signal canceled(var arg)
|
||||
signal completed(var arg)
|
||||
|
||||
property bool flipped: false
|
||||
|
||||
Component.onCompleted: {
|
||||
back = backComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return rotation.angle > 90})})
|
||||
front = frontComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return rotation.angle < 90})})
|
||||
front.canceled.connect(function() { canceled.apply(this, arguments) })
|
||||
front.completed.connect(function() {
|
||||
back.display.apply(this, arguments)
|
||||
flipped = true
|
||||
})
|
||||
back.canceled.connect(function() {
|
||||
front.display.apply(this, arguments)
|
||||
flipped = false
|
||||
})
|
||||
back.completed.connect(function() { completed.apply(this, arguments) })
|
||||
}
|
||||
|
||||
transform: Rotation {
|
||||
id: rotation
|
||||
origin.x: flipable.width/2
|
||||
origin.y: flipable.height/2
|
||||
// set axis.y to 1 to rotate around y-axis
|
||||
axis.x: 0; axis.y: 1; axis.z: 0
|
||||
// the default angle
|
||||
angle: 0
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "back"
|
||||
PropertyChanges { target: rotation; angle: 180 }
|
||||
when: flipable.flipped
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
NumberAnimation { target: rotation; property: "angle"; duration: 500 }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
import "jdenticon/jdenticon-1.0.1.min.js" as Jdenticon
|
||||
|
||||
Canvas {
|
||||
id: identicon
|
||||
contextType: "2d"
|
||||
|
||||
property var name
|
||||
property bool showOwnership: false
|
||||
property bool fullOwnership: false
|
||||
|
||||
onNameChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
if (name)
|
||||
Jdenticon.draw(identicon, name)
|
||||
else {
|
||||
var context = identicon.context
|
||||
if (!context) return
|
||||
|
||||
context.reset()
|
||||
var draw_circle = function(context, x, y, radius) {
|
||||
context.beginPath()
|
||||
context.arc(x, y, radius, 0, 2 * Math.PI, false)
|
||||
context.fillStyle = "rgba(0, 0, 0, 0.1)"
|
||||
context.fill()
|
||||
}
|
||||
var size = Math.min(identicon.height, identicon.width)
|
||||
var centerX = size / 2
|
||||
var centerY = size / 2
|
||||
var radius = size/15
|
||||
draw_circle(context, centerX, centerY, radius)
|
||||
draw_circle(context, 2*radius, 2*radius, radius)
|
||||
draw_circle(context, centerX, 2*radius, radius)
|
||||
draw_circle(context, size - 2*radius, 2*radius, radius)
|
||||
draw_circle(context, size - 2*radius, centerY, radius)
|
||||
draw_circle(context, size - 2*radius, size - 2*radius, radius)
|
||||
draw_circle(context, centerX, size - 2*radius, radius)
|
||||
draw_circle(context, 2*radius, size - 2*radius, radius)
|
||||
draw_circle(context, 2*radius, centerY, radius)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: parent.height / 4
|
||||
color: fullOwnership? "green" : "blue"
|
||||
opacity: .6
|
||||
visible: showOwnership
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
color: "white"
|
||||
font.pixelSize: parent.height * 2/3
|
||||
font.bold: true
|
||||
text: fullOwnership? qsTr("FULL") : qsTr("PARTIAL")
|
||||
}
|
||||
TooltipArea {
|
||||
text: fullOwnership? qsTr("You have full control of this account, and can use it to perform actions directly.")
|
||||
: qsTr("You have partial control of this account, and can vote for it to take an action.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
pragma Singleton
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
Item {
|
||||
function mm(millimeters) {
|
||||
return Screen.pixelDensity * millimeters
|
||||
}
|
||||
function cm(centimeters) {
|
||||
return mm(centimeters * 10)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls.Private 1.0
|
||||
|
||||
// TooltipArea.qml
|
||||
// This file contains private Qt Quick modules that might change in future versions of Qt
|
||||
// Tested on: Qt 5.4.1
|
||||
|
||||
MouseArea {
|
||||
id: _root
|
||||
property string text: ""
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: _root.enabled
|
||||
|
||||
onExited: Tooltip.hideText()
|
||||
onCanceled: Tooltip.hideText()
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: _root.enabled && _root.containsMouse && _root.text.length
|
||||
onTriggered: Tooltip.showText(_root, Qt.point(_root.mouseX, _root.mouseY), _root.text)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
/**
|
||||
* This is the form for previewing and approving a transaction prior to broadcasting it
|
||||
*
|
||||
* The arguments property should be populated with an Array of operations. These operations will be used to create a
|
||||
* Transaction, display it, and get confirmation to sign and broadcast it. This form will populate the transaction with
|
||||
* the operations and expiration details, sign it, and pass the signed transaction through the completed signal.
|
||||
*/
|
||||
FormBase {
|
||||
id: base
|
||||
|
||||
readonly property alias trx: __trx
|
||||
|
||||
Component.onCompleted: console.log("Made a transaction confirmation form")
|
||||
Component.onDestruction: console.log("Destroyed a transaction confirmation form")
|
||||
|
||||
onDisplay: {
|
||||
trx.clearOperations()
|
||||
for (var op in arg)
|
||||
trx.appendOperation(arg[op])
|
||||
}
|
||||
|
||||
Transaction {
|
||||
id: __trx
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
Layout.preferredHeight: childrenRect.height + Scaling.mm(4)
|
||||
Layout.preferredWidth: childrenRect.width + Scaling.mm(4)
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
horizontalOffset: Scaling.mm(.5)
|
||||
verticalOffset: Scaling.mm(.5)
|
||||
source: background
|
||||
color: "#80000000"
|
||||
transparentBorder: true
|
||||
}
|
||||
|
||||
// Debugging shim; disable before release
|
||||
Loader {
|
||||
id: delegateLoader
|
||||
x: Scaling.mm(2)
|
||||
y: Scaling.mm(2)
|
||||
Layout.preferredWidth: Scaling.cm(10)
|
||||
Component.onCompleted: setSource("TransactionDelegate.qml", {"trx": __trx, "app": base.app})
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
console.log("Reloading transaction")
|
||||
delegateLoader.source = ""
|
||||
delegateLoader.setSource("TransactionDelegate.qml", {"trx": __trx, "app": base.app})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Label {
|
||||
text: qsTr("Transaction expires in")
|
||||
}
|
||||
ComboBox {
|
||||
id: expirationSelector
|
||||
model: [qsTr("five seconds"), qsTr("thirty seconds"), qsTr("a minute"), qsTr("an hour"), qsTr("a month"), qsTr("a year")]
|
||||
|
||||
function getExpiration() {
|
||||
switch(expirationSelector.currentIndex) {
|
||||
case 0: return new Date(app.model.chainTime.getTime() + 1000*5)
|
||||
case 1: return new Date(app.model.chainTime.getTime() + 1000*30)
|
||||
case 2: return new Date(app.model.chainTime.getTime() + 1000*60)
|
||||
case 3: return new Date(app.model.chainTime.getTime() + 1000*60*60)
|
||||
case 4: return new Date(app.model.chainTime.getTime() + 1000*60*60*24*30)
|
||||
case 5: return new Date(app.model.chainTime.getTime() + 1000*60*60*24*365)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UnlockingFinishButtons {
|
||||
app: base.app
|
||||
Layout.fillWidth: true
|
||||
rightButtonText: qsTr("Commit")
|
||||
onLeftButtonClicked: canceled({})
|
||||
onRightButtonClicked: {
|
||||
if (app.wallet.isLocked)
|
||||
app.wallet.unlock(passwordField.text)
|
||||
else {
|
||||
trx.setExpiration(expirationSelector.getExpiration())
|
||||
app.signTransaction(trx)
|
||||
app.model.broadcast(trx)
|
||||
completed(trx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
ColumnLayout {
|
||||
id: base
|
||||
spacing: Scaling.mm(2)
|
||||
|
||||
property Transaction trx
|
||||
property GrapheneApplication app
|
||||
|
||||
Text {
|
||||
font.bold: true
|
||||
font.pointSize: Scaling.cm(.4)
|
||||
text: qsTr("Transaction Status: %1").arg(trx.statusString)
|
||||
}
|
||||
Repeater {
|
||||
model: trx.operations
|
||||
Loader {
|
||||
function load() {
|
||||
var source
|
||||
switch (modelData.operationType) {
|
||||
case OperationBase.TransferOperationType: source = "TransferOperationDelegate.qml"
|
||||
}
|
||||
setSource(source, {"op": modelData, "app": base.app, "width": base.width})
|
||||
}
|
||||
Component.onCompleted: load()
|
||||
// Debugging shim; disable before release
|
||||
MouseArea {
|
||||
anchors.fill: item
|
||||
onClicked: {
|
||||
console.log("Reloading " + parent.source)
|
||||
parent.source = ""
|
||||
parent.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
/**
|
||||
* This is the form for transferring some amount of asset from one account to another.
|
||||
*/
|
||||
FormBase {
|
||||
id: base
|
||||
|
||||
/// The Account object for the sender
|
||||
property alias senderAccount: senderPicker.account
|
||||
/// The Account object for the receiver
|
||||
property alias receiverAccount: recipientPicker.account
|
||||
|
||||
function operation() {
|
||||
if (!finishLine.rightButtonEnabled) return app.operationBuilder.transfer(0,0,0,0, memoField.text, 0)
|
||||
|
||||
return app.operationBuilder.transfer(senderPicker.account.id, recipientPicker.account.id,
|
||||
amountField.value * amountField.precisionAdjustment,
|
||||
amountField.maxBalance.type.id, memoField.text, 0)
|
||||
}
|
||||
|
||||
Component.onCompleted: console.log("Made a transfer form")
|
||||
Component.onDestruction: console.log("Destroyed a transfer form")
|
||||
|
||||
AccountPicker {
|
||||
id: senderPicker
|
||||
// The senderPicker is really the heart of the form. Everything else in the form adjusts based on the account
|
||||
// selected here. The assetField below updates to contain all assets this account has a nonzero balance in.
|
||||
// The amountField updates based on the asset selected in the assetField to have the appropriate precision and
|
||||
// to have a maximum value equal to the account's balance in that asset. The transfer button enables only when
|
||||
// both accounts are set, and a nonzero amount is selected to be transferred.
|
||||
|
||||
app: base.app
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: Scaling.cm(5)
|
||||
Component.onCompleted: setFocus()
|
||||
placeholderText: qsTr("Sender")
|
||||
showBalance: balances? balances.reduce(function(foundIndex, balance, index) {
|
||||
if (foundIndex >= 0) return foundIndex
|
||||
return balance.type.symbol === assetField.currentText? index : -1
|
||||
}, -1) : -1
|
||||
onBalanceClicked: amountField.value = balance
|
||||
}
|
||||
AccountPicker {
|
||||
id: recipientPicker
|
||||
app: base.app
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: Scaling.cm(5)
|
||||
placeholderText: qsTr("Recipient")
|
||||
layoutDirection: Qt.RightToLeft
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
TextField {
|
||||
id: memoField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Memo")
|
||||
}
|
||||
CheckBox {
|
||||
id: encryptMemoField
|
||||
text: qsTr("Encrypt Memo")
|
||||
checked: true
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
SpinBox {
|
||||
id: amountField
|
||||
Layout.preferredWidth: Scaling.cm(4)
|
||||
Layout.minimumWidth: Scaling.cm(1.5)
|
||||
enabled: maxBalance
|
||||
minimumValue: 0
|
||||
maximumValue: maxBalance? maxBalance.amountReal() : 0
|
||||
decimals: maxBalance? maxBalance.type.precision : 0
|
||||
|
||||
Keys.onReturnPressed: if (finishLine.rightButtonEnabled) finishLine.clickRight()
|
||||
|
||||
property Balance maxBalance: assetField.enabled && senderPicker.showBalance >= 0?
|
||||
senderPicker.balances[senderPicker.showBalance] : null
|
||||
property int precisionAdjustment: maxBalance? Math.pow(10, maxBalance.type.precision) : 1
|
||||
|
||||
// Workaround to preserve value in case form gets disabled then re-enabled
|
||||
onEnabledChanged: if (!enabled) __valueBackup = value
|
||||
onMaximumValueChanged: if (enabled && maximumValue > __valueBackup) value = __valueBackup
|
||||
property real __valueBackup
|
||||
}
|
||||
ComboBox {
|
||||
id: assetField
|
||||
Layout.minimumWidth: Scaling.cm(1)
|
||||
enabled: senderPicker.balances instanceof Array && senderPicker.balances.length > 0
|
||||
model: enabled? senderPicker.balances.filter(function(balance) { return balance.amount > 0 })
|
||||
.map(function(balance) { return balance.type.symbol })
|
||||
: ["Asset Type"]
|
||||
}
|
||||
Text {
|
||||
font.pixelSize: assetField.height / 2.5
|
||||
text: {
|
||||
if (!senderPicker.account || !amountField.maxBalance)
|
||||
return ""
|
||||
return qsTr("Fee:<br/>") + amountField.maxBalance.type.formatAmount(operation().fee) + " CORE"
|
||||
}
|
||||
}
|
||||
}
|
||||
UnlockingFinishButtons {
|
||||
id: finishLine
|
||||
app: base.app
|
||||
rightButtonText: {
|
||||
return !senderAccount ||
|
||||
!senderAccount.isLoaded ||
|
||||
senderPicker.accountControlLevel >= 1? qsTr("Transfer") : qsTr("Propose")
|
||||
}
|
||||
rightButtonEnabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
|
||||
requiresUnlocking: encryptMemoField.checked && memoField.text
|
||||
Layout.fillWidth: true
|
||||
|
||||
onLeftButtonClicked: canceled({})
|
||||
onRightButtonClicked: {
|
||||
var op = operation()
|
||||
if (encryptMemoField.checked)
|
||||
op.encryptMemo(app.wallet, app.model)
|
||||
completed([op])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
import "."
|
||||
|
||||
ColumnLayout {
|
||||
id: base
|
||||
|
||||
property TransferOperation op
|
||||
property GrapheneApplication app
|
||||
|
||||
property Asset transferAsset: app.model.getAsset(op.amountType)
|
||||
property Asset feeAsset: app.model.getAsset(op.feeType)
|
||||
property Account sender: app.model.getAccount(op.sender)
|
||||
property Account receiver: app.model.getAccount(op.receiver)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Identicon {
|
||||
height: Scaling.cm(1)
|
||||
width: Scaling.cm(1)
|
||||
name: sender.name
|
||||
}
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: "-><br/>%1 %2".arg(transferAsset.formatAmount(op.amount)).arg(transferAsset.symbol)
|
||||
horizontalAlignment: Text.Center
|
||||
}
|
||||
Identicon {
|
||||
height: Scaling.cm(1)
|
||||
width: Scaling.cm(1)
|
||||
name: receiver.name
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: qsTr("Transfer from %1 to %2").arg(sender.name).arg(receiver.name)
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pointSize: 9
|
||||
text: qsTr("Fee: %1 %2").arg(feeAsset.formatAmount(op.fee)).arg(feeAsset.symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: op.memo? qsTr("Memo: %1").arg(op.memoIsEncrypted && !app.wallet.isLocked? op.decryptedMemo(app.wallet, app.model)
|
||||
: op.memo)
|
||||
: qsTr("No memo")
|
||||
color: op.memo? "black" : "grey"
|
||||
font.italic: !op.memo
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import "."
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
RowLayout {
|
||||
property string leftButtonText: qsTr("Cancel")
|
||||
property string unlockButtonText: qsTr("Unlock")
|
||||
property string rightButtonText: qsTr("Finish")
|
||||
property bool leftButtonEnabled: true
|
||||
property bool rightButtonEnabled: true
|
||||
property bool requiresUnlocking: true
|
||||
property GrapheneApplication app
|
||||
|
||||
signal leftButtonClicked
|
||||
signal rightButtonClicked
|
||||
|
||||
function clickLeft() { leftButton.clicked() }
|
||||
function clickRight() { rightButton.clicked() }
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
id: leftButton
|
||||
text: leftButtonText
|
||||
onClicked: leftButtonClicked()
|
||||
}
|
||||
TextField {
|
||||
id: passwordField
|
||||
property bool shown: requiresUnlocking && app.wallet.isLocked
|
||||
property real desiredWidth: shown? Scaling.cm(4) : 0
|
||||
Layout.preferredWidth: desiredWidth
|
||||
echoMode: TextInput.Password
|
||||
placeholderText: qsTr("Wallet password")
|
||||
visible: desiredWidth > 0
|
||||
onAccepted: rightButton.clicked()
|
||||
|
||||
Behavior on desiredWidth { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
||||
}
|
||||
Button {
|
||||
id: rightButton
|
||||
text: passwordField.shown? unlockButtonText : rightButtonText
|
||||
enabled: rightButtonEnabled
|
||||
onClicked: {
|
||||
if (passwordField.visible)
|
||||
return app.wallet.unlock(passwordField.text)
|
||||
|
||||
rightButtonClicked()
|
||||
}
|
||||
Behavior on implicitWidth { NumberAnimation {} }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Jdenticon 1.0.1 | jdenticon.com | zlib licensed | (c) 2014-2015 Daniel Mester Pirttijärvi
|
||||
/*Alteration to official Jdenticon code: make jdenticon() a variable in this context instead of a property of window*/
|
||||
var jdenticon=function(){function h(){}function n(b,a,c,d){this.h=b;this.i=a;this.k=c;this.f=d}function u(b,a){this.e=b;this.g=a||n.q;b.beginPath()}function r(b,a,c){var d=(b="string"===typeof b?document.querySelector(b):b).getContext("2d");c=Math.min(b.width)*(1-2*(c===t?.08:c));d.save();d.clearRect(0,0,b.width,b.height);d.translate(0|(b.width-c)/2,0|(b.height-c)/2);v(d,a||b.getAttribute("data-jdenticon-hash"),c);d.restore()}function v(b,a,c){function d(b,c,d,e,g){e=e?parseInt(a.charAt(e),16):
|
||||
0;c=c[parseInt(a.charAt(d),16)%c.length];var f;for(d=0;d<g.length;d++)f=new n(g[d][0]*p,g[d][1]*p,p,e++%4),f=new u(b,f),c(f,p,d),f.fill()}function e(a){if(0<=a.indexOf(k))for(var b=0;b<a.length;b++)if(0<=l.indexOf(a[b]))return!0}function g(a){b.fillStyle=m[l[a]].toString()}if(30>c)throw Error("Jdenticon cannot render identicons smaller than 30 pixels.");if(!/^[0-9a-f]{10,}$/i.test(a))throw Error("Invalid hash passed to Jdenticon.");c|=0;for(var p=2*(0|c/8),f=parseInt(a.substr(-7),16)/268435455,m=
|
||||
[h.o(76,76,76),h.m(f,.6),h.o(230,230,230),h.m(f,.8),h.n(f,.4)],l=[],k,f=0;3>f;f++){k=parseInt(a.charAt(8+f),16)%m.length;if(e([0,4])||e([2,3]))k=1;l.push(k)}b.clearRect(0,0,c,c);g(0);d(b,w,2,3,[[1,0],[2,0],[2,3],[1,3],[0,1],[3,1],[3,2],[0,2]]);g(1);d(b,w,4,5,[[0,0],[3,0],[3,3],[0,3]]);g(2);d(b,y,1,null,[[1,1],[2,1],[2,2],[1,2]])}function q(){for(var b,a=document.getElementsByTagName("canvas"),c=0;c<a.length;c++)(b=a[c].getAttribute("data-jdenticon-hash"))&&r(a[c],b)}var t,x=window.jQuery;h.o=function(b,
|
||||
a,c){var d=new h;d.p="rgba("+(b&255)+","+(a&255)+","+(c&255)+","+(void 0===t?1:void 0)+")";return d};h.n=function(b,a){var c=new h;c.p="hsla("+(360*b|0)+",50%,"+(100*a|0)+"%,"+(void 0===t?1:void 0)+")";return c};h.m=function(b,a){return h.n(b,1-[.95,1,1,1,.7,.8,.8][6*b+.5|0]*(1-a))};h.prototype={toString:function(){return this.p}};n.q=new n(0,0,0,0);n.prototype={j:function(b,a,c,d){var e=this.h+this.k,g=this.i+this.k;return 1===this.f?[e-a-(d||0),this.i+b]:2===this.f?[e-b-(c||0),g-a-(d||0)]:3===this.f?
|
||||
[this.h+a,g-b-(c||0)]:[this.h+b,this.i+a]}};u.prototype={c:function(b,a){var c=a?-2:2,d=a?b.length-2:0,e=this.e;e.moveTo.apply(e,this.g.j(b[d],b[d+1]));for(d+=c;d<b.length&&0<=d;d+=c)e.lineTo.apply(e,this.g.j(b[d],b[d+1]));e.closePath()},d:function(b,a,c,d,e){var g=this.e;a=this.g.j(b,a,c,d);b=a[0];a=a[1];var h=c/2*.5522848,f=d/2*.5522848,m=b+c,l=a+d;c=b+c/2;var k=a+d/2;e&&(l=a,a+=d,f=-f);g.moveTo(b,k);g.bezierCurveTo(b,k-f,c-h,a,c,a);g.bezierCurveTo(c+h,a,m,k-f,m,k);g.bezierCurveTo(m,k+f,c+h,l,c,
|
||||
l);g.bezierCurveTo(c-h,l,b,k+f,b,k);g.closePath()},a:function(b,a,c,d,e){this.c([b,a,b+c,a,b+c,a+d,b,a+d],e)},b:function(b,a,c,d,e,g){b=[b+c,a,b+c,a+d,b,a+d,b,a];b.splice((e||0)%4,2);this.c(b,g)},l:function(b,a,c,d,e){this.c([b+c/2,a,b+c,a+d/2,b+c/2,a+d,b,a+d/2],e)},fill:function(){this.e.fill()}};var y=[function(b,a){var c=.42*a;b.c([0,0,a,0,a,a-2*c,a-c,a,0,a])},function(b,a){var c=0|.4*a;b.b(a-c,a-2*c,c,2*c,1)},function(b,a){var c=0|a/3;b.a(c,c,a-c,a-c)},function(b,a){var c=0|.1*a,d=0|.25*a;b.a(d,
|
||||
d,a-c-d,a-c-d)},function(b,a){var c=0|.15*a,d=0|.5*a;b.d(a-d-c,a-d-c,d,d)},function(b,a){var c=.1*a,d=4*c;b.a(0,0,a,a);b.c([d,d,a-c,d,d+(a-d-c)/2,a-c],!0)},function(b,a){b.b(0,0,a,a,0)},function(b,a){b.b(a/2,a/2,a/2,a/2,0)},function(b,a){b.a(0,0,a,a/2);b.a(0,a/2,a/2,a/2);b.b(a/2,a/2,a/2,a/2,1)},function(b,a){var c=0|.14*a,d=0|.35*a;b.a(0,0,a,a);b.a(d,d,a-d-c,a-d-c,!0)},function(b,a){var c=.12*a,d=3*c;b.a(0,0,a,a);b.d(d,d,a-c-d,a-c-d,!0)},function(b,a){b.b(a/2,a/2,a/2,a/2,3)},function(b,a){var c=.25*
|
||||
a;b.a(0,0,a,a);b.l(c,c,a-c,a-c,!0)},function(b,a,c){var d=.4*a;a*=1.2;c||b.d(d,d,a,a)}],w=[function(b,a){b.b(0,0,a,a,0)},function(b,a){b.b(0,a/2,a,a/2,0)},function(b,a){b.l(0,0,a,a)},function(b,a){var c=a/6;b.d(c,c,a-2*c,a-2*c)}];q.drawIcon=v;q.update=r;x&&(x.fn.jdenticon=function(b,a){this.each(function(c,d){r(d,b,a)});return this});/*Alteration to official Jdenticon code: Removed call to schedule a render immediately on load*/return q}();
|
||||
|
||||
// The following functions were added, and are not part of the official Jdenticon code
|
||||
|
||||
/// @param canvas The canvas to draw on
|
||||
/// @param data The data to generate an identicon for (this will be hashed to generate the identicon)
|
||||
function draw(canvas, data) {
|
||||
jdenticon.drawIcon(canvas.context, Crypto.sha256(data), Math.min(canvas.width, canvas.height))
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
Jdenticon
|
||||
Copyright (c) 2014 Daniel Mester Pirttijärvi
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import Graphene.Client 0.1
|
||||
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
visible: true
|
||||
width: 640
|
||||
height: 480
|
||||
title: qsTr("Hello World")
|
||||
|
||||
menuBar: MenuBar {
|
||||
Menu {
|
||||
title: qsTr("File")
|
||||
MenuItem {
|
||||
text: qsTr("&Open")
|
||||
onTriggered: console.log("Open action triggered");
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Exit")
|
||||
onTriggered: Qt.quit();
|
||||
shortcut: "Ctrl+Q"
|
||||
}
|
||||
}
|
||||
}
|
||||
statusBar: StatusBar {
|
||||
Label {
|
||||
anchors.right: parent.right
|
||||
text: qsTr("Network: %1 Wallet: %2").arg(app.isConnected? qsTr("Connected") : qsTr("Disconnected"))
|
||||
.arg(app.wallet.isLocked? qsTr("Locked") : qsTr("Unlocked"))
|
||||
}
|
||||
}
|
||||
|
||||
GrapheneApplication {
|
||||
id: app
|
||||
Component.onCompleted: {
|
||||
var walletFile = appSettings.walletPath + "/wallet.json"
|
||||
if (!wallet.open(walletFile)) {
|
||||
// TODO: onboarding experience
|
||||
wallet.create(walletFile, "default password", "default brain key")
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
running: !app.isConnected
|
||||
interval: 5000
|
||||
repeat: true
|
||||
onTriggered: app.start("ws://localhost:8090", "user", "pass")
|
||||
triggeredOnStart: true
|
||||
}
|
||||
Settings {
|
||||
id: appSettings
|
||||
category: "appSettings"
|
||||
|
||||
property string walletPath: app.defaultDataPath()
|
||||
}
|
||||
Connections {
|
||||
target: app
|
||||
onExceptionThrown: console.log("Exception from app: " + message)
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
enabled: app.isConnected
|
||||
|
||||
Button {
|
||||
text: "Transfer"
|
||||
onClicked: {
|
||||
var front = Qt.createComponent("TransferForm.qml")
|
||||
// TODO: make back into a preview and confirm dialog
|
||||
var back = Qt.createComponent("TransactionConfirmationForm.qml")
|
||||
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
|
||||
function(arg) {
|
||||
console.log("Closed form: " + JSON.stringify(arg))
|
||||
})
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: nameField
|
||||
onAccepted: lookupNameButton.clicked()
|
||||
focus: true
|
||||
}
|
||||
Button {
|
||||
id: lookupNameButton
|
||||
text: "Lookup Name"
|
||||
onClicked: {
|
||||
var acct = app.model.getAccount(nameField.text)
|
||||
console.log(JSON.stringify(acct))
|
||||
// @disable-check M126
|
||||
if (acct == null)
|
||||
console.log("Got back null account")
|
||||
else if (acct.id >= 0)
|
||||
{
|
||||
console.log("ID ALREADY SET" );
|
||||
console.log(JSON.stringify(acct))
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("Waiting for result...")
|
||||
acct.idChanged.connect(function() {
|
||||
console.log( "ID CHANGED" );
|
||||
console.log(JSON.stringify(acct))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: accountIdField
|
||||
onAccepted: lookupAccountIdButton.clicked()
|
||||
focus: true
|
||||
}
|
||||
Button {
|
||||
id: lookupAccountIdButton
|
||||
text: "Lookup Account ID"
|
||||
onClicked: {
|
||||
var acct = app.model.getAccount(parseInt(accountIdField.text))
|
||||
console.log(JSON.stringify(acct))
|
||||
// @disable-check M126
|
||||
if (acct == null)
|
||||
console.log("Got back null account")
|
||||
else if ( !(parseInt(acct.name) <= 0) )
|
||||
console.log(JSON.stringify(acct))
|
||||
else {
|
||||
console.log("Waiting for result...")
|
||||
acct.nameChanged.connect(function() {
|
||||
console.log(JSON.stringify(acct))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: assetIdField
|
||||
onAccepted: lookupassetIdButton.clicked()
|
||||
focus: true
|
||||
}
|
||||
Button {
|
||||
id: lookupassetIdButton
|
||||
text: "Lookup Asset ID"
|
||||
onClicked: {
|
||||
var acct = app.model.getAsset(parseInt(assetIdField.text))
|
||||
console.log(JSON.stringify(acct))
|
||||
// @disable-check M126
|
||||
if (acct == null)
|
||||
console.log("Got back null asset")
|
||||
else if ( !(parseInt(acct.name) <= 0) )
|
||||
{
|
||||
console.log(JSON.stringify(acct))
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("Waiting for result...")
|
||||
acct.nameChanged.connect(function() {
|
||||
console.log(JSON.stringify(acct))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: passwordField
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Button {
|
||||
text: app.wallet.isLocked? "Unlock wallet" : "Lock wallet"
|
||||
onClicked: {
|
||||
if (app.wallet.isLocked)
|
||||
app.wallet.unlock(passwordField.text)
|
||||
else
|
||||
app.wallet.lock()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: keyField
|
||||
placeholderText: "Private key"
|
||||
}
|
||||
TextField {
|
||||
id: keyLabelField
|
||||
placeholderText: "Key label"
|
||||
}
|
||||
Button {
|
||||
text: "Import key"
|
||||
enabled: !app.wallet.isLocked && keyField.text && keyLabelField.text
|
||||
onClicked: app.wallet.importPrivateKey(keyField.text, keyLabelField.text)
|
||||
}
|
||||
}
|
||||
|
||||
FormBox {
|
||||
id: formBox
|
||||
anchors.fill: parent
|
||||
z: 10
|
||||
}
|
||||
|
||||
// This Settings is only for geometry -- it doesn't get an id. See appSettings for normal settings
|
||||
Settings {
|
||||
category: "windowGeometry"
|
||||
property alias x: window.x
|
||||
property alias y: window.y
|
||||
property alias width: window.width
|
||||
property alias height: window.height
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>main.qml</file>
|
||||
<file>FormBase.qml</file>
|
||||
<file>TransferForm.qml</file>
|
||||
<file>TransactionDelegate.qml</file>
|
||||
<file>TransferOperationDelegate.qml</file>
|
||||
<file>TransactionConfirmationForm.qml</file>
|
||||
<file>FormBox.qml</file>
|
||||
<file>FormFlipper.qml</file>
|
||||
<file>UnlockingFinishButtons.qml</file>
|
||||
<file>Scaling.qml</file>
|
||||
<file>Identicon.qml</file>
|
||||
<file>AccountPicker.qml</file>
|
||||
<file>TooltipArea.qml</file>
|
||||
<file>jdenticon/jdenticon-1.0.1.min.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
singleton Scaling Scaling.qml
|
||||
Loading…
Reference in a new issue