1
0
Fork 0
forked from lthn/blockchain

Merge branch 'develop' into develop_mobile

This commit is contained in:
cryptozoidberg 2020-11-25 18:48:56 +01:00
commit a7d2fcde36
No known key found for this signature in database
GPG key ID: 22DEB97A54C6FDEC
53 changed files with 1410 additions and 408 deletions

View file

@ -60,18 +60,42 @@ namespace currency
return m_keys;
}
//-----------------------------------------------------------------
std::string account_base::get_seed_phrase() const
void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password)
{
crypto::chacha8_key key = AUTO_VAL_INIT(key);
crypto::generate_chacha8_key(password, key);
crypto::hash pass_hash = crypto::cn_fast_hash(password.data(), password.size());
crypto::chacha8_iv iv = AUTO_VAL_INIT(iv);
CHECK_AND_ASSERT_THROW_MES(sizeof(pass_hash) >= sizeof(iv), "Invalid configuration: hash size is less than keys_file_data.iv");
iv = *((crypto::chacha8_iv*)&pass_hash);
crypto::chacha8(scr_data, src_length, key, iv, (char*)dst_data);
}
std::string account_base::get_seed_phrase(const std::string& password) const
{
if (m_keys_seed_binary.empty())
return "";
std::string keys_seed_text = tools::mnemonic_encoding::binary2text(m_keys_seed_binary);
std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp);
std::vector<unsigned char> processed_seed_binary = m_keys_seed_binary;
if (!password.empty())
{
//encrypt seed phrase binary data
crypt_with_pass(&m_keys_seed_binary[0], m_keys_seed_binary.size(), &processed_seed_binary[0], password);
}
std::string keys_seed_text = tools::mnemonic_encoding::binary2text(processed_seed_binary);
std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp, !password.empty());
// floor creation time to WALLET_BRAIN_DATE_QUANTUM to make checksum calculation stable
uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word);
bool self_check_is_password_used = false;
uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word, self_check_is_password_used);
CHECK_AND_ASSERT_THROW_MES(self_check_is_password_used == !password.empty(), "Account seed phrase internal error: password flag encoded wrong");
constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum
crypto::hash h = crypto::cn_fast_hash(m_keys_seed_binary.data(), m_keys_seed_binary.size());
std::string binary_for_check_sum((const char*)&m_keys_seed_binary[0], m_keys_seed_binary.size());
binary_for_check_sum.append(password);
crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size());
*reinterpret_cast<uint64_t*>(&h) = creation_timestamp_rounded;
h = crypto::cn_fast_hash(&h, sizeof h);
uint64_t h_64 = *reinterpret_cast<uint64_t*>(&h);
@ -104,7 +128,7 @@ namespace currency
return true;
}
//-----------------------------------------------------------------
bool account_base::restore_from_seed_phrase(const std::string& seed_phrase)
bool account_base::restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password)
{
//cut the last timestamp word from restore_dats
std::list<std::string> words;
@ -138,9 +162,20 @@ namespace currency
auditable_flag_and_checksum = tools::mnemonic_encoding::num_by_word(auditable_flag_and_checksum_word);
std::vector<unsigned char> keys_seed_binary = tools::mnemonic_encoding::text2binary(keys_seed_text);
CHECK_AND_ASSERT_MES(keys_seed_binary.size(), false, "text2binary failed to convert the given text"); // don't prints event incorrect seed into the log for security
std::vector<unsigned char> keys_seed_processed_binary = keys_seed_binary;
m_creation_timestamp = get_timstamp_from_word(timestamp_word);
bool has_password = false;
m_creation_timestamp = get_timstamp_from_word(timestamp_word, has_password);
//double check is password setting from timestamp word match with passed parameters
CHECK_AND_ASSERT_MES(has_password != seed_password.empty(), false, "Seed phrase password wrong interpretation");
if (has_password)
{
CHECK_AND_ASSERT_MES(!seed_password.empty(), false, "Seed phrase password wrong interpretation: internal error");
crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &keys_seed_processed_binary[0], seed_password);
}
CHECK_AND_ASSERT_MES(keys_seed_processed_binary.size(), false, "text2binary failed to convert the given text"); // don't prints event incorrect seed into the log for security
bool auditable_flag = false;
@ -150,7 +185,9 @@ namespace currency
auditable_flag = (auditable_flag_and_checksum & 1) != 0; // auditable flag is the lower 1 bit
uint16_t checksum = static_cast<uint16_t>(auditable_flag_and_checksum >> 1); // checksum -- everything else
constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum
crypto::hash h = crypto::cn_fast_hash(keys_seed_binary.data(), keys_seed_binary.size());
std::string binary_for_check_sum((const char*)&keys_seed_processed_binary[0], keys_seed_processed_binary.size());
binary_for_check_sum.append(seed_password);
crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size());
*reinterpret_cast<uint64_t*>(&h) = m_creation_timestamp;
h = crypto::cn_fast_hash(&h, sizeof h);
uint64_t h_64 = *reinterpret_cast<uint64_t*>(&h);
@ -158,10 +195,10 @@ namespace currency
CHECK_AND_ASSERT_MES(checksum == checksum_calculated, false, "seed phase has invalid checksum: " << checksum_calculated << ", while " << checksum << " is expected, check your words");
}
bool r = restore_keys(keys_seed_binary);
bool r = restore_keys(keys_seed_processed_binary);
CHECK_AND_ASSERT_MES(r, false, "restore_keys failed");
m_keys_seed_binary = keys_seed_binary;
m_keys_seed_binary = keys_seed_processed_binary;
if (auditable_flag)
m_keys.account_address.flags |= ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE;
@ -169,6 +206,34 @@ namespace currency
return true;
}
//-----------------------------------------------------------------
bool account_base::is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected)
{
//cut the last timestamp word from restore_dats
std::list<std::string> words;
boost::split(words, seed_phrase, boost::is_space());
std::string timestamp_word;
if (words.size() == SEED_PHRASE_V1_WORDS_COUNT)
{
// 24 seed words + one timestamp word = 25 total
timestamp_word = words.back();
}
else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT)
{
// 24 seed words + one timestamp word + one flags & checksum = 26 total
words.erase(--words.end());
timestamp_word = words.back();
}
else
{
return false;
}
get_timstamp_from_word(timestamp_word, is_password_protected);
return true;
}
//-----------------------------------------------------------------
bool account_base::restore_from_tracking_seed(const std::string& tracking_seed)
{
set_null();

View file

@ -53,9 +53,9 @@ namespace currency
const account_public_address& get_public_address() const { return m_keys.account_address; };
std::string get_public_address_str() const;
std::string get_seed_phrase() const;
std::string get_seed_phrase(const std::string& seed_password) const;
std::string get_tracking_seed() const;
bool restore_from_seed_phrase(const std::string& seed_phrase);
bool restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password);
bool restore_from_tracking_seed(const std::string& tracking_seed);
uint64_t get_createtime() const { return m_creation_timestamp; }
@ -78,6 +78,7 @@ namespace currency
static std::string vector_of_chars_to_string(const std::vector<unsigned char>& v) { return std::string(v.begin(), v.end()); }
static std::vector<unsigned char> string_to_vector_of_chars(const std::string& v) { return std::vector<unsigned char>(v.begin(), v.end()); }
static bool is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected);
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(m_keys)

View file

@ -150,9 +150,10 @@
#define WALLET_FILE_MAX_KEYS_SIZE 10000 //
#define WALLET_BRAIN_DATE_OFFSET 1543622400
#define WALLET_BRAIN_DATE_QUANTUM 604800 //by last word we encode a number of week since launch of the project,
#define WALLET_BRAIN_DATE_QUANTUM 604800 //by last word we encode a number of week since launch of the project AND password flag,
//which let us to address tools::mnemonic_encoding::NUMWORDS weeks after project launch
//which is about 30 years
//which is about 15 years
#define WALLET_BRAIN_DATE_MAX_WEEKS_COUNT 800
#define OFFER_MAXIMUM_LIFE_TIME (60*60*24*30) // 30 days

View file

@ -1316,21 +1316,34 @@ namespace currency
return reward;
}
//---------------------------------------------------------------
std::string get_word_from_timstamp(uint64_t timestamp)
std::string get_word_from_timstamp(uint64_t timestamp, bool use_password)
{
uint64_t date_offset = timestamp > WALLET_BRAIN_DATE_OFFSET ? timestamp - WALLET_BRAIN_DATE_OFFSET : 0;
uint64_t weeks_count = date_offset / WALLET_BRAIN_DATE_QUANTUM;
CHECK_AND_ASSERT_THROW_MES(weeks_count < WALLET_BRAIN_DATE_MAX_WEEKS_COUNT, "SEED PHRASE need to be extended or refactored");
if (use_password)
weeks_count += WALLET_BRAIN_DATE_MAX_WEEKS_COUNT;
CHECK_AND_ASSERT_THROW_MES(weeks_count < std::numeric_limits<uint32_t>::max(), "internal error: unable to convert to uint32, val = " << weeks_count);
uint32_t weeks_count_32 = static_cast<uint32_t>(weeks_count);
return tools::mnemonic_encoding::word_by_num(weeks_count_32);
}
//---------------------------------------------------------------
uint64_t get_timstamp_from_word(std::string word)
uint64_t get_timstamp_from_word(std::string word, bool& password_used)
{
uint64_t count_of_weeks = tools::mnemonic_encoding::num_by_word(word);
if (count_of_weeks >= WALLET_BRAIN_DATE_MAX_WEEKS_COUNT)
{
count_of_weeks -= WALLET_BRAIN_DATE_MAX_WEEKS_COUNT;
password_used = true;
}
else {
password_used = false;
}
uint64_t timestamp = count_of_weeks * WALLET_BRAIN_DATE_QUANTUM + WALLET_BRAIN_DATE_OFFSET;
return timestamp;
}
//---------------------------------------------------------------

View file

@ -291,8 +291,8 @@ namespace currency
bool fill_tx_rpc_details(tx_rpc_extended_info& tei, const transaction& tx, const transaction_chain_entry* ptce, const crypto::hash& h, uint64_t timestamp, bool is_short = false);
bool fill_block_rpc_details(block_rpc_extended_info& pei_rpc, const block_extended_info& bei_chain, const crypto::hash& h);
void append_per_block_increments_for_tx(const transaction& tx, std::unordered_map<uint64_t, uint32_t>& gindices);
std::string get_word_from_timstamp(uint64_t timestamp);
uint64_t get_timstamp_from_word(std::string word);
std::string get_word_from_timstamp(uint64_t timestamp, bool use_password);
uint64_t get_timstamp_from_word(std::string word, bool& password_used);
template<class t_txin_v>
typename std::conditional<std::is_const<t_txin_v>::value, const std::vector<txin_etc_details_v>, std::vector<txin_etc_details_v> >::type& get_txin_etc_options(t_txin_v& in)

