mirror of
https://github.com/danog/ton.git
synced 2024-11-26 20:14:55 +01:00
added missing files
This commit is contained in:
parent
9d6853ef24
commit
433545916e
593
tonlib/tonlib/tonlib-cli.cpp
Normal file
593
tonlib/tonlib/tonlib-cli.cpp
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
#include "td/actor/actor.h"
|
||||||
|
|
||||||
|
#include "td/utils/filesystem.h"
|
||||||
|
#include "td/utils/OptionsParser.h"
|
||||||
|
#include "td/utils/Parser.h"
|
||||||
|
#include "td/utils/port/signals.h"
|
||||||
|
#include "td/utils/port/path.h"
|
||||||
|
|
||||||
|
#include "terminal/terminal.h"
|
||||||
|
|
||||||
|
#include "tonlib/TonlibClient.h"
|
||||||
|
#include "tonlib/TonlibCallback.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class TonlibCli : public td::actor::Actor {
|
||||||
|
public:
|
||||||
|
struct Options {
|
||||||
|
bool enable_readline{true};
|
||||||
|
std::string config;
|
||||||
|
std::string key_dir{"."};
|
||||||
|
};
|
||||||
|
TonlibCli(Options options) : options_(std::move(options)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Options options_;
|
||||||
|
td::actor::ActorOwn<td::TerminalIO> io_;
|
||||||
|
td::actor::ActorOwn<tonlib::TonlibClient> client_;
|
||||||
|
std::uint64_t next_query_id_{1};
|
||||||
|
td::Promise<td::Slice> cont_;
|
||||||
|
|
||||||
|
struct KeyInfo {
|
||||||
|
std::string public_key;
|
||||||
|
td::SecureString secret;
|
||||||
|
};
|
||||||
|
std::vector<KeyInfo> keys_;
|
||||||
|
|
||||||
|
std::map<std::uint64_t, td::Promise<tonlib_api::object_ptr<tonlib_api::Object>>> query_handlers_;
|
||||||
|
|
||||||
|
bool is_closing_{false};
|
||||||
|
td::uint32 ref_cnt_{1};
|
||||||
|
|
||||||
|
void start_up() override {
|
||||||
|
class Cb : public td::TerminalIO::Callback {
|
||||||
|
public:
|
||||||
|
void line_cb(td::BufferSlice line) override {
|
||||||
|
td::actor::send_closure(id_, &TonlibCli::parse_line, std::move(line));
|
||||||
|
}
|
||||||
|
Cb(td::actor::ActorShared<TonlibCli> id) : id_(std::move(id)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
td::actor::ActorShared<TonlibCli> id_;
|
||||||
|
};
|
||||||
|
ref_cnt_++;
|
||||||
|
io_ = td::TerminalIO::create("> ", options_.enable_readline, std::make_unique<Cb>(actor_shared(this)));
|
||||||
|
td::actor::send_closure(io_, &td::TerminalIO::set_log_interface);
|
||||||
|
|
||||||
|
class TonlibCb : public tonlib::TonlibCallback {
|
||||||
|
public:
|
||||||
|
TonlibCb(td::actor::ActorShared<TonlibCli> id) : id_(std::move(id)) {
|
||||||
|
}
|
||||||
|
void on_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) override {
|
||||||
|
send_closure(id_, &TonlibCli::on_tonlib_result, id, std::move(result));
|
||||||
|
}
|
||||||
|
void on_error(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::error> error) override {
|
||||||
|
send_closure(id_, &TonlibCli::on_tonlib_error, id, std::move(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
td::actor::ActorShared<TonlibCli> id_;
|
||||||
|
};
|
||||||
|
ref_cnt_++;
|
||||||
|
client_ = td::actor::create_actor<tonlib::TonlibClient>("Tonlib", td::make_unique<TonlibCb>(actor_shared(this)));
|
||||||
|
|
||||||
|
td::mkdir(options_.key_dir).ignore();
|
||||||
|
|
||||||
|
load_keys();
|
||||||
|
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
send_query(make_object<tonlib_api::init>(make_object<tonlib_api::options>(options_.config, options_.key_dir)),
|
||||||
|
[](auto r_ok) {
|
||||||
|
LOG_IF(ERROR, r_ok.is_error()) << r_ok.error();
|
||||||
|
td::TerminalIO::out() << "Tonlib is inited\n";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void hangup_shared() override {
|
||||||
|
CHECK(ref_cnt_ > 0);
|
||||||
|
ref_cnt_--;
|
||||||
|
try_stop();
|
||||||
|
}
|
||||||
|
void try_stop() {
|
||||||
|
if (is_closing_ && ref_cnt_ == 0) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void tear_down() override {
|
||||||
|
td::actor::SchedulerContext::get()->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_line(td::BufferSlice line) {
|
||||||
|
if (is_closing_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cont_) {
|
||||||
|
auto cont = std::move(cont_);
|
||||||
|
cont.set_value(line.as_slice());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::ConstParser parser(line.as_slice());
|
||||||
|
auto cmd = parser.read_word();
|
||||||
|
if (cmd.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cmd == "help") {
|
||||||
|
td::TerminalIO::out() << "help - show this help\n";
|
||||||
|
td::TerminalIO::out() << "genkey - generate new secret key\n";
|
||||||
|
td::TerminalIO::out() << "keys - show all stored keys\n";
|
||||||
|
td::TerminalIO::out() << "exportkey [key_id] - export key\n";
|
||||||
|
td::TerminalIO::out() << "setconfig <path> - set lite server config\n";
|
||||||
|
td::TerminalIO::out() << "getstate <key_id> - get state of simple wallet with requested key\n";
|
||||||
|
td::TerminalIO::out() << "init <key_id> - init simple wallet with requested key\n";
|
||||||
|
td::TerminalIO::out() << "transfer <from_key_id> <to_key_id> <amount> - transfer <amount> of grams from "
|
||||||
|
"<from_key_id> to <to_key_id>.\n"
|
||||||
|
<< "\t<from_key_id> could also be 'giver'\n"
|
||||||
|
<< "\t<to_key_id> could also be 'giver' or smartcontract address\n";
|
||||||
|
td::TerminalIO::out() << "exit - exit from this programm\n";
|
||||||
|
} else if (cmd == "genkey") {
|
||||||
|
generate_key();
|
||||||
|
} else if (cmd == "exit") {
|
||||||
|
is_closing_ = true;
|
||||||
|
io_.reset();
|
||||||
|
client_.reset();
|
||||||
|
ref_cnt_--;
|
||||||
|
try_stop();
|
||||||
|
} else if (cmd == "keys") {
|
||||||
|
dump_keys();
|
||||||
|
} else if (cmd == "exportkey") {
|
||||||
|
export_key(parser.read_word());
|
||||||
|
} else if (cmd == "importkey") {
|
||||||
|
import_key(parser.read_all());
|
||||||
|
} else if (cmd == "setconfig") {
|
||||||
|
set_config(parser.read_word());
|
||||||
|
} else if (cmd == "getstate") {
|
||||||
|
get_state(parser.read_word());
|
||||||
|
} else if (cmd == "init") {
|
||||||
|
init_simple_wallet(parser.read_word());
|
||||||
|
} else if (cmd == "transfer") {
|
||||||
|
auto from = parser.read_word();
|
||||||
|
auto to = parser.read_word();
|
||||||
|
auto grams = parser.read_word();
|
||||||
|
transfer(from, to, grams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_tonlib_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) {
|
||||||
|
auto it = query_handlers_.find(id);
|
||||||
|
if (it == query_handlers_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto promise = std::move(it->second);
|
||||||
|
query_handlers_.erase(it);
|
||||||
|
promise.set_value(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_tonlib_error(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::error> error) {
|
||||||
|
auto it = query_handlers_.find(id);
|
||||||
|
if (it == query_handlers_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto promise = std::move(it->second);
|
||||||
|
query_handlers_.erase(it);
|
||||||
|
promise.set_error(td::Status::Error(error->code_, error->message_));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class QueryT>
|
||||||
|
void send_query(tonlib_api::object_ptr<QueryT> query, td::Promise<typename QueryT::ReturnType> promise) {
|
||||||
|
if (is_closing_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto query_id = next_query_id_++;
|
||||||
|
td::actor::send_closure(client_, &tonlib::TonlibClient::request, query_id, std::move(query));
|
||||||
|
query_handlers_[query_id] =
|
||||||
|
[promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::Object>> r_obj) mutable {
|
||||||
|
if (r_obj.is_error()) {
|
||||||
|
return promise.set_error(r_obj.move_as_error());
|
||||||
|
}
|
||||||
|
promise.set_value(ton::move_tl_object_as<typename QueryT::ReturnType::element_type>(r_obj.move_as_ok()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_key(std::string entropy = "") {
|
||||||
|
if (entropy.size() < 20) {
|
||||||
|
td::TerminalIO::out() << "Enter some entropy";
|
||||||
|
cont_ = [this, entropy](td::Slice new_entropy) { generate_key(entropy + new_entropy.str()); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||||
|
cont_ = [this, entropy](td::Slice password) { generate_key(std::move(entropy), td::SecureString(password)); };
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_key(std::string entropy, td::SecureString password) {
|
||||||
|
//TODO: use entropy
|
||||||
|
auto password_copy = password.copy();
|
||||||
|
send_query(tonlib_api::make_object<tonlib_api::createNewKey>(std::move(password_copy),
|
||||||
|
td::SecureString() /*mnemonic password*/),
|
||||||
|
[this, password = std::move(password)](auto r_key) mutable {
|
||||||
|
if (r_key.is_error()) {
|
||||||
|
LOG(ERROR) << "Failed to create new key: " << r_key.error();
|
||||||
|
}
|
||||||
|
auto key = r_key.move_as_ok();
|
||||||
|
LOG(ERROR) << to_string(key);
|
||||||
|
KeyInfo info;
|
||||||
|
info.public_key = key->public_key_;
|
||||||
|
info.secret = std::move(key->secret_);
|
||||||
|
keys_.push_back(std::move(info));
|
||||||
|
export_key(info.public_key, keys_.size() - 1, std::move(password));
|
||||||
|
store_keys();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void store_keys() {
|
||||||
|
td::SecureString buf(10000);
|
||||||
|
td::StringBuilder sb(buf.as_mutable_slice());
|
||||||
|
for (auto& info : keys_) {
|
||||||
|
sb << td::base64_encode(info.public_key) << " " << td::base64_encode(info.secret) << "\n";
|
||||||
|
}
|
||||||
|
LOG_IF(FATAL, sb.is_error()) << "StringBuilder overflow";
|
||||||
|
td::atomic_write_file(key_db_path(), sb.as_cslice());
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_keys() {
|
||||||
|
auto r_db = td::read_file_secure(key_db_path());
|
||||||
|
if (r_db.is_error()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto db = r_db.move_as_ok();
|
||||||
|
td::ConstParser parser(db.as_slice());
|
||||||
|
while (true) {
|
||||||
|
auto public_key_b64 = parser.read_word();
|
||||||
|
auto secret_b64 = parser.read_word();
|
||||||
|
if (secret_b64.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto r_public_key = td::base64_decode(public_key_b64);
|
||||||
|
auto r_secret = td::base64_decode_secure(secret_b64);
|
||||||
|
if (r_public_key.is_error() || r_secret.is_error()) {
|
||||||
|
LOG(ERROR) << "Invalid key database at " << key_db_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyInfo info;
|
||||||
|
info.public_key = r_public_key.move_as_ok();
|
||||||
|
info.secret = r_secret.move_as_ok();
|
||||||
|
LOG(INFO) << td::buffer_to_hex(info.public_key);
|
||||||
|
|
||||||
|
keys_.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_keys() {
|
||||||
|
td::TerminalIO::out() << "Got " << keys_.size() << " keys"
|
||||||
|
<< "\n";
|
||||||
|
for (size_t i = 0; i < keys_.size(); i++) {
|
||||||
|
td::TerminalIO::out() << " #" << i << ": " << td::buffer_to_hex(keys_[i].public_key) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key_db_path() {
|
||||||
|
return options_.key_dir + TD_DIR_SLASH + "key_db";
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Result<size_t> to_key_i(td::Slice key) {
|
||||||
|
if (key.empty()) {
|
||||||
|
return td::Status::Error("Empty key id");
|
||||||
|
}
|
||||||
|
if (key[0] == '#') {
|
||||||
|
TRY_RESULT(res, td::to_integer_safe<size_t>(key.substr(1)));
|
||||||
|
if (res < keys_.size()) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return td::Status::Error("Invalid key id");
|
||||||
|
}
|
||||||
|
auto r_res = td::to_integer_safe<size_t>(key);
|
||||||
|
if (r_res.is_ok() && r_res.ok() < keys_.size()) {
|
||||||
|
return r_res.ok();
|
||||||
|
}
|
||||||
|
if (key.size() < 3) {
|
||||||
|
return td::Status::Error("Too short key id");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prefix = td::to_lower(key);
|
||||||
|
size_t res = 0;
|
||||||
|
size_t cnt = 0;
|
||||||
|
for (size_t i = 0; i < keys_.size(); i++) {
|
||||||
|
auto full_key = td::to_lower(td::buffer_to_hex(keys_[i].public_key));
|
||||||
|
if (td::begins_with(full_key, prefix)) {
|
||||||
|
res = i;
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cnt == 0) {
|
||||||
|
return td::Status::Error("Unknown key prefix");
|
||||||
|
}
|
||||||
|
if (cnt > 1) {
|
||||||
|
return td::Status::Error("Non unique key prefix");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Address {
|
||||||
|
tonlib_api::object_ptr<tonlib_api::accountAddress> address;
|
||||||
|
std::string public_key;
|
||||||
|
td::SecureString secret;
|
||||||
|
};
|
||||||
|
|
||||||
|
td::Result<Address> to_account_address(td::Slice key, bool need_private_key) {
|
||||||
|
if (key.empty()) {
|
||||||
|
return td::Status::Error("account address is empty");
|
||||||
|
}
|
||||||
|
auto r_key_i = to_key_i(key);
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
if (r_key_i.is_ok()) {
|
||||||
|
auto obj = tonlib::TonlibClient::static_request(make_object<tonlib_api::testWallet_getAccountAddress>(
|
||||||
|
make_object<tonlib_api::testWallet_initialAccountState>(keys_[r_key_i.ok()].public_key)));
|
||||||
|
if (obj->get_id() != tonlib_api::error::ID) {
|
||||||
|
Address res;
|
||||||
|
res.address = ton::move_tl_object_as<tonlib_api::accountAddress>(obj);
|
||||||
|
res.public_key = keys_[r_key_i.ok()].public_key;
|
||||||
|
res.secret = keys_[r_key_i.ok()].secret.copy();
|
||||||
|
return std::move(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key == "giver") {
|
||||||
|
auto obj = tonlib::TonlibClient::static_request(make_object<tonlib_api::testGiver_getAccountAddress>());
|
||||||
|
if (obj->get_id() != tonlib_api::error::ID) {
|
||||||
|
Address res;
|
||||||
|
res.address = ton::move_tl_object_as<tonlib_api::accountAddress>(obj);
|
||||||
|
return std::move(res);
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Unexpected error during testGiver_getAccountAddress: " << to_string(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (need_private_key) {
|
||||||
|
return td::Status::Error("Don't have a private key for this address");
|
||||||
|
}
|
||||||
|
//TODO: validate address
|
||||||
|
Address res;
|
||||||
|
res.address = make_object<tonlib_api::accountAddress>(key.str());
|
||||||
|
return std::move(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_key(td::Slice key) {
|
||||||
|
if (key.empty()) {
|
||||||
|
dump_keys();
|
||||||
|
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
|
||||||
|
cont_ = [this](td::Slice key) { this->export_key(key); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r_key_i = to_key_i(key);
|
||||||
|
if (r_key_i.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto key_i = r_key_i.move_as_ok();
|
||||||
|
|
||||||
|
td::TerminalIO::out() << "Key #" << key_i << "\n"
|
||||||
|
<< "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n";
|
||||||
|
|
||||||
|
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||||
|
cont_ = [this, key = key.str(), key_i](td::Slice password) { this->export_key(key, key_i, password); };
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_key(std::string key, size_t key_i, td::Slice password) {
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||||
|
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||||
|
td::SecureString(password))),
|
||||||
|
[key = std::move(key)](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void import_key(td::Slice slice, std::vector<td::SecureString> words = {}) {
|
||||||
|
td::ConstParser parser(slice);
|
||||||
|
while (true) {
|
||||||
|
auto word = parser.read_word();
|
||||||
|
if (word.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
words.push_back(td::SecureString(word));
|
||||||
|
}
|
||||||
|
if (words.size() < 24) {
|
||||||
|
td::TerminalIO::out() << "Enter mnemonic words (got " << words.size() << " out of 24)";
|
||||||
|
cont_ = [this, words = std::move(words)](td::Slice slice) mutable { this->import_key(slice, std::move(words)); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||||
|
cont_ = [this, words = std::move(words)](td::Slice password) mutable {
|
||||||
|
this->import_key(std::move(words), password);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void import_key(std::vector<td::SecureString> words, td::Slice password) {
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
send_query(make_object<tonlib_api::importKey>(td::SecureString(password), td::SecureString(),
|
||||||
|
make_object<tonlib_api::exportedKey>(std::move(words))),
|
||||||
|
[](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't import key " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_config(td::Slice path) {
|
||||||
|
auto r_data = td::read_file_str(path.str());
|
||||||
|
if (r_data.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't read file [" << path << "] : " << r_data.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = r_data.move_as_ok();
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
send_query(make_object<tonlib_api::options_setConfig>(data), [](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't set config: " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_state(td::Slice key) {
|
||||||
|
if (key.empty()) {
|
||||||
|
dump_keys();
|
||||||
|
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
|
||||||
|
cont_ = [this](td::Slice key) { this->get_state(key); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r_key_i = to_key_i(key);
|
||||||
|
if (r_key_i.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto key_i = r_key_i.move_as_ok();
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
auto obj = tonlib::TonlibClient::static_request(make_object<tonlib_api::testWallet_getAccountAddress>(
|
||||||
|
make_object<tonlib_api::testWallet_initialAccountState>(keys_[key_i].public_key)));
|
||||||
|
if (obj->get_id() == tonlib_api::error::ID) {
|
||||||
|
td::TerminalIO::out() << "Can't get state of [" << key << "] : " << to_string(obj);
|
||||||
|
}
|
||||||
|
send_query(
|
||||||
|
make_object<tonlib_api::generic_getAccountState>(ton::move_tl_object_as<tonlib_api::accountAddress>(obj)),
|
||||||
|
[](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't get state: " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void transfer(td::Slice from, td::Slice to, td::Slice grams) {
|
||||||
|
auto r_from_address = to_account_address(from, true);
|
||||||
|
if (r_from_address.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Unknown key id: [" << from << "] : " << r_from_address.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r_to_address = to_account_address(to, false);
|
||||||
|
if (r_to_address.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Unknown key id: [" << to << "] : " << r_to_address.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r_grams = td::to_integer_safe<td::uint64>(grams);
|
||||||
|
if (r_grams.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Invalid grams amount: [" << grams << "]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (from != "giver") {
|
||||||
|
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||||
|
cont_ = [this, from = r_from_address.move_as_ok(), to = r_to_address.move_as_ok(), grams = r_grams.move_as_ok()](
|
||||||
|
td::Slice password) mutable { this->transfer(std::move(from), std::move(to), grams, password); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void transfer(Address from, Address to, td::uint64 grams, td::Slice password) {
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
auto key = !from.secret.empty()
|
||||||
|
? make_object<tonlib_api::inputKey>(
|
||||||
|
make_object<tonlib_api::key>(from.public_key, from.secret.copy()), td::SecureString(password))
|
||||||
|
: nullptr;
|
||||||
|
send_query(make_object<tonlib_api::generic_sendGrams>(std::move(key), std::move(from.address),
|
||||||
|
std::move(to.address), grams),
|
||||||
|
[](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't get state: " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_simple_wallet(td::Slice key) {
|
||||||
|
if (key.empty()) {
|
||||||
|
dump_keys();
|
||||||
|
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
|
||||||
|
cont_ = [this](td::Slice key) { this->init_simple_wallet(key); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r_key_i = to_key_i(key);
|
||||||
|
if (r_key_i.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto key_i = r_key_i.move_as_ok();
|
||||||
|
|
||||||
|
td::TerminalIO::out() << "Key #" << key_i << "\n"
|
||||||
|
<< "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n";
|
||||||
|
|
||||||
|
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||||
|
cont_ = [this, key = key.str(), key_i](td::Slice password) { this->init_simple_wallet(key, key_i, password); };
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_simple_wallet(std::string key, size_t key_i, td::Slice password) {
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
send_query(make_object<tonlib_api::testWallet_init>(make_object<tonlib_api::inputKey>(
|
||||||
|
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||||
|
td::SecureString(password))),
|
||||||
|
[key = std::move(key)](auto r_res) {
|
||||||
|
if (r_res.is_error()) {
|
||||||
|
td::TerminalIO::out() << "Can't init wallet with key: [" << key << "] " << r_res.error() << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
td::TerminalIO::out() << to_string(r_res.ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
using tonlib_api::make_object;
|
||||||
|
SET_VERBOSITY_LEVEL(verbosity_INFO);
|
||||||
|
td::set_default_failure_signal_handler();
|
||||||
|
|
||||||
|
td::OptionsParser p;
|
||||||
|
TonlibCli::Options options;
|
||||||
|
p.set_description("console for validator for TON Blockchain");
|
||||||
|
p.add_option('h', "help", "prints_help", [&]() {
|
||||||
|
std::cout << (PSLICE() << p).c_str();
|
||||||
|
std::exit(2);
|
||||||
|
return td::Status::OK();
|
||||||
|
});
|
||||||
|
p.add_option('r', "disable-readline", "disable readline", [&]() {
|
||||||
|
options.enable_readline = false;
|
||||||
|
return td::Status::OK();
|
||||||
|
});
|
||||||
|
p.add_option('R', "enable-readline", "enable readline", [&]() {
|
||||||
|
options.enable_readline = true;
|
||||||
|
return td::Status::OK();
|
||||||
|
});
|
||||||
|
p.add_option('D', "directory", "set keys directory", [&](td::Slice arg) {
|
||||||
|
options.key_dir = arg.str();
|
||||||
|
return td::Status::OK();
|
||||||
|
});
|
||||||
|
p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
|
||||||
|
auto verbosity = td::to_integer<int>(arg);
|
||||||
|
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
|
||||||
|
return (verbosity >= 0 && verbosity <= 20) ? td::Status::OK() : td::Status::Error("verbosity must be 0..20");
|
||||||
|
});
|
||||||
|
p.add_option('C', "config", "set lite server config", [&](td::Slice arg) {
|
||||||
|
TRY_RESULT(data, td::read_file_str(arg.str()));
|
||||||
|
options.config = std::move(data);
|
||||||
|
return td::Status::OK();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto S = p.run(argc, argv);
|
||||||
|
if (S.is_error()) {
|
||||||
|
std::cerr << S.move_as_error().message().str() << std::endl;
|
||||||
|
std::_Exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
td::actor::Scheduler scheduler({2});
|
||||||
|
scheduler.run_in_context([&] { td::actor::create_actor<TonlibCli>("console", options).release(); });
|
||||||
|
scheduler.run();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user