View file

@ -239,6 +239,28 @@ QString MainWindow::get_tx_pool_info()
CATCH_ENTRY_FAIL_API_RESPONCE();
}
QString MainWindow::request_dummy()
{
static int code_ = 0;
TRY_ENTRY();
LOG_API_TIMING();
PREPARE_RESPONSE(currency::COMMAND_RPC_GET_POOL_INFO::response, ar);
if (code_ == 2)
{
code_ = -1;
ar.error_code = API_RETURN_CODE_CORE_BUSY;
}
else
{
ar.error_code = API_RETURN_CODE_OK;
}
++code_;
return MAKE_RESPONSE(ar);
CATCH_ENTRY_FAIL_API_RESPONCE();
}
QString MainWindow::get_default_fee()
{
TRY_ENTRY();
@ -1654,7 +1676,7 @@ QString MainWindow::restore_wallet(const QString& param)
//return que_call2<view::restore_wallet_request>("restore_wallet", param, [this](const view::restore_wallet_request& owd, view::api_response& ar){
PREPARE_ARG_FROM_JSON(view::restore_wallet_request, owd);
PREPARE_RESPONSE(view::open_wallet_response, ar);
ar.error_code = m_backend.restore_wallet(epee::string_encoding::utf8_to_wstring(owd.path), owd.pass, owd.restore_key, ar.response_data);
ar.error_code = m_backend.restore_wallet(epee::string_encoding::utf8_to_wstring(owd.path), owd.pass, owd.seed_phrase, owd.seed_pass, ar.response_data);
return MAKE_RESPONSE(ar);
CATCH_ENTRY_FAIL_API_RESPONCE();
}
@ -1855,9 +1877,9 @@ QString MainWindow::get_smart_wallet_info(const QString& param)
{
TRY_ENTRY();
LOG_API_TIMING();
PREPARE_ARG_FROM_JSON(view::wallet_id_obj, wo);
PREPARE_ARG_FROM_JSON(view::request_get_smart_wallet_info, wo);
PREPARE_RESPONSE(view::get_restore_info_response, ar);
ar.error_code = m_backend.get_wallet_restore_info(wo.wallet_id, ar.response_data.restore_key);
ar.error_code = m_backend.get_wallet_restore_info(wo.wallet_id, ar.response_data.seed_phrase, wo.seed_password);
return MAKE_RESPONSE(ar);
CATCH_ENTRY_FAIL_API_RESPONCE();
}
@ -1951,14 +1973,26 @@ QString MainWindow::open_url_in_browser(const QString& param)
CATCH_ENTRY2(API_RETURN_CODE_INTERNAL_ERROR);
}
QString MainWindow::is_valid_restore_wallet_text(const QString& param)
{
TRY_ENTRY();
LOG_API_TIMING();
return m_backend.is_valid_brain_restore_data(param.toStdString()).c_str();
PREPARE_ARG_FROM_JSON(view::is_valid_restore_wallet_text_param, rwtp);
return m_backend.is_valid_brain_restore_data(rwtp.seed_phrase, rwtp.seed_password).c_str();
CATCH_ENTRY2(API_RETURN_CODE_INTERNAL_ERROR);
}
QString MainWindow::get_seed_phrase_info(const QString& param)
{
TRY_ENTRY();
LOG_API_TIMING();
PREPARE_ARG_FROM_JSON(view::is_valid_restore_wallet_text_param, rwtp);
PREPARE_RESPONSE(view::seed_phrase_info, ar);
ar.error_code = m_backend.get_seed_phrase_info(rwtp.seed_phrase, rwtp.seed_password, ar.response_data).c_str();
return MAKE_RESPONSE(ar);
CATCH_ENTRY_FAIL_API_RESPONCE();
}
void MainWindow::contextMenuEvent(QContextMenuEvent * event)
{
TRY_ENTRY();

View file

@ -137,6 +137,7 @@ public:
QString is_autostart_enabled();
QString toggle_autostart(const QString& param);
QString is_valid_restore_wallet_text(const QString& param);
QString get_seed_phrase_info(const QString& param);
QString print_text(const QString& param);
QString print_log(const QString& param);
QString set_clipboard(const QString& param);
@ -159,6 +160,9 @@ public:
QString is_remnotenode_mode_preconfigured();
QString start_backend(const QString& params);
//for test purposes onlys
QString request_dummy();
signals:
void quit_requested(const QString str);
void update_daemon_state(const QString str);

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4db1ff;}
</style>
<polygon class="st0" points="31.9,4.9 29.1,2.1 17,14.2 4.9,2.1 2.1,4.9 14.2,17 2.1,29.1 4.9,31.9 17,19.8 29.1,31.9 31.9,29.1
19.8,17 "/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#5cda9d;}
</style>
<path id="receive" class="st0" d="M64.2,309.6l58.2-210.4l48.1,71.6L178,182l12.7,4.7l81.9,30.4L64.2,309.6 M0,384l384-170.3
l-178.7-66.4L106.3,0L0,384L0,384z"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#ff5252;}
</style>
<path id="send" class="st0" d="M319.8,74.4l-58.2,210.4l-48.1-71.6L206,202l-12.7-4.7l-81.9-30.4L319.8,74.4 M384,0L0,170.3
l178.7,66.4L277.7,384L384,0L384,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4db1ff;}
</style>
<polygon class="st0" points="31.9,4.9 29.1,2.1 17,14.2 4.9,2.1 2.1,4.9 14.2,17 2.1,29.1 4.9,31.9 17,19.8 29.1,31.9 31.9,29.1
19.8,17 "/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -5800,8 +5800,8 @@ __webpack_require__.r(__webpack_exports__);
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(/*! /Users/mekasan/Projects/Projects/zano/src/gui/qt-daemon/html_source/src/polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! /Users/mekasan/Projects/Projects/zano/src/gui/qt-daemon/html_source/node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js */"./node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js");
__webpack_require__(/*! /Users/mekasan/Projects/Projects/zano_v1/src/gui/qt-daemon/html_source/src/polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! /Users/mekasan/Projects/Projects/zano_v1/src/gui/qt-daemon/html_source/node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js */"./node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js");
/***/ })

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#5cda9d;}
</style>
<path id="receive" class="st0" d="M64.2,309.6l58.2-210.4l48.1,71.6L178,182l12.7,4.7l81.9,30.4L64.2,309.6 M0,384l384-170.3
l-178.7-66.4L106.3,0L0,384L0,384z"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#ff5252;}
</style>
<path id="send" class="st0" d="M319.8,74.4l-58.2,210.4l-48.1-71.6L206,202l-12.7-4.7l-81.9-30.4L319.8,74.4 M384,0L0,170.3
l178.7,66.4L277.7,384L384,0L384,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -90280,12 +90280,12 @@ exports.byteLength = byteLength
exports.toByteArray = toByteArray
exports.fromByteArray = fromByteArray
var lookup = []
var revLookup = []
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
const lookup = []
const revLookup = []
const Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for (var i = 0, len = code.length; i < len; ++i) {
const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for (let i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i]
revLookup[code.charCodeAt(i)] = i
}
@ -90296,7 +90296,7 @@ revLookup['-'.charCodeAt(0)] = 62
revLookup['_'.charCodeAt(0)] = 63
function getLens (b64) {
var len = b64.length
const len = b64.length
if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
@ -90304,10 +90304,10 @@ function getLens (b64) {
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf('=')
let validLen = b64.indexOf('=')
if (validLen === -1) validLen = len
var placeHoldersLen = validLen === len
const placeHoldersLen = validLen === len
? 0
: 4 - (validLen % 4)
@ -90316,9 +90316,9 @@ function getLens (b64) {
// base64 is 4/3 + up to two characters of the original data
function byteLength (b64) {
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
const lens = getLens(b64)
const validLen = lens[0]
const placeHoldersLen = lens[1]
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
@ -90327,21 +90327,21 @@ function _byteLength (b64, validLen, placeHoldersLen) {
}
function toByteArray (b64) {
var tmp
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
let tmp
const lens = getLens(b64)
const validLen = lens[0]
const placeHoldersLen = lens[1]
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
const arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
var curByte = 0
let curByte = 0
// if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0
const len = placeHoldersLen > 0
? validLen - 4
: validLen
var i
let i
for (i = 0; i < len; i += 4) {
tmp =
(revLookup[b64.charCodeAt(i)] << 18) |
@ -90380,9 +90380,9 @@ function tripletToBase64 (num) {
}
function encodeChunk (uint8, start, end) {
var tmp
var output = []
for (var i = start; i < end; i += 3) {
let tmp
const output = []
for (let i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xFF0000) +
((uint8[i + 1] << 8) & 0xFF00) +
@ -90393,17 +90393,15 @@ function encodeChunk (uint8, start, end) {
}
function fromByteArray (uint8) {
var tmp
var len = uint8.length
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
var parts = []
var maxChunkLength = 16383 // must be multiple of 3
let tmp
const len = uint8.length
const extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
const parts = []
const maxChunkLength = 16383 // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(
uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)
))
for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
}
// pad the end with zeros, but make sure to not forget the extra bytes
@ -93364,8 +93362,8 @@ var __WEBPACK_AMD_DEFINE_RESULT__;;(function (globalObject) {
var base64 = __webpack_require__(/*! base64-js */ "./node_modules/base64-js/index.js")
var ieee754 = __webpack_require__(/*! ieee754 */ "./node_modules/ieee754/index.js")
var customInspectSymbol =
(typeof Symbol === 'function' && typeof Symbol.for === 'function')
? Symbol.for('nodejs.util.inspect.custom')
(typeof Symbol === 'function' && typeof Symbol['for'] === 'function') // eslint-disable-line dot-notation
? Symbol['for']('nodejs.util.inspect.custom') // eslint-disable-line dot-notation
: null
exports.Buffer = Buffer
@ -93469,7 +93467,7 @@ function from (value, encodingOrOffset, length) {
}
if (ArrayBuffer.isView(value)) {
return fromArrayLike(value)
return fromArrayView(value)
}
if (value == null) {
@ -93550,7 +93548,7 @@ function alloc (size, fill, encoding) {
if (fill !== undefined) {
// Only pay attention to encoding if it's a string. This
// prevents accidentally sending in a number that would
// be interpretted as a start offset.
// be interpreted as a start offset.
return typeof encoding === 'string'
? createBuffer(size).fill(fill, encoding)
: createBuffer(size).fill(fill)
@ -93617,6 +93615,14 @@ function fromArrayLike (array) {
return buf
}
function fromArrayView (arrayView) {
if (isInstance(arrayView, Uint8Array)) {
var copy = new Uint8Array(arrayView)
return fromArrayBuffer(copy.buffer, copy.byteOffset, copy.byteLength)
}
return fromArrayLike(arrayView)
}
function fromArrayBuffer (array, byteOffset, length) {
if (byteOffset < 0 || array.byteLength < byteOffset) {
throw new RangeError('"offset" is outside of buffer bounds')
@ -93756,12 +93762,20 @@ Buffer.concat = function concat (list, length) {
for (i = 0; i < list.length; ++i) {
var buf = list[i]
if (isInstance(buf, Uint8Array)) {
buf = Buffer.from(buf)
}
if (!Buffer.isBuffer(buf)) {
if (pos + buf.length > buffer.length) {
Buffer.from(buf).copy(buffer, pos)
} else {
Uint8Array.prototype.set.call(
buffer,
buf,
pos
)
}
} else if (!Buffer.isBuffer(buf)) {
throw new TypeError('"list" argument must be an Array of Buffers')
} else {
buf.copy(buffer, pos)
}
buf.copy(buffer, pos)
pos += buf.length
}
return buffer
@ -93843,7 +93857,7 @@ function slowToString (encoding, start, end) {
return ''
}
// Force coersion to uint32. This will also coerce falsey/NaN values to 0.
// Force coercion to uint32. This will also coerce falsey/NaN values to 0.
end >>>= 0
start >>>= 0
@ -94194,10 +94208,6 @@ function asciiWrite (buf, string, offset, length) {
return blitBuffer(asciiToBytes(string), buf, offset, length)
}
function latin1Write (buf, string, offset, length) {
return asciiWrite(buf, string, offset, length)
}
function base64Write (buf, string, offset, length) {
return blitBuffer(base64ToBytes(string), buf, offset, length)
}
@ -94253,11 +94263,9 @@ Buffer.prototype.write = function write (string, offset, length, encoding) {
return utf8Write(this, string, offset, length)
case 'ascii':
return asciiWrite(this, string, offset, length)
case 'latin1':
case 'binary':
return latin1Write(this, string, offset, length)
return asciiWrite(this, string, offset, length)
case 'base64':
// Warning: maxLength not taken into account in base64Write
@ -94300,10 +94308,13 @@ function utf8Slice (buf, start, end) {
while (i < end) {
var firstByte = buf[i]
var codePoint = null
var bytesPerSequence = (firstByte > 0xEF) ? 4
: (firstByte > 0xDF) ? 3
: (firstByte > 0xBF) ? 2
: 1
var bytesPerSequence = (firstByte > 0xEF)
? 4
: (firstByte > 0xDF)
? 3
: (firstByte > 0xBF)
? 2
: 1
if (i + bytesPerSequence <= end) {
var secondByte, thirdByte, fourthByte, tempCodePoint
@ -94424,7 +94435,8 @@ function hexSlice (buf, start, end) {
function utf16leSlice (buf, start, end) {
var bytes = buf.slice(start, end)
var res = ''
for (var i = 0; i < bytes.length; i += 2) {
// If bytes.length is odd, the last 8 bits must be ignored (same as node.js)
for (var i = 0; i < bytes.length - 1; i += 2) {
res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))
}
return res
@ -94466,6 +94478,7 @@ function checkOffset (offset, ext, length) {
if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
}
Buffer.prototype.readUintLE =
Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
@ -94481,6 +94494,7 @@ Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert)
return val
}
Buffer.prototype.readUintBE =
Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
@ -94497,24 +94511,28 @@ Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert)
return val
}
Buffer.prototype.readUint8 =
Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 1, this.length)
return this[offset]
}
Buffer.prototype.readUint16LE =
Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
return this[offset] | (this[offset + 1] << 8)
}
Buffer.prototype.readUint16BE =
Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
return (this[offset] << 8) | this[offset + 1]
}
Buffer.prototype.readUint32LE =
Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
@ -94525,6 +94543,7 @@ Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
(this[offset + 3] * 0x1000000)
}
Buffer.prototype.readUint32BE =
Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
@ -94642,6 +94661,7 @@ function checkInt (buf, value, offset, ext, max, min) {
if (offset + ext > buf.length) throw new RangeError('Index out of range')
}
Buffer.prototype.writeUintLE =
Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
@ -94661,6 +94681,7 @@ Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength,
return offset + byteLength
}
Buffer.prototype.writeUintBE =
Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
@ -94680,6 +94701,7 @@ Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength,
return offset + byteLength
}
Buffer.prototype.writeUint8 =
Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
value = +value
offset = offset >>> 0
@ -94688,6 +94710,7 @@ Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
return offset + 1
}
Buffer.prototype.writeUint16LE =
Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
@ -94697,6 +94720,7 @@ Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert
return offset + 2
}
Buffer.prototype.writeUint16BE =
Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
@ -94706,6 +94730,7 @@ Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert
return offset + 2
}
Buffer.prototype.writeUint32LE =
Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
@ -94717,6 +94742,7 @@ Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert
return offset + 4
}
Buffer.prototype.writeUint32BE =
Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
@ -94896,11 +94922,6 @@ Buffer.prototype.copy = function copy (target, targetStart, start, end) {
if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {
// Use built-in when available, missing from IE11
this.copyWithin(targetStart, start, end)
} else if (this === target && start < targetStart && targetStart < end) {
// descending copy from end
for (var i = len - 1; i >= 0; --i) {
target[i + targetStart] = this[i + start]
}
} else {
Uint8Array.prototype.set.call(
target,
@ -98143,6 +98164,7 @@ var NotIdle = /** @class */ (function (_super) {
/*! no static exports found */
/***/ (function(module, exports) {
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
exports.read = function (buffer, offset, isLE, mLen, nBytes) {
var e, m
var eLen = (nBytes * 8) - mLen - 1

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,9 @@ import {Transaction} from './transaction.model';
import {BigNumber} from 'bignumber.js';
export class Wallet {
stop_paginate: boolean;
open_from_exist: boolean;
updated = false;
wallet_id: number;
name: string;
pass: string;

View file

@ -198,6 +198,7 @@ export class BackendService {
};
}
const core_busy = Result.error_code === 'CORE_BUSY';
const Status = (Result.error_code === 'OK' || Result.error_code === 'TRUE');
if (!Status && Status !== undefined && Result.error_code !== undefined) {
@ -207,24 +208,44 @@ export class BackendService {
let res_error_code = false;
if (typeof Result === 'object' && 'error_code' in Result && Result.error_code !== 'OK' && Result.error_code !== 'TRUE' && Result.error_code !== 'FALSE') {
this.informerRun(Result.error_code, params, command);
res_error_code = Result.error_code;
if (core_busy) {
setTimeout( () => {
// this is will avoid update data when user
// on other wallet after CORE_BUSY (blink of data)
if (command !== 'get_recent_transfers') {
this.runCommand(command, params, callback);
} else {
const current_wallet_id = this.variablesService.currentWallet.wallet_id;
if (current_wallet_id === params.wallet_id) {
this.runCommand(command, params, callback);
}
}
}, 50);
} else {
this.informerRun(Result.error_code, params, command);
res_error_code = Result.error_code;
}
}
// if ( command === 'get_offers_ex' ){
// Service.printLog( "get_offers_ex offers count "+((data.offers)?data.offers.length:0) );
// }
if (typeof callback === 'function') {
callback(Status, data, res_error_code);
} else {
return data;
if (!core_busy) {
if (typeof callback === 'function') {
callback(Status, data, res_error_code);
} else {
return data;
}
}
}
private runCommand(command, params?, callback?) {
if (this.backendObject) {
if (command === 'get_recent_transfers') {
this.variablesService.get_recent_transfers = true;
}
const Action = this.backendObject[command];
if (!Action) {
BackendService.Debug(0, 'Run Command Error! Command "' + command + '" don\'t found in backendObject');
@ -232,6 +253,9 @@ export class BackendService {
const that = this;
params = (typeof params === 'string') ? params : JSONBigNumber.stringify(params);
if (params === undefined || params === '{}') {
if (command === 'get_recent_transfers') {
this.variablesService.get_recent_transfers = false;
}
Action(function (resultStr) {
that.commandDebug(command, params, resultStr);
return that.backendCallback(resultStr, params, callback, command);
@ -632,14 +656,14 @@ export class BackendService {
}
}
getRecentTransfers( id, offset, count,exclude_mining_txs, callback) {
getRecentTransfers( id, offset, count, exclude_mining_txs, callback) {
const params = {
wallet_id: id,
offset: offset,
count: count,
exclude_mining_txs: exclude_mining_txs
};
this.runCommand('get_recent_transfers', params, callback);
this.runCommand('get_recent_transfers', params, callback);
}
getPoolInfo(callback) {

View file

@ -2,6 +2,7 @@ import { Injectable, NgZone } from '@angular/core';
import { VariablesService } from './variables.service';
import { PaginationStore } from './pagination.store';
import * as _ from 'lodash';
import {Store} from 'store';
@Injectable({
providedIn: 'root',
@ -11,17 +12,15 @@ export class PaginationService {
constructor(
private variables: VariablesService,
private ngZone: NgZone,
private paginationStore: PaginationStore
private paginationStore: PaginationStore,
) { }
paginate(currentPage = 1) {
if (currentPage < 1) {
currentPage = 1;
} else if (currentPage > this.variables.currentWallet.totalPages) {
currentPage = this.variables.currentWallet.totalPages;
}
let startPage: number, endPage: number;
if (this.variables.currentWallet.totalPages <= this.variables.maxPages) {
startPage = 1;
@ -48,12 +47,13 @@ export class PaginationService {
});
}
getOffset() {
getOffset(walletID) {
const mining = this.variables.currentWallet.exclude_mining_txs;
const currentPage = (this.variables.currentWallet.currentPage);
let offset = (currentPage * this.variables.count);
let offset = ((currentPage - 1) * this.variables.count);
if (!mining) { return offset; }
const pages = this.paginationStore.value;
const value = this.paginationStore.value;
const pages = value.filter(item => item.walletID === walletID);
if (pages && pages.length) {
const max = _.maxBy(pages, 'page');
const isForward = this.paginationStore.isForward(pages, currentPage);
@ -66,4 +66,37 @@ export class PaginationService {
}
return offset;
}
calcPages(data) {
if (data.total_history_items && (data && data.history)) {
this.variables.currentWallet.totalPages = Math.ceil( data.total_history_items / this.variables.count);
this.variables.currentWallet.totalPages > this.variables.maxPages
? this.variables.currentWallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: this.variables.currentWallet.pages =
new Array(this.variables.currentWallet.totalPages).fill(1).map((value, index) => value + index);
} else if (this.variables.currentWallet.restore) {
this.variables.currentWallet.totalPages = Math.ceil( data.history.length / this.variables.count);
this.variables.currentWallet.totalPages > this.variables.maxPages
? this.variables.currentWallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: this.variables.currentWallet.pages =
new Array(this.variables.currentWallet.totalPages).fill(1).map((value, index) => value + index);
}
}
prepareHistory(data, status) {
if (status && (data && data.total_history_items)) {
this.variables.currentWallet.history.splice(0, this.variables.currentWallet.history.length);
this.ngZone.run(() => {
this.paginate(this.variables.currentWallet.currentPage);
if (data.history.length !== 0) {
this.variables.currentWallet.restore = false;
this.variables.currentWallet.total_history_item = data.total_history_items;
this.variables.currentWallet.prepareHistory(data.history);
if (this.variables.currentWallet.currentPage === 1 && data.unconfirmed) {
this.variables.currentWallet.prepareHistory(data.unconfirmed);
}
}
});
}
}
}

View file

@ -1,20 +1,17 @@
import {Injectable} from '@angular/core';
import {Observable, BehaviorSubject} from 'rxjs';
import {VariablesService} from './variables.service';
import * as _ from 'lodash';
export interface Pages {
page: number;
page: number;
offset: number;
walletID: number;
}
@Injectable({
providedIn: 'root'
})
export class PaginationStore {
constructor(
private variablesService: VariablesService
) {
}
) {}
private subject = new BehaviorSubject<Pages[]>(null);
pages$: Observable<Pages[]> = this.subject.asObservable();
@ -22,15 +19,13 @@ export class PaginationStore {
const max = _.maxBy(pages, 'page');
return !max || max.page < currentPage || max.page === currentPage;
}
setPage(pageNumber: number, offset: number) {
const pages = this.subject.getValue();
const current = (this.variablesService.currentWallet.currentPage);
const isForward = this.isForward(pages, current);
setPage(pageNumber: number, offset: number, walletID: number) {
let newPages: Pages[] = [];
const pages = this.subject.getValue();
if (pages && pages.length) {
newPages = pages.slice(0);
}
isForward ? newPages.push({page: pageNumber, offset}) : newPages.pop();
newPages.push({page: pageNumber, offset, walletID});
this.subject.next(newPages);
}

View file

@ -11,7 +11,9 @@ import {BigNumber} from 'bignumber.js';
providedIn: 'root'
})
export class VariablesService {
public request_on_in = {};
public stop_paginate = {};
public sync_started = false;
public digits = 12;
public appPass = '';
public appLogin = false;
@ -36,6 +38,7 @@ export class VariablesService {
progress_value: 0,
progress_value_text: '0'
};
public get_recent_transfers = false; // avoid of execute function before collback complete
public default_fee = '0.010000000000';
public default_fee_big = new BigNumber('10000000000');
@ -71,7 +74,7 @@ export class VariablesService {
public newContact: Contact = {name: null, address: null, notes: null};
public pattern = '^[a-zA-Z0-9_.\\\]\*\|\~\!\?\@\#\$\%\^\&\+\{\}\(\)\<\>\:\;\"\'\-\=\/\,\[\\\\]*$';
public after_sync_request: any = {};
getExpMedTsEvent = new BehaviorSubject(null);
getHeightAppEvent = new BehaviorSubject(null);
getHeightMaxEvent = new BehaviorSubject(null);
@ -162,6 +165,15 @@ export class VariablesService {
return null;
}
getNotLoadedWallet() {
for (let i = 0; i < this.wallets.length; i++) {
if (!this.wallets[i].loaded) {
return this.wallets[i];
}
}
return null;
}
startCountdown() {
this.idle.within(this.settings.appLockTime).start();
}

View file

@ -9,6 +9,7 @@ import {IntToMoneyPipe} from './_helpers/pipes/int-to-money.pipe';
import {BigNumber} from 'bignumber.js';
import {ModalService} from './_helpers/services/modal.service';
import {UtilsService} from './_helpers/services/utils.service';
import {Store} from 'store';
@Component({
selector: 'app-root',
@ -40,7 +41,8 @@ export class AppComponent implements OnInit, OnDestroy {
private ngZone: NgZone,
private intToMoneyPipe: IntToMoneyPipe,
private modalService: ModalService,
private utilsService: UtilsService
private utilsService: UtilsService,
private store: Store
) {
translate.addLangs(['en', 'fr', 'de', 'it', 'pt']);
translate.setDefaultLang('en');
@ -129,7 +131,6 @@ export class AppComponent implements OnInit, OnDestroy {
const wallet_state = data.wallet_state;
const is_mining = data.is_mining;
const wallet = this.variablesService.getWallet(data.wallet_id);
// 1-synch, 2-ready, 3 - error
if (wallet) {
this.ngZone.run(() => {
@ -157,21 +158,15 @@ export class AppComponent implements OnInit, OnDestroy {
if (wallet) {
this.ngZone.run(() => {
wallet.progress = (data.progress < 0) ? 0 : ((data.progress > 100) ? 100 : data.progress);
if (!this.variablesService.sync_started) {
this.variablesService.sync_started = true;
}
this.addToStore(wallet, true); // subscribe on data
if (wallet.progress === 0) {
wallet.loaded = false;
} else if (wallet.progress === 100) {
wallet.loaded = true;
if (wallet.total_history_item) {
wallet.totalPages = Math.ceil( wallet.total_history_item / this.variablesService.count);
wallet.totalPages > this.variablesService.maxPages
? wallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: wallet.pages = new Array(wallet.totalPages).fill(1).map((value, index) => value + index);
} else if (wallet.restore) {
wallet.totalPages = Math.ceil( wallet.history.length / this.variablesService.count);
wallet.totalPages > this.variablesService.maxPages
? wallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: wallet.pages = new Array(wallet.totalPages).fill(1).map((value, index) => value + index);
}
this.addToStore(wallet, false);
}
});
}
@ -722,6 +717,28 @@ export class AppComponent implements OnInit, OnDestroy {
}
}
addToStore(wallet, boolean) {
const value = this.store.value.sync;
if (value && value.length) {
const sync = value.filter(item => item.wallet_id === wallet.wallet_id);
if (sync && sync.length) {
const result = value.map(item => {
if (item.wallet_id === wallet.wallet_id) {
return {sync: boolean, wallet_id: wallet.wallet_id};
} else {
return item;
}
});
this.store.set('sync', result);
} else {
value.push({sync: boolean, wallet_id: wallet.wallet_id});
this.store.set('sync', value);
}
} else {
this.store.set('sync', [{sync: boolean, wallet_id: wallet.wallet_id}]);
}
}
ngOnDestroy() {
if (this.intervalUpdateContractsState) {
clearInterval(this.intervalUpdateContractsState);

View file

@ -37,6 +37,11 @@ import { BackendService } from './_helpers/services/backend.service';
import { ModalService } from './_helpers/services/modal.service';
import { PaginationStore } from './_helpers/services/pagination.store';
// SERVICES
// Feature module
import { Store } from 'store';
// Feature module
import { MoneyToIntPipe } from './_helpers/pipes/money-to-int.pipe';
import { IntToMoneyPipe } from './_helpers/pipes/int-to-money.pipe';
import { HistoryTypeMessagesPipe } from './_helpers/pipes/history-type-messages.pipe';
@ -146,6 +151,7 @@ export function highchartsFactory() {
ContextMenuModule.forRoot()
],
providers: [
Store,
BackendService,
ModalService,
PaginationStore,

View file

@ -64,9 +64,16 @@
}
&.send {
.status-transaction {
mask: url(../../assets/icons/send.svg) no-repeat center;
background-color: transparent;
}
.status-transaction::after {
display: block;
content:'';
background:url("../../assets/icons/send-red.svg") no-repeat center;
width: 13px;
height: 13px;
}
}

View file

@ -125,10 +125,12 @@ export class OpenWalletModalComponent implements OnInit {
}
skipWallet() {
if (this.wallets.length) {
this.wallets.splice(0, 1);
this.ngOnInit();
}
this.ngZone.run(() => {
if (this.wallets.length) {
this.wallets.splice(0, 1);
this.ngOnInit();
}
});
}
}

View file

@ -98,6 +98,7 @@ export class OpenWalletComponent implements OnInit, OnDestroy {
);
new_wallet.alias = this.backend.getWalletAlias(new_wallet.address);
new_wallet.currentPage = 1;
new_wallet.open_from_exist = true;
new_wallet.exclude_mining_txs = false;
new_wallet.is_auditable = open_data['wi'].is_auditable;
new_wallet.is_watch_only = open_data['wi'].is_watch_only;

View file

@ -121,6 +121,8 @@ export class RestoreWalletComponent implements OnInit {
}
runWallet() {
// add flag when wallet was restored form seed
this.variablesService.after_sync_request[this.wallet.id] = true;
let exists = false;
this.variablesService.wallets.forEach((wallet) => {
if (wallet.address === this.variablesService.opening_wallet.address) {

View file

@ -3,7 +3,7 @@
<h3>{{ 'SIDEBAR.TITLE' | translate }}</h3><button (click)="goMainPage()">{{ 'SIDEBAR.ADD_NEW' | translate }}</button>
</div>
<div class="sidebar-accounts-list scrolled-content">
<div class="sidebar-account" *ngFor="let wallet of variablesService.wallets" [class.active]="wallet?.wallet_id === walletActive" [routerLink]="['/wallet/' + wallet.wallet_id + '/history']">
<div class="sidebar-account" *ngFor="let wallet of variablesService.wallets" [class.active]="wallet?.wallet_id === walletActive" [routerLink]="['/wallet/' + wallet.wallet_id + '/history']" [queryParams]="{sidenav: true}">
<div class="wallet-type" (click)="goToAuditableWalletHelpPage($event)">
<div class="content auditable" *ngIf="wallet.is_auditable && !wallet.is_watch_only">
Auditable

View file

@ -85,7 +85,7 @@
flex-direction: column;
flex: 1 1 auto;
margin: 0 -3rem;
overflow-y: overlay;
overflow-y: auto;
.sidebar-account {
position: relative;
@ -112,15 +112,20 @@
.icon {
width: 1.3rem;
height: 1.3rem;
&.close-wallet {
mask: url(../../assets/icons/close-wallet.svg) no-repeat center;
background-color: transparent;
}
&.close-wallet::after {
display: block;
content:'';
background:url("../../assets/icons/close-wallet-blue.svg") no-repeat center;
width: 13px;
height: 13px;
}
}
}
}
.sidebar-account-row {
display: flex;
align-items: center;

View file

@ -51,20 +51,39 @@
<div *ngIf="activeTab === 'history'" class="pagination-wrapper">
<div class="pagination">
<div>
<button [disabled]="variablesService.currentWallet.currentPage === 1" (click)="setPage(variablesService.currentWallet.currentPage - 1)"><</button>
<button
[ngClass]="{'disabled': sync_started || wallet}"
[disabled]="variablesService.currentWallet.currentPage === 1 || sync_started || wallet"
(click)="setPage(variablesService.currentWallet.currentPage - 1)">
<
</button>
<ng-container *ngIf="!mining">
<button *ngFor="let page of variablesService.currentWallet.pages" [ngClass]="{ 'active': variablesService.currentWallet.currentPage === page }"
<button [disabled]="sync_started || wallet"
*ngFor="let page of variablesService.currentWallet.pages"
[ngClass]="{ 'active': variablesService.currentWallet.currentPage === page,'disabled': sync_started || wallet }"
(click)="setPage(page)">{{page}}</button>
</ng-container>
<ng-container *ngIf="mining">
<button [ngClass]="{ 'active': variablesService.currentWallet.currentPage }"
(click)="setPage(variablesService.currentWallet.currentPage)">{{variablesService.currentWallet.currentPage}}</button>
<button [ngClass]="{ 'active': variablesService.currentWallet.currentPage, 'disabled': sync_started || wallet}"
[disabled]="stop_paginate || sync_started || wallet"
(click)="setPage(variablesService.currentWallet.currentPage)">
{{variablesService.currentWallet.currentPage}}
</button>
</ng-container>
<button [disabled]="variablesService.currentWallet.currentPage === variablesService.currentWallet.totalPages" (click)="setPage(variablesService.currentWallet.currentPage + 1)">></button>
<button
[disabled]="stop_paginate || sync_started || wallet"
[ngClass]="{'disabled': sync_started || wallet}"
(click)="setPage(variablesService.currentWallet.currentPage + 1)">
>
</button>
</div>
<div class="mining-transaction-switch">
<span class="switch-text">Hide mining transactions</span>
<div class="switch" (click)="toggleMiningTransactions(); $event.stopPropagation()">
<div class="switch" [ngClass]="{'disabled': sync_started || wallet}" (click)="toggleMiningTransactions(); $event.stopPropagation()">
<span class="option" *ngIf="mining">{{ 'STAKING.SWITCH.ON' | translate }}</span>
<span class="circle" [class.on]="mining" [class.off]="!mining"></span>
<span class="option" *ngIf="!mining">{{ 'STAKING.SWITCH.OFF' | translate }}</span>

View file

@ -254,6 +254,14 @@
width: 2.5rem;
height: 2.5rem;
font-size: 1.2rem;
transition: all 0.3s;
}
.disabled {
background-color: #18202a;
color: #e0e0e0;
opacity: 0.3;
cursor: default;
transition: all 0.3s;
}
}
}

View file

@ -4,12 +4,15 @@ import { VariablesService } from '../_helpers/services/variables.service';
import { BackendService } from '../_helpers/services/backend.service';
import { TranslateService } from '@ngx-translate/core';
import { IntToMoneyPipe } from '../_helpers/pipes/int-to-money.pipe';
import { Subscription } from 'rxjs';
import {Subscription} from 'rxjs';
import { LOCKED_BALANCE_HELP_PAGE } from '../_shared/constants';
import icons from '../../assets/icons/icons.json';
import { PaginationService } from '../_helpers/services/pagination.service';
import { PaginationStore } from '../_helpers/services/pagination.store';
import {PaginationService} from '../_helpers/services/pagination.service';
import {PaginationStore} from '../_helpers/services/pagination.store';
import {Store, Sync} from 'store';
import {Wallet} from '../_helpers/models/wallet.model';
import {distinctUntilChanged, filter} from 'rxjs/operators';
@Component({
selector: 'app-wallet',
@ -25,9 +28,11 @@ export class WalletComponent implements OnInit, OnDestroy {
copyAnimationTimeout;
balanceTooltip;
activeTab = 'history';
public mining:boolean = false;
public mining = false;
public currentPage = 1;
wallet: Wallet;
sync_started = false;
stop_paginate = false;
@ViewChild('scrolledContent') private scrolledContent: ElementRef;
@ -88,6 +93,7 @@ export class WalletComponent implements OnInit, OnDestroy {
}
];
aliasSubscription: Subscription;
walletsSubscription: Subscription;
constructor(
private route: ActivatedRoute,
@ -98,21 +104,81 @@ export class WalletComponent implements OnInit, OnDestroy {
private translate: TranslateService,
private intToMoneyPipe: IntToMoneyPipe,
private pagination: PaginationService,
private paginationStore: PaginationStore
private paginationStore: PaginationStore,
private store: Store,
) { }
ngOnInit() {
this.subRouting1 = this.route.params.subscribe(params => {
// set current wallet only by user click to avoid after sync show synchronized data
this.walletID = +params['id'];
this.variablesService.setCurrentWallet(this.walletID);
this.walletsSubscription = this.store.select('sync').pipe(
filter(Boolean),
distinctUntilChanged(),
).subscribe(value => {
const data = value.filter((item: Sync) => item.wallet_id === this.walletID)[0];
if (data && !data.sync) {
let in_progress;
const values = this.store.value.sync;
if (values && values.length) {
in_progress = values.filter(item => item.sync);
this.variablesService.sync_started = !!(in_progress && in_progress.length);
if (!in_progress) {
this.variablesService.sync_started = false;
}
} else {
this.variablesService.sync_started = false;
}
}
let restore = false;
if (this.variablesService.after_sync_request.hasOwnProperty(this.walletID)) {
restore = this.variablesService.after_sync_request[this.walletID];
}
if (!this.variablesService.sync_started && restore && this.walletID === (data && data.wallet_id)) {
this.wallet = this.variablesService.getNotLoadedWallet();
if (this.wallet) {
this.tick();
}
// if this is was restore wallet and it was selected on moment when sync completed
this.getRecentTransfers();
this.variablesService.after_sync_request[this.walletID] = false;
}
});
let after_sync_request = false;
if (this.variablesService.after_sync_request.hasOwnProperty(this.walletID)) {
after_sync_request = this.variablesService.after_sync_request[this.walletID];
}
if (after_sync_request && !this.variablesService.sync_started) {
// if user click on the wallet at the first time after restore.
this.getRecentTransfers();
}
if (this.variablesService.stop_paginate.hasOwnProperty(this.walletID)) {
this.stop_paginate = this.variablesService.stop_paginate[this.walletID];
} else {
this.stop_paginate = false;
}
// this will hide pagination a bit earlier
this.wallet = this.variablesService.getNotLoadedWallet();
if (this.wallet) {
this.tick();
}
this.scrolledContent.nativeElement.scrollTop = 0;
clearTimeout(this.copyAnimationTimeout);
this.copyAnimation = false;
this.mining = this.variablesService.currentWallet.exclude_mining_txs;
if (this.variablesService.wallets.length === 1) {
this.walletID = +params['id'];
this.variablesService.setCurrentWallet(this.walletID);
}
});
this.subRouting2 = this.router.events.subscribe(val => {
if (val instanceof RoutesRecognized) {
this.activeTab = val.urlAfterRedirects.split('/').pop();
this.activeTab = val.urlAfterRedirects.replace('?sidenav=true', '').split('/').pop();
if (val.state.root.firstChild && val.state.root.firstChild.firstChild) {
for (let i = 0; i < this.tabs.length; i++) {
this.tabs[i].active = (this.tabs[i].link === '/' + val.state.root.firstChild.firstChild.url[0].path);
@ -138,10 +204,26 @@ export class WalletComponent implements OnInit, OnDestroy {
}
});
}
resetPaginationValues() {
this.ngZone.run(() => {
const total_history_item = this.variablesService.currentWallet.total_history_item;
const count = this.variablesService.count;
this.variablesService.currentWallet.totalPages = Math.ceil( total_history_item / count);
this.variablesService.currentWallet.exclude_mining_txs = this.mining;
this.variablesService.currentWallet.currentPage = 1;
if (!this.variablesService.currentWallet.totalPages) {
this.variablesService.currentWallet.totalPages = 1;
}
this.variablesService.currentWallet.totalPages > this.variablesService.maxPages
? this.variablesService.currentWallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: this.variablesService.currentWallet.pages = new Array(this.variablesService.currentWallet.totalPages).fill(1).map((value, index) => value + index);
})
}
changeTab(index) {
if (((this.tabs[index].link === '/send' || this.tabs[index].link === '/contracts' || this.tabs[index].link === '/staking') && (this.variablesService.daemon_state !== 2 || !this.variablesService.currentWallet.loaded))
|| ((this.tabs[index].link === '/send' || this.tabs[index].link === '/contracts') && this.variablesService.currentWallet.is_watch_only && this.variablesService.currentWallet.is_auditable)) {
|| ((this.tabs[index].link === '/send' || this.tabs[index].link === '/contracts') && this.variablesService.currentWallet.is_watch_only && this.variablesService.currentWallet.is_auditable)) {
return;
}
this.tabs.forEach((tab) => {
@ -195,49 +277,84 @@ export class WalletComponent implements OnInit, OnDestroy {
}
public setPage(pageNumber: number) {
// this is will allow pagination for wallets that was open from existed wallets'
if (this.variablesService.currentWallet.open_from_exist && !this.variablesService.currentWallet.updated) {
this.variablesService.get_recent_transfers = false;
this.variablesService.currentWallet.updated = true;
}
if (pageNumber === this.variablesService.currentWallet.currentPage) {
return;
}
this.variablesService.currentWallet.currentPage = pageNumber;
this.getRecentTransfers();
// if not running get_recent_transfers callback
if (!this.variablesService.get_recent_transfers) {
this.variablesService.currentWallet.currentPage = pageNumber;
}
if (!this.variablesService.get_recent_transfers) {
this.getRecentTransfers();
}
}
toggleMiningTransactions() {
this.mining = !this.mining;
this.variablesService.currentWallet.exclude_mining_txs = this.mining;
this.variablesService.currentWallet.currentPage = 1;
this.getRecentTransfers();
if (!this.variablesService.sync_started && !this.wallet) {
const value = this.paginationStore.value;
if (!value) {
this.paginationStore.setPage(1, 0, this.walletID); // add back page for the first page
} else {
const pages = value.filter(item => item.walletID === this.walletID);
if (!pages.length) {
this.paginationStore.setPage(1, 0, this.walletID); // add back page for the first page
}
}
this.mining = !this.mining;
this.resetPaginationValues();
this.getRecentTransfers();
}
}
tick() {
const walletInterval = setInterval(() => {
this.wallet = this.variablesService.getNotLoadedWallet();
if (!this.wallet) {
clearInterval(walletInterval);
}
}, 1000);
}
getRecentTransfers () {
const offset = this.pagination.getOffset();
const pages = this.paginationStore.value;
if (!pages) {
this.paginationStore.setPage(1, 40); // add back page for the first page
}
const offset = this.pagination.getOffset(this.walletID);
const value = this.paginationStore.value;
const pages = value ? value.filter(item => item.walletID === this.walletID) : [];
this.backend.getRecentTransfers(
this.walletID,
offset,
this.variablesService.count, this.variablesService.currentWallet.exclude_mining_txs, (status, data) => {
const page = (this.variablesService.currentWallet.currentPage + 1);
this.paginationStore.setPage(page, data.last_item_index); // add back page for current page
if (data.history.length < this.variablesService.count) {
this.variablesService.currentWallet.totalPages = (page - 1); // stop paginate
const isForward = this.paginationStore.isForward(pages, this.variablesService.currentWallet.currentPage);
if (this.mining && isForward && pages && pages.length === 1) {
this.variablesService.currentWallet.currentPage = 1; // set init page after navigation back
}
if (status && data.total_history_items) {
this.variablesService.currentWallet.history.splice(0, this.variablesService.currentWallet.history.length);
this.ngZone.run(() => {
this.pagination.paginate(this.variablesService.currentWallet.currentPage);
if (data.history.length !== 0) {
this.variablesService.currentWallet.restore = false;
this.variablesService.currentWallet.total_history_item = data.total_history_items;
this.variablesService.currentWallet.prepareHistory(data.history);
if (this.variablesService.currentWallet.currentPage === 1 && data.unconfirmed) {
this.variablesService.currentWallet.prepareHistory(data.unconfirmed);
}
}
});
const history = (data && data.history);
this.variablesService.stop_paginate[this.walletID] = history && history.length < this.variablesService.count || !history;
this.stop_paginate = this.variablesService.stop_paginate[this.walletID];
if (!this.variablesService.stop_paginate[this.walletID]) {
const page = this.variablesService.currentWallet.currentPage + 1;
if (isForward && this.mining && history && history.length === this.variablesService.count) {
this.paginationStore.setPage(page, data.last_item_index, this.walletID); // add back page for current page
}
}
this.pagination.calcPages(data);
this.pagination.prepareHistory(data, status);
this.ngZone.run(() => {
this.variablesService.get_recent_transfers = false;
if (this.variablesService.after_sync_request.hasOwnProperty(this.walletID)) {
// this is will complete get_recent_transfers request
// this will switch of
this.variablesService.after_sync_request[this.walletID] = false;
}
});
});
}
@ -246,6 +363,9 @@ export class WalletComponent implements OnInit, OnDestroy {
this.subRouting2.unsubscribe();
this.queryRouting.unsubscribe();
this.aliasSubscription.unsubscribe();
if (this.walletsSubscription) {
this.walletsSubscription.unsubscribe();
}
clearTimeout(this.copyAnimationTimeout);
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4db1ff;}
</style>
<polygon class="st0" points="31.9,4.9 29.1,2.1 17,14.2 4.9,2.1 2.1,4.9 14.2,17 2.1,29.1 4.9,31.9 17,19.8 29.1,31.9 31.9,29.1
19.8,17 "/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View file

@ -0,0 +1,36 @@
import { pluck, distinctUntilChanged } from 'rxjs/operators';
import { Observable, BehaviorSubject } from 'rxjs';
import { Wallet } from './app/_helpers/models/wallet.model';
export interface Sync {
sync: boolean;
wallet_id: number;
}
export interface State {
wallets: Wallet[];
sync: Sync[];
[key: string]: any;
}
const state: State = {
wallets: undefined,
sync: undefined,
};
export class Store {
private subject = new BehaviorSubject<State>(state);
private store = this.subject.asObservable().pipe(distinctUntilChanged());
get value() {
return this.subject.value;
}
select<T>(name: string): Observable<T> {
return this.store.pipe(pluck(name));
}
// tslint:disable-next-line:no-shadowed-variable
set(name: string, state: any) {
this.subject.next({ ...this.value, [name]: state });
}
}

View file

@ -2,6 +2,11 @@
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"paths": {
"store": [
"src/store.ts"
]
},
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,

View file

@ -46,13 +46,14 @@ namespace
const command_line::arg_descriptor<std::string> arg_password = {"password", "Wallet password", "", true};
const command_line::arg_descriptor<bool> arg_dont_refresh = { "no-refresh", "Do not refresh after load", false, true };
const command_line::arg_descriptor<bool> arg_dont_set_date = { "no-set-creation-date", "Do not set wallet creation date", false, false };
const command_line::arg_descriptor<bool> arg_print_brain_wallet = { "print-brain-wallet", "Print to conosole brain wallet", false, false };
const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of default", 0};
const command_line::arg_descriptor<uint32_t> arg_log_level = {"set-log", "", 0, true};
const command_line::arg_descriptor<bool> arg_do_pos_mining = { "do-pos-mining", "Do PoS mining", false, false };
const command_line::arg_descriptor<std::string> arg_pos_mining_reward_address = { "pos-mining-reward-address", "Block reward will be sent to the giving address if specified", "" };
const command_line::arg_descriptor<std::string> arg_restore_wallet = { "restore-wallet", "Restore wallet from seed phrase or tracking seed and save it to <arg>", "" };
const command_line::arg_descriptor<bool> arg_offline_mode = { "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)", false, true };
const command_line::arg_descriptor<std::string> arg_scan_for_wallet = { "scan-for-wallet", "", "", true };
const command_line::arg_descriptor<std::string> arg_addr_to_compare = { "addr-to-compare", "", "", true };
const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""};
@ -178,7 +179,6 @@ bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<st
simple_wallet::simple_wallet()
: m_daemon_port(0),
m_print_brain_wallet(false),
m_do_refresh_after_load(false),
m_do_not_set_date(false),
m_do_pos_mining(false),
@ -307,10 +307,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
m_do_refresh_after_load = false;
}
if (command_line::has_arg(vm, arg_print_brain_wallet))
{
m_print_brain_wallet = true;
}
if (!m_generate_new.empty())
{
@ -336,9 +332,26 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << "failed to read seed phrase";
return false;
}
bool looks_like_tracking_seed = restore_seed_container.password().find(':') != std::string::npos;
bool r = restore_wallet(m_restore_wallet, restore_seed_container.password(), pwd_container.password(), looks_like_tracking_seed);
tools::password_container seed_password_container;
if (!looks_like_tracking_seed)
{
bool is_password_protected = false;
bool sr = account_base::is_seed_password_protected(restore_seed_container.password(), is_password_protected);
if (!sr)
{
fail_msg_writer() << "failed to parse seed phrase";
return false;
}
if (is_password_protected && !seed_password_container.read_password("This seed is secured, to use it please enter the password:\n"))
{
fail_msg_writer() << "failed to read seed phrase";
return false;
}
}
bool r = restore_wallet(m_restore_wallet, restore_seed_container.password(), pwd_container.password(), looks_like_tracking_seed, seed_password_container.password());
CHECK_AND_ASSERT_MES(r, false, "wallet restoring failed");
}
else
@ -400,10 +413,6 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas
if (m_do_not_set_date)
m_wallet->reset_creation_time(0);
if (m_print_brain_wallet)
{
std::cout << "Seed phrase (keep it in secret): " << m_wallet->get_account().get_seed_phrase() << std::endl << std::flush;
}
}
catch (const std::exception& e)
@ -422,7 +431,7 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::string& seed_or_tracking_seed, const std::string& password, bool tracking_wallet)
bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::string& seed_or_tracking_seed, const std::string& password, bool tracking_wallet, const std::string& seed_password)
{
m_wallet_file = wallet_file;
@ -434,13 +443,13 @@ bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::st
if (tracking_wallet)
{
// auditable watch-only aka tracking wallet
m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_tracking_seed, true);
m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_tracking_seed, true, "");
message_writer(epee::log_space::console_color_white, true) << "Tracking wallet restored: " << m_wallet->get_account().get_public_address_str();
}
else
{
// normal or auditable wallet
m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_tracking_seed, false);
m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_tracking_seed, false, seed_password);
message_writer(epee::log_space::console_color_white, true) << (m_wallet->is_auditable() ? "Auditable wallet" : "Wallet") << " restored: " << m_wallet->get_account().get_public_address_str();
std::cout << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().view_secret_key) << std::endl << std::flush;
if (m_wallet->is_auditable())
@ -454,7 +463,11 @@ bool simple_wallet::restore_wallet(const std::string& wallet_file, const std::st
fail_msg_writer() << "failed to restore wallet, check your " << (tracking_wallet ? "tracking seed!" : "seed phrase!") << ENDL << e.what();
return false;
}
catch (...)
{
fail_msg_writer() << "failed to restore wallet, check your " << (tracking_wallet ? "tracking seed!" : "seed phrase!") << ENDL;
return false;
}
m_wallet->init(m_daemon_address);
success_msg_writer() <<
@ -482,9 +495,6 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa
m_wallet->load(epee::string_encoding::utf8_to_wstring(m_wallet_file), password);
message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_auditable() ? " auditable" : "") << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str();
if (m_print_brain_wallet)
std::cout << "Seed phrase (keep it in secret): " << m_wallet->get_account().get_seed_phrase() << std::endl << std::flush;
break;
}
catch (const tools::error::wallet_load_notice_wallet_restored& /*e*/)
@ -1359,9 +1369,24 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_seed(const std::vector<std::string> &args)
{
success_msg_writer() << "Here's your wallet's seed phrase. Write it down and keep in a safe place.";
success_msg_writer(true) << "Anyone who knows the following 26 words can access your wallet:";
std::cout << m_wallet->get_account().get_seed_phrase() << std::endl << std::flush;
success_msg_writer() << "Please enter a password to secure this seed. Securing your seed is HIGHLY recommended. Leave password blank to stay unsecured.";
success_msg_writer(true) << "Remember, restoring a wallet from Secured Seed can only be done if you know its password.";
tools::password_container seed_password_container1;
if (!seed_password_container1.read_password("Enter seed password: "))
{
return false;
}
tools::password_container seed_password_container2;
if (!seed_password_container2.read_password("Confirm seed password: "))
{
return false;
}
if(seed_password_container1.password() != seed_password_container2.password())
{
std::cout << "Error: password mismatch. Please make sure you entered the correct password and confirmed it" << std::endl << std::flush;
}
std::cout << m_wallet->get_account().get_seed_phrase(seed_password_container2.password()) << std::endl << std::flush;
return true;
}
//----------------------------------------------------------------------------------------------------
@ -1712,6 +1737,109 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
uint64_t
get_tick_count__()
{
using namespace std::chrono;
return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
}
//----------------------------------------------------------------------------------------------------
bool check_if_file_looks_like_a_wallet(const std::wstring& wallet_)
{
std::string keys_buff;
boost::system::error_code e;
bool exists = boost::filesystem::exists(wallet_, e);
if (e || !exists)
return false;
boost::filesystem::ifstream data_file;
data_file.open(wallet_, std::ios_base::binary | std::ios_base::in);
if (data_file.fail())
return false;
tools::wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh);
data_file.read((char*)&wbh, sizeof(wbh));
if (data_file.fail())
{
return false;
}
if (wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2)
{
return false;
}
//std::cout << "\r \r";
LOG_PRINT_L0("Found wallet file: " << epee::string_encoding::convert_to_ansii(wallet_));
return false;
}
bool search_for_wallet_file(const std::wstring &search_here/*, const std::string &addr_to_compare*/)
{
if (search_here == L"/proc" || search_here == L"/bin" || search_here == L"/dev" || search_here == L"/etc"
|| search_here == L"/lib" || search_here == L"/lib64" || search_here == L"/proc" || search_here == L"/run"
|| search_here == L"/sbin" || search_here == L"/srv" || search_here == L"/sys" || search_here == L"/usr"
|| search_here == L"/var")
{
LOG_PRINT_L0("Skiping " << epee::string_encoding::convert_to_ansii(search_here));
return false;
}
//LOG_PRINT_L0("FOLDER: " << epee::string_encoding::convert_to_ansii(search_here));
static uint64_t last_tick = 0;
using namespace boost::filesystem;
//recursive_directory_iterator dir(search_here), end;
try
{
for (auto& dir : boost::make_iterator_range(directory_iterator(search_here), {}))
{
boost::system::error_code ec = AUTO_VAL_INIT(ec);
bool r = boost::filesystem::is_directory(dir.path(), ec);
if (r)
{
if (get_tick_count__() - last_tick > 300)
{
last_tick = get_tick_count__();
//std::cout << "\r \r ->" << dir.path();
}
bool r = search_for_wallet_file(dir.path().wstring());
if (r)
return true;
}
else
{
boost::system::error_code ec = AUTO_VAL_INIT(ec);
bool r = boost::filesystem::is_regular_file(dir.path(), ec);
if (!r)
{
//LOG_PRINT_L0("Skiping as not regular: " << epee::string_encoding::convert_to_ansii(dir.path().wstring()));
return false;
}
//LOG_PRINT_L0("FILE: " << dir.path().string());
std::wstring pa = dir.path().wstring();
r = check_if_file_looks_like_a_wallet(pa);
if (r)
return true;
}
}
}
catch (std::exception& /* ex*/)
{
//std::cout << "\r \r";
LOG_PRINT_CYAN("Skip: " << search_here, LOG_LEVEL_0);
return false;
}
return false;
}
//----------------------------------------------------------------------------------------------------
#ifdef WIN32
int wmain( int argc, wchar_t* argv_w[ ], wchar_t* envp[ ] )
@ -1763,14 +1891,14 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_params, arg_log_level);
command_line::add_arg(desc_params, arg_dont_refresh);
command_line::add_arg(desc_params, arg_dont_set_date);
command_line::add_arg(desc_params, arg_print_brain_wallet);
command_line::add_arg(desc_params, arg_do_pos_mining);
command_line::add_arg(desc_params, arg_pos_mining_reward_address);
command_line::add_arg(desc_params, arg_restore_wallet);
command_line::add_arg(desc_params, arg_offline_mode);
command_line::add_arg(desc_params, command_line::arg_log_file);
command_line::add_arg(desc_params, command_line::arg_log_level);
command_line::add_arg(desc_params, arg_scan_for_wallet);
command_line::add_arg(desc_params, arg_addr_to_compare);
tools::wallet_rpc_server::init_options(desc_params);
@ -1827,6 +1955,17 @@ int main(int argc, char* argv[])
log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, command_line::arg_log_level));
}
if (command_line::has_arg(vm, arg_scan_for_wallet))
{
log_space::log_singletone::add_logger(LOGGER_CONSOLE, nullptr, nullptr, LOG_LEVEL_4);
LOG_PRINT_L0("Searching from "
<< epee::string_encoding::convert_to_ansii(command_line::get_arg(vm, arg_scan_for_wallet)));
search_for_wallet_file(epee::string_encoding::convert_to_unicode(command_line::get_arg(vm, arg_scan_for_wallet)));
return EXIT_SUCCESS;
}
bool offline_mode = command_line::get_arg(vm, arg_offline_mode);
if (command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port))

View file

@ -46,7 +46,7 @@ namespace currency
bool new_wallet(const std::string &wallet_file, const std::string& password, bool create_auditable_wallet);
bool open_wallet(const std::string &wallet_file, const std::string& password);
bool restore_wallet(const std::string& wallet_file, const std::string& seed_or_tracking_seed, const std::string& password, bool tracking_wallet);
bool restore_wallet(const std::string& wallet_file, const std::string& seed_or_tracking_seed, const std::string& password, bool tracking_wallet, const std::string& seed_password);
bool close_wallet();
bool help(const std::vector<std::string> &args = std::vector<std::string>());
@ -163,7 +163,6 @@ namespace currency
int m_daemon_port;
bool m_do_refresh_after_load;
bool m_do_not_set_date;
bool m_print_brain_wallet;
bool m_do_pos_mining;
bool m_offline_mode;
std::string m_restore_wallet;

View file

@ -8,6 +8,6 @@
#define PROJECT_REVISION "7"
#define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION
#define PROJECT_VERSION_BUILD_NO 105
#define PROJECT_VERSION_BUILD_NO 110
#define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO)
#define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]"

View file

@ -400,13 +400,13 @@ namespace plain_wallet
return epee::serialization::store_t_to_json(err_result);
}
std::string restore(const std::string& seed, const std::string& path, const std::string& password)
std::string restore(const std::string& seed, const std::string& path, const std::string& password, const std::string& seed_password)
{
GET_INSTANCE_PTR(inst_ptr);
std::string full_path = get_wallets_folder() + path;
epee::json_rpc::response<view::open_wallet_response, epee::json_rpc::dummy_error> ok_response = AUTO_VAL_INIT(ok_response);
std::string rsp = inst_ptr->gwm.restore_wallet(epee::string_encoding::convert_to_unicode(full_path), password, seed, ok_response.result);
std::string rsp = inst_ptr->gwm.restore_wallet(epee::string_encoding::convert_to_unicode(full_path), password, seed, seed_password, ok_response.result);
if (rsp == API_RETURN_CODE_OK || rsp == API_RETURN_CODE_FILE_RESTORED)
{
if (rsp == API_RETURN_CODE_FILE_RESTORED)
@ -526,7 +526,7 @@ namespace plain_wallet
}
async_callback = [job_id, rwr]()
{
std::string res = restore(rwr.restore_key, rwr.path, rwr.pass);
std::string res = restore(rwr.seed_phrase, rwr.path, rwr.pass, rwr.seed_pass);
put_result(job_id, res);
};
}

View file

@ -27,7 +27,7 @@ namespace plain_wallet
std::string get_connectivity_status();
std::string open(const std::string& path, const std::string& password);
std::string restore(const std::string& seed, const std::string& path, const std::string& password);
std::string restore(const std::string& seed, const std::string& path, const std::string& password, const std::string& seed_password);
std::string generate(const std::string& path, const std::string& password);
std::string get_opened_wallets();

View file

@ -272,6 +272,19 @@ public:
END_KV_SERIALIZE_MAP()
};
struct request_get_smart_wallet_info
{
uint64_t wallet_id;
std::string seed_password;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(wallet_id)
KV_SERIALIZE(seed_password)
END_KV_SERIALIZE_MAP()
};
struct response_mining_estimate
{
uint64_t final_amount;
@ -429,16 +442,43 @@ public:
END_KV_SERIALIZE_MAP()
};
struct is_valid_restore_wallet_text_param
{
std::string seed_phrase;
std::string seed_password;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(seed_phrase)
KV_SERIALIZE(seed_password)
END_KV_SERIALIZE_MAP()
};
struct seed_phrase_info
{
bool syntax_correct;
bool require_password;
bool hash_sum_matched;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(syntax_correct)
KV_SERIALIZE(require_password)
KV_SERIALIZE(hash_sum_matched)
END_KV_SERIALIZE_MAP()
};
struct restore_wallet_request
{
std::string pass;
std::string seed_pass;
std::string path;
std::string restore_key;
std::string seed_phrase;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(pass)
KV_SERIALIZE(path)
KV_SERIALIZE(restore_key)
KV_SERIALIZE(seed_pass)
KV_SERIALIZE(seed_phrase)
END_KV_SERIALIZE_MAP()
};
@ -587,12 +627,10 @@ public:
struct get_restore_info_response
{
std::string restore_key;
std::string error_code;
std::string seed_phrase;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(restore_key)
KV_SERIALIZE(error_code)
KV_SERIALIZE(seed_phrase)
END_KV_SERIALIZE_MAP()
};

View file

@ -2347,7 +2347,7 @@ void wallet2::generate(const std::wstring& path, const std::string& pass, bool a
store();
}
//----------------------------------------------------------------------------------------------------
void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet)
void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password)
{
bool r = false;
clear();
@ -2363,7 +2363,7 @@ void wallet2::restore(const std::wstring& path, const std::string& pass, const s
}
else
{
r = m_account.restore_from_seed_phrase(seed_or_tracking_seed);
r = m_account.restore_from_seed_phrase(seed_or_tracking_seed, seed_password);
init_log_prefix();
THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file));
}

View file

@ -470,7 +470,7 @@ namespace tools
void assign_account(const currency::account_base& acc);
void generate(const std::wstring& path, const std::string& password, bool auditable_wallet);
void restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet);
void restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password);
void load(const std::wstring& path, const std::string& password);
void store();
void store(const std::wstring& path);

View file

@ -196,7 +196,7 @@ namespace tools
res.path = epee::string_encoding::convert_to_ansii(m_wallet.get_wallet_path());
res.transfers_count = m_wallet.get_recent_transfers_total_count();
res.transfer_entries_count = m_wallet.get_transfer_entries_count();
res.seed = m_wallet.get_account().get_seed_phrase();
//res.seed = m_wallet.get_account().get_seed_phrase();
std::map<uint64_t, uint64_t> distribution;
m_wallet.get_utxo_distribution(distribution);
for (const auto& ent : distribution)

View file

@ -911,7 +911,7 @@ std::string wallets_manager::open_wallet(const std::wstring& path, const std::st
w->get_unconfirmed_transfers(owr.recent_history.history, exclude_mining_txs);
owr.wallet_local_bc_size = w->get_blockchain_current_size();
//workaround for missed fee
owr.seed = w->get_account().get_seed_phrase();
//owr.seed = w->get_account().get_seed_phrase();
break;
}
catch (const tools::error::file_not_found& /**/)
@ -1015,7 +1015,7 @@ std::string wallets_manager::generate_wallet(const std::wstring& path, const std
{
w->generate(path, password, false);
w->set_minimum_height(m_last_daemon_height);
owr.seed = w->get_account().get_seed_phrase();
//owr.seed = w->get_account().get_seed_phrase();
}
catch (const tools::error::file_exists&)
{
@ -1058,10 +1058,10 @@ std::string wallets_manager::is_pos_allowed()
else
return API_RETURN_CODE_FALSE;
}
std::string wallets_manager::is_valid_brain_restore_data(const std::string& seed_phrase)
std::string wallets_manager::is_valid_brain_restore_data(const std::string& seed_phrase, const std::string& seed_password)
{
currency::account_base acc;
if (acc.restore_from_seed_phrase(seed_phrase))
if (acc.restore_from_seed_phrase(seed_phrase, seed_password))
return API_RETURN_CODE_TRUE;
currency::account_public_address addr;
@ -1081,11 +1081,25 @@ void wallets_manager::subscribe_to_core_events(currency::i_core_event_handler* p
}
#endif
std::string wallets_manager::get_seed_phrase_info(const std::string& seed_phrase, const std::string& seed_password, view::seed_phrase_info& result)
{
//cut the last timestamp word from restore_dats
result.syntax_correct = currency::account_base::is_seed_password_protected(seed_phrase, result.require_password);
if (result.syntax_correct)
{
currency::account_base acc;
result.hash_sum_matched = acc.restore_from_seed_phrase(seed_phrase, seed_password);
}
return API_RETURN_CODE_OK;
}
void wallets_manager::get_gui_options(view::gui_options& opt)
{
opt = m_ui_opt;
}
std::string wallets_manager::restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, view::open_wallet_response& owr)
std::string wallets_manager::restore_wallet(const std::wstring& path, const std::string& password, const std::string& seed_phrase, const std::string& seed_password, view::open_wallet_response& owr)
{
std::shared_ptr<tools::wallet2> w(new tools::wallet2());
w->set_use_deffered_global_outputs(m_use_deffered_global_outputs);
@ -1108,9 +1122,9 @@ std::string wallets_manager::restore_wallet(const std::wstring& path, const std:
currency::account_base acc;
try
{
bool auditable_watch_only = restore_key.find(':') != std::string::npos;
w->restore(path, password, restore_key, auditable_watch_only);
owr.seed = w->get_account().get_seed_phrase();
bool auditable_watch_only = seed_phrase.find(':') != std::string::npos;
w->restore(path, password, seed_phrase, auditable_watch_only, seed_password);
//owr.seed = w->get_account().get_seed_phrase();
}
catch (const tools::error::file_exists&)
{
@ -1625,14 +1639,14 @@ std::string wallets_manager::get_mining_history(uint64_t wallet_id, tools::walle
wo.w->get()->get_mining_history(mh);
return API_RETURN_CODE_OK;
}
std::string wallets_manager::get_wallet_restore_info(uint64_t wallet_id, std::string& restore_key)
std::string wallets_manager::get_wallet_restore_info(uint64_t wallet_id, std::string& seed_phrase, const std::string& seed_password)
{
GET_WALLET_OPT_BY_ID(wallet_id, wo);
if (wo.wallet_state != view::wallet_status_info::wallet_state_ready || wo.long_refresh_in_progress)
return API_RETURN_CODE_CORE_BUSY;
restore_key = wo.w->get()->get_account().get_seed_phrase();
seed_phrase = wo.w->get()->get_account().get_seed_phrase(seed_password);
return API_RETURN_CODE_OK;
}

View file

@ -99,7 +99,7 @@ public:
bool get_opened_wallets(std::list<view::open_wallet_response>& result);
std::string open_wallet(const std::wstring& path, const std::string& password, uint64_t txs_to_return, view::open_wallet_response& owr, bool exclude_mining_txs = false);
std::string generate_wallet(const std::wstring& path, const std::string& password, view::open_wallet_response& owr);
std::string restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, view::open_wallet_response& owr);
std::string restore_wallet(const std::wstring& path, const std::string& password, const std::string& seed_phrase, const std::string& seed_password, view::open_wallet_response& owr);
std::string invoke(uint64_t wallet_id, std::string params);
std::string get_wallet_status(uint64_t wallet_id);
std::string run_wallet(uint64_t wallet_id);
@ -132,7 +132,7 @@ public:
std::string stop_pos_mining(uint64_t wallet_id);
std::string check_available_sources(uint64_t wallet_id, std::list<uint64_t>& amounts);
std::string get_mining_history(uint64_t wallet_id, tools::wallet_public::mining_history& wrpc);
std::string get_wallet_restore_info(uint64_t wallet_id, std::string& restore_key);
std::string get_wallet_restore_info(uint64_t wallet_id, std::string& seed_phrase, const std::string& seed_password);
std::string backup_wallet(uint64_t wallet_id, const std::wstring& path);
std::string reset_wallet_password(uint64_t wallet_id, const std::string& pass);
std::string is_wallet_password_valid(uint64_t wallet_id, const std::string& pass);
@ -149,7 +149,8 @@ public:
void toggle_pos_mining();
std::string transfer(size_t wallet_id, const view::transfer_params& tp, currency::transaction& res_tx);
std::string get_config_folder();
std::string is_valid_brain_restore_data(const std::string& seed_phrase);
std::string is_valid_brain_restore_data(const std::string& seed_phrase, const std::string& seed_password);
std::string get_seed_phrase_info(const std::string& seed_phrase, const std::string& seed_password, view::seed_phrase_info& result);
#ifndef MOBILE_WALLET_BUILD
void subscribe_to_core_events(currency::i_core_event_handler* pevents_handler);
//void unsubscribe_to_core_events();

View file

@ -842,7 +842,7 @@ namespace db_test
ptr = db_array[4];
ASSERT_EQ(ptr->v, "X");
ASSERT_TRUE(db_array.clear());
ASSERT_TRUE((db_array.clear()? true: false));
db_array.commit_transaction();

View file

@ -13,10 +13,10 @@ TEST(wallet_seed, store_restore_test)
{
currency::account_base acc;
acc.generate();
auto seed_phrase = acc.get_seed_phrase();
auto seed_phrase = acc.get_seed_phrase("");
currency::account_base acc2;
bool r = acc2.restore_from_seed_phrase(seed_phrase);
bool r = acc2.restore_from_seed_phrase(seed_phrase, "");
ASSERT_TRUE(r);
if (memcmp(&acc2.get_keys(), &acc.get_keys(), sizeof(currency::account_keys)))
@ -29,10 +29,10 @@ TEST(wallet_seed, store_restore_test)
{
currency::account_base acc;
acc.generate();
auto seed_phrase = acc.get_seed_phrase();
auto seed_phrase = acc.get_seed_phrase("");
currency::account_base acc2;
bool r = acc2.restore_from_seed_phrase(seed_phrase);
bool r = acc2.restore_from_seed_phrase(seed_phrase, "");
ASSERT_TRUE(r);
if (memcmp(&acc2.get_keys(), &acc.get_keys(), sizeof(currency::account_keys)))
@ -57,7 +57,7 @@ wallet_seed_entry wallet_seed_entries[] =
{
{
// legacy 24-word seed phrase -- invalid
"dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew",
"dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew god",
"",
"",
0,
@ -66,7 +66,7 @@ wallet_seed_entry wallet_seed_entries[] =
},
{
// old-style 25-word seed phrase -- valid
"dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew",
"dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew god",
"5e051454d7226b5734ebd64f754b57db4c655ecda00bd324f1b241d0b6381c0f",
"7dde5590fdf430568c00556ac2accf09da6cde9a29a4bc7d1cb6fd267130f006",
0,
@ -148,7 +148,41 @@ TEST(wallet_seed, basic_test)
bool r = false;
try
{
r = acc.restore_from_seed_phrase(wse.seed_phrase);
r = acc.restore_from_seed_phrase(wse.seed_phrase, "");
if (r)
{
for (size_t j = 0; j != 100; j++)
{
//generate random password
std::string pass = epee::string_tools::pod_to_hex(crypto::cn_fast_hash(&j, sizeof(j)));
if (j!= 0 && j < 64)
{
pass.resize(j);
}
//get secured seed
std::string secured_seed = acc.get_seed_phrase(pass);
//try to restore it without password(should fail)
currency::account_base acc2;
bool r_fail = acc2.restore_from_seed_phrase(secured_seed, "");
ASSERT_EQ(r_fail, false);
//try to restore it with wrong password
bool r_fake_pass = acc2.restore_from_seed_phrase(secured_seed, "fake_password");
if (r_fake_pass)
{
//accidentally checksumm matched(quite possible)
ASSERT_EQ(false, acc2.get_keys() == acc.get_keys());
}
//try to restore it from right password
currency::account_base acc3;
bool r_true_res = acc3.restore_from_seed_phrase(secured_seed, pass);
ASSERT_EQ(true, r_true_res);
ASSERT_EQ(true, acc3.get_keys() == acc.get_keys());
}
}
}
catch (...)
{