/** * @file * @copyright defined in eos/LICENSE.txt * @defgroup eosclienttool EOSIO Command Line Client Reference * @brief Tool for sending transactions and querying state from @ref nodeos * @ingroup eosclienttool */ /** @defgroup eosclienttool @section intro Introduction to cleos `cleos` is a command line tool that interfaces with the REST api exposed by @ref nodeos. In order to use `cleos` you will need to have a local copy of `nodeos` running and configured to load the 'eosio::chain_api_plugin'. cleos contains documentation for all of its commands. For a list of all commands known to cleos, simply run it with no arguments: ``` $ ./cleos Command Line Interface to EOSIO Client Usage: programs/cleos/cleos [OPTIONS] SUBCOMMAND Options: -h,--help Print this help message and exit -u,--url TEXT=http://localhost:8888/ the http/https URL where nodeos is running --wallet-url TEXT=http://localhost:8888/ the http/https URL where keosd is running -r,--header pass specific HTTP header, repeat this option to pass multiple headers -n,--no-verify don't verify peer certificate when using HTTPS -v,--verbose output verbose actions on error Subcommands: version Retrieve version information create Create various items, on and off the blockchain get Retrieve various items and information from the blockchain set Set or update blockchain state transfer Transfer EOS from account to account net Interact with local p2p network connections wallet Interact with local wallet sign Sign a transaction push Push arbitrary transactions to the blockchain multisig Multisig contract commands ``` To get help with any particular subcommand, run it with no arguments as well: ``` $ ./cleos create Create various items, on and off the blockchain Usage: ./cleos create SUBCOMMAND Subcommands: key Create a new keypair and print the public and private keys account Create a new account on the blockchain $ ./cleos create account Create a new account on the blockchain Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey Positionals: creator TEXT The name of the account creating the new account name TEXT The name of the new account OwnerKey TEXT The owner public key for the new account ActiveKey TEXT The active public key for the new account Options: -x,--expiration set the time in seconds before a transaction expires, defaults to 30s -f,--force-unique force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times -s,--skip-sign Specify if unlocked wallet keys should be used to sign transaction -d,--dont-broadcast don't broadcast transaction to the network (just print to stdout) -p,--permission TEXT ... An account and permission level to authorize, as in 'account@permission' (defaults to 'creator@active') ``` */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma push_macro("N") #undef N #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma pop_macro("N") #include #include #include #include #include #include #include #include "CLI11.hpp" #include "help_text.hpp" #include "localize.hpp" #include "config.hpp" #include "httpc.hpp" using namespace std; using namespace eosio; using namespace eosio::chain; using namespace eosio::utilities; using namespace eosio::client::help; using namespace eosio::client::http; using namespace eosio::client::localize; using namespace eosio::client::config; using namespace boost::filesystem; FC_DECLARE_EXCEPTION( explained_exception, 9000000, "explained exception, see error log" ); FC_DECLARE_EXCEPTION( localized_exception, 10000000, "an error occured" ); #define EOSC_ASSERT( TEST, ... ) \ FC_EXPAND_MACRO( \ FC_MULTILINE_MACRO_BEGIN \ if( UNLIKELY(!(TEST)) ) \ { \ std::cerr << localized( __VA_ARGS__ ) << std::endl; \ FC_THROW_EXCEPTION( explained_exception, #TEST ); \ } \ FC_MULTILINE_MACRO_END \ ) string url = "http://localhost:8888/"; string wallet_url = "http://localhost:8900/"; int64_t wallet_unlock_timeout = 0; bool no_verify = false; vector headers; auto tx_expiration = fc::seconds(30); string tx_ref_block_num_or_id; bool tx_force_unique = false; bool tx_dont_broadcast = false; bool tx_skip_sign = false; bool tx_print_json = false; uint8_t tx_max_cpu_usage = 0; uint32_t tx_max_net_usage = 0; vector tx_permission; void add_standard_transaction_options(CLI::App* cmd, string default_permission = "") { CLI::callback_t parse_expiration = [](CLI::results_t res) -> bool { double value_s; if (res.size() == 0 || !CLI::detail::lexical_cast(res[0], value_s)) { return false; } tx_expiration = fc::seconds(static_cast(value_s)); return true; }; cmd->add_option("-x,--expiration", parse_expiration, localized("set the time in seconds before a transaction expires, defaults to 30s")); cmd->add_flag("-f,--force-unique", tx_force_unique, localized("force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times")); cmd->add_flag("-s,--skip-sign", tx_skip_sign, localized("Specify if unlocked wallet keys should be used to sign transaction")); cmd->add_flag("-j,--json", tx_print_json, localized("print result as json")); cmd->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout)")); cmd->add_option("-r,--ref-block", tx_ref_block_num_or_id, (localized("set the reference block num or block id used for TAPOS (Transaction as Proof-of-Stake)"))); string msg = "An account and permission level to authorize, as in 'account@permission'"; if(!default_permission.empty()) msg += " (defaults to '" + default_permission + "')"; cmd->add_option("-p,--permission", tx_permission, localized(msg.c_str())); cmd->add_option("--max-cpu-usage-ms", tx_max_cpu_usage, localized("set an upper limit on the milliseconds of cpu usage budget, for the execution of the transaction (defaults to 0 which means no limit)")); cmd->add_option("--max-net-usage", tx_max_net_usage, localized("set an upper limit on the net usage budget, in bytes, for the transaction (defaults to 0 which means no limit)")); } vector get_account_permissions(const vector& permissions) { auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) { vector pieces; split(pieces, p, boost::algorithm::is_any_of("@")); if( pieces.size() == 1 ) pieces.push_back( "active" ); return chain::permission_level{ .actor = pieces[0], .permission = pieces[1] }; }); vector accountPermissions; boost::range::copy(fixedPermissions, back_inserter(accountPermissions)); return accountPermissions; } template fc::variant call( const std::string& url, const std::string& path, const T& v ) { try { eosio::client::http::connection_param *cp = new eosio::client::http::connection_param((std::string&)url, (std::string&)path, no_verify ? false : true, headers); return eosio::client::http::do_http_call( *cp, fc::variant(v) ); } catch(boost::system::system_error& e) { if(url == ::url) std::cerr << localized("Failed to connect to nodeos at ${u}; is nodeos running?", ("u", url)) << std::endl; else if(url == ::wallet_url) std::cerr << localized("Failed to connect to keosd at ${u}; is keosd running?", ("u", url)) << std::endl; throw connection_exception(fc::log_messages{FC_LOG_MESSAGE(error, e.what())}); } } template fc::variant call( const std::string& path, const T& v ) { return call( url, path, fc::variant(v) ); } template<> fc::variant call( const std::string& url, const std::string& path) { return call( url, path, fc::variant() ); } eosio::chain_apis::read_only::get_info_results get_info() { return call(url, get_info_func).as(); } string generate_nonce_string() { return fc::to_string(fc::time_point::now().time_since_epoch().count()); } chain::action generate_nonce_action() { return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count())); } fc::variant determine_required_keys(const signed_transaction& trx) { // TODO better error checking //wdump((trx)); const auto& public_keys = call(wallet_url, wallet_public_keys); auto get_arg = fc::mutable_variant_object ("transaction", (transaction)trx) ("available_keys", public_keys); const auto& required_keys = call(get_required_keys, get_arg); return required_keys["required_keys"]; } void sign_transaction(signed_transaction& trx, fc::variant& required_keys, const chain_id_type& chain_id) { fc::variants sign_args = {fc::variant(trx), required_keys, fc::variant(chain_id)}; const auto& signed_trx = call(wallet_url, wallet_sign_trx, sign_args); trx = signed_trx.as(); } fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { auto info = get_info(); trx.expiration = info.head_block_time + tx_expiration; // Set tapos, default to last irreversible block if it's not specified by the user block_id_type ref_block_id = info.last_irreversible_block_id; try { fc::variant ref_block; if (!tx_ref_block_num_or_id.empty()) { ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id)); ref_block_id = ref_block["id"].as(); } } EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id)); trx.set_reference_block(ref_block_id); if (tx_force_unique) { trx.context_free_actions.emplace_back( generate_nonce_action() ); } trx.max_cpu_usage_ms = tx_max_net_usage; trx.max_net_usage_words = (tx_max_net_usage + 7)/8; if (!tx_skip_sign) { auto required_keys = determine_required_keys(trx); sign_transaction(trx, required_keys, info.chain_id); } if (!tx_dont_broadcast) { return call(push_txn_func, packed_transaction(trx, compression)); } else { return fc::variant(trx); } } fc::variant push_actions(std::vector&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) { signed_transaction trx; trx.actions = std::forward(actions); return push_transaction(trx, extra_kcpu, compression); } void print_action( const fc::variant& at ) { const auto& receipt = at["receipt"]; auto receiver = receipt["receiver"].as_string(); const auto& act = at["act"].get_object(); auto code = act["account"].as_string(); auto func = act["name"].as_string(); auto args = fc::json::to_string( act["data"] ); auto console = at["console"].as_string(); /* if( code == "eosio" && func == "setcode" ) args = args.substr(40)+"..."; if( name(code) == config::system_account_name && func == "setabi" ) args = args.substr(40)+"..."; */ if( args.size() > 100 ) args = args.substr(0,100) + "..."; cout << "#" << std::setw(14) << right << receiver << " <= " << std::setw(28) << std::left << (code +"::" + func) << " " << args << "\n"; if( console.size() ) { std::stringstream ss(console); string line; std::getline( ss, line ); cout << ">> " << line << "\n"; } } void print_action_tree( const fc::variant& action ) { print_action( action ); const auto& inline_traces = action["inline_traces"].get_array(); for( const auto& t : inline_traces ) { print_action_tree( t ); } } void print_result( const fc::variant& result ) { try { if (result.is_object() && result.get_object().contains("processed")) { const auto& processed = result["processed"]; const auto& transaction_id = processed["id"].as_string(); string status = processed["receipt"].is_object() ? processed["receipt"]["status"].as_string() : "failed"; int64_t net = -1; int64_t cpu = -1; if( processed.get_object().contains( "receipt" )) { const auto& receipt = processed["receipt"]; if( receipt.is_object()) { net = receipt["net_usage_words"].as_int64() * 8; cpu = receipt["cpu_usage_us"].as_int64(); } } cerr << status << " transaction: " << transaction_id << " "; if( net < 0 ) { cerr << ""; } else { cerr << net; } cerr << " bytes "; if( cpu < 0 ) { cerr << ""; } else { cerr << cpu; } cerr << " us\n"; if( status == "failed" ) { auto soft_except = processed["except"].as>(); if( soft_except ) { edump((soft_except->to_detail_string())); } } else { const auto& actions = processed["action_traces"].get_array(); for( const auto& a : actions ) { print_action_tree( a ); } wlog( "\rwarning: transaction executed locally, but may not be confirmed by the network yet" ); } } else { cerr << fc::json::to_pretty_string( result ) << endl; } } FC_CAPTURE_AND_RETHROW( (result) ) } using std::cout; void send_actions(std::vector&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { auto result = push_actions( move(actions), extra_kcpu, compression); if( tx_print_json ) { cout << fc::json::to_pretty_string( result ) << endl; } else { print_result( result ); } } void send_transaction( signed_transaction& trx, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) { auto result = push_transaction(trx, extra_kcpu, compression); if( tx_print_json ) { cout << fc::json::to_pretty_string( result ) << endl; } else { print_result( result ); } } chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) { return action { tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), eosio::chain::newaccount{ .creator = creator, .name = newaccount, .owner = eosio::chain::authority{1, {{owner, 1}}, {}}, .active = eosio::chain::authority{1, {{active, 1}}, {}} } }; } chain::action create_action(const vector& authorization, const account_name& code, const action_name& act, const fc::variant& args) { auto arg = fc::mutable_variant_object() ("code", code) ("action", act) ("args", args); auto result = call(json_to_bin_func, arg); wdump((result)(arg)); return chain::action{authorization, code, act, result.get_object()["binargs"].as()}; } chain::action create_buyram(const name& creator, const name& newaccount, const asset& quantity) { fc::variant act_payload = fc::mutable_variant_object() ("payer", creator.to_string()) ("receiver", newaccount.to_string()) ("quant", quantity.to_string()); return create_action(tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), config::system_account_name, N(buyram), act_payload); } chain::action create_buyrambytes(const name& creator, const name& newaccount, uint32_t numbytes) { fc::variant act_payload = fc::mutable_variant_object() ("payer", creator.to_string()) ("receiver", newaccount.to_string()) ("bytes", numbytes); return create_action(tx_permission.empty() ? vector{{creator,config::active_name}} : get_account_permissions(tx_permission), config::system_account_name, N(buyrambytes), act_payload); } chain::action create_delegate(const name& from, const name& receiver, const asset& net, const asset& cpu, bool transfer) { fc::variant act_payload = fc::mutable_variant_object() ("from", from.to_string()) ("receiver", receiver.to_string()) ("stake_net_quantity", net.to_string()) ("stake_cpu_quantity", cpu.to_string()) ("transfer", transfer); return create_action(tx_permission.empty() ? vector{{from,config::active_name}} : get_account_permissions(tx_permission), config::system_account_name, N(delegatebw), act_payload); } fc::variant regproducer_variant(const account_name& producer, public_key_type key, string url, uint16_t location = 0) { /* fc::variant_object params = fc::mutable_variant_object() ("max_block_net_usage", config::default_max_block_net_usage) ("target_block_net_usage_pct", config::default_target_block_net_usage_pct) ("max_transaction_net_usage", config::default_max_transaction_net_usage) ("base_per_transaction_net_usage", config::default_base_per_transaction_net_usage) ("net_usage_leeway", config::default_net_usage_leeway) ("context_free_discount_net_usage_num", config::default_context_free_discount_net_usage_num) ("context_free_discount_net_usage_den", config::default_context_free_discount_net_usage_den) ("max_block_cpu_usage", config::default_max_block_cpu_usage) ("target_block_cpu_usage_pct", config::default_target_block_cpu_usage_pct) ("max_transaction_cpu_usage", config::default_max_transaction_cpu_usage) ("max_transaction_lifetime", config::default_max_trx_lifetime) ("deferred_trx_expiration_window", config::default_deferred_trx_expiration_window) ("max_transaction_delay", config::default_max_trx_delay) ("max_inline_action_size", config::default_max_inline_action_size) ("max_inline_depth", config::default_max_inline_action_depth) ("max_authority_depth", config::default_max_auth_depth) ("max_storage_size", max_storage_size) ("percent_of_max_inflation_rate", percent_of_max_inflation_rate) ("storage_reserve_ratio", storage_reserve_ratio); */ return fc::mutable_variant_object() ("producer", producer) ("producer_key", key) ("url", url) ("location", 0) ; } chain::action create_transfer(const string& contract, const name& sender, const name& recipient, asset amount, const string& memo ) { auto transfer = fc::mutable_variant_object ("from", sender) ("to", recipient) ("quantity", amount) ("memo", memo); auto args = fc::mutable_variant_object ("code", contract) ("action", "transfer") ("args", transfer); auto result = call(json_to_bin_func, args); return action { tx_permission.empty() ? vector{{sender,config::active_name}} : get_account_permissions(tx_permission), contract, "transfer", result.get_object()["binargs"].as() }; } chain::action create_setabi(const name& account, const abi_def& abi) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), setabi{ .account = account, .abi = fc::raw::pack(abi) } }; } chain::action create_setcode(const name& account, const bytes& code) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), setcode{ .account = account, .vmtype = 0, .vmversion = 0, .code = code } }; } chain::action create_updateauth(const name& account, const name& permission, const name& parent, const authority& auth) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), updateauth{account, permission, parent, auth}}; } chain::action create_deleteauth(const name& account, const name& permission) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), deleteauth{account, permission}}; } chain::action create_linkauth(const name& account, const name& code, const name& type, const name& requirement) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), linkauth{account, code, type, requirement}}; } chain::action create_unlinkauth(const name& account, const name& code, const name& type) { return action { tx_permission.empty() ? vector{{account,config::active_name}} : get_account_permissions(tx_permission), unlinkauth{account, code, type}}; } fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) { regex r("^[ \t]*[\{\[]"); if ( !regex_search(file_or_str, r) && fc::is_regular_file(file_or_str) ) { return fc::json::from_file(file_or_str, ptype); } else { return fc::json::from_string(file_or_str, ptype); } } authority parse_json_authority(const std::string& authorityJsonOrFile) { try { return json_from_file_or_string(authorityJsonOrFile).as(); } EOS_RETHROW_EXCEPTIONS(authority_type_exception, "Fail to parse Authority JSON '${data}'", ("data",authorityJsonOrFile)) } authority parse_json_authority_or_key(const std::string& authorityJsonOrFile) { if (boost::istarts_with(authorityJsonOrFile, "EOS")) { try { return authority(public_key_type(authorityJsonOrFile)); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", authorityJsonOrFile)) } else { return parse_json_authority(authorityJsonOrFile); } } asset to_asset( const string& code, const string& s ) { static map cache; auto a = asset::from_string( s ); eosio::chain::symbol_code sym = a.get_symbol().to_symbol_code(); auto it = cache.find( sym ); auto sym_str = a.symbol_name(); if ( it == cache.end() ) { auto json = call(get_currency_stats_func, fc::mutable_variant_object("json", false) ("code", code) ("symbol", sym_str) ); auto obj = json.get_object(); auto obj_it = obj.find( sym_str ); if (obj_it != obj.end()) { auto result = obj_it->value().as(); auto p = cache.insert(make_pair( sym, result.max_supply.get_symbol() )); it = p.first; } else { FC_THROW("Symbol ${s} is not supported by token contract ${c}", ("s", sym_str)("c", code)); } } auto expected_symbol = it->second; if ( a.decimals() < expected_symbol.decimals() ) { auto factor = expected_symbol.precision() / a.precision(); auto a_old = a; a = asset( a.get_amount() * factor, expected_symbol ); } else if ( a.decimals() > expected_symbol.decimals() ) { FC_THROW("Too many decimal digits in ${a}, only ${d} supported", ("a", a)("d", expected_symbol.decimals())); } // else precision matches return a; } inline asset to_asset( const string& s ) { return to_asset( "eosio.token", s ); } struct set_account_permission_subcommand { string accountStr; string permissionStr; string authorityJsonOrFile; string parentStr; set_account_permission_subcommand(CLI::App* accountCmd) { auto permissions = accountCmd->add_subcommand("permission", localized("set parmaters dealing with account permissions")); permissions->add_option("account", accountStr, localized("The account to set/delete a permission authority for"))->required(); permissions->add_option("permission", permissionStr, localized("The permission name to set/delete an authority for"))->required(); permissions->add_option("authority", authorityJsonOrFile, localized("[delete] NULL, [create/update] public key, JSON string, or filename defining the authority"))->required(); permissions->add_option("parent", parentStr, localized("[create] The permission name of this parents permission (Defaults to: \"Active\")")); add_standard_transaction_options(permissions, "account@active"); permissions->set_callback([this] { name account = name(accountStr); name permission = name(permissionStr); bool is_delete = boost::iequals(authorityJsonOrFile, "null"); if (is_delete) { send_actions({create_deleteauth(account, permission)}); } else { authority auth = parse_json_authority_or_key(authorityJsonOrFile); name parent; if (parentStr.size() == 0 && permissionStr != "owner") { // see if we can auto-determine the proper parent const auto account_result = call(get_account_func, fc::mutable_variant_object("account_name", accountStr)); const auto& existing_permissions = account_result.get_object()["permissions"].get_array(); auto permissionPredicate = [this](const auto& perm) { return perm.is_object() && perm.get_object().contains("permission") && boost::equals(perm.get_object()["permission"].get_string(), permissionStr); }; auto itr = boost::find_if(existing_permissions, permissionPredicate); if (itr != existing_permissions.end()) { parent = name((*itr).get_object()["parent"].get_string()); } else { // if this is a new permission and there is no parent we default to "active" parent = name(config::active_name); } } else { parent = name(parentStr); } send_actions({create_updateauth(account, permission, parent, auth)}); } }); } }; struct set_action_permission_subcommand { string accountStr; string codeStr; string typeStr; string requirementStr; set_action_permission_subcommand(CLI::App* actionRoot) { auto permissions = actionRoot->add_subcommand("permission", localized("set parmaters dealing with account permissions")); permissions->add_option("account", accountStr, localized("The account to set/delete a permission authority for"))->required(); permissions->add_option("code", codeStr, localized("The account that owns the code for the action"))->required(); permissions->add_option("type", typeStr, localized("the type of the action"))->required(); permissions->add_option("requirement", requirementStr, localized("[delete] NULL, [set/update] The permission name require for executing the given action"))->required(); add_standard_transaction_options(permissions, "account@active"); permissions->set_callback([this] { name account = name(accountStr); name code = name(codeStr); name type = name(typeStr); bool is_delete = boost::iequals(requirementStr, "null"); if (is_delete) { send_actions({create_unlinkauth(account, code, type)}); } else { name requirement = name(requirementStr); send_actions({create_linkauth(account, code, type, requirement)}); } }); } }; bool port_used(uint16_t port) { using namespace boost::asio; io_service ios; boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), port); boost::asio::ip::tcp::socket socket(ios); boost::system::error_code ec = error::would_block; //connecting/failing to connect to localhost should be always fast - don't care about timeouts socket.async_connect(endpoint, [&](const boost::system::error_code& error) { ec = error; } ); do { ios.run_one(); } while (ec == error::would_block); return !ec; } void try_port( uint16_t port, uint32_t duration ) { using namespace std::chrono; auto start_time = duration_cast( system_clock::now().time_since_epoch() ).count(); while ( !port_used(port)) { if (duration_cast( system_clock::now().time_since_epoch()).count() - start_time > duration ) { std::cerr << "Unable to connect to keosd, if keosd is running please kill the process and try again.\n"; throw connection_exception(fc::log_messages{FC_LOG_MESSAGE(error, "Unable to connect to keosd")}); } } } void ensure_keosd_running(CLI::App* app) { // get, version, net do not require keosd if (tx_skip_sign || app->got_subcommand("get") || app->got_subcommand("version") || app->got_subcommand("net")) return; if (app->get_subcommand("create")->got_subcommand("key")) // create key does not require wallet return; auto parsed_url = parse_url(wallet_url); if (parsed_url.server != "localhost" && parsed_url.server != "127.0.0.1") return; auto wallet_port = std::stoi(parsed_url.port); if (wallet_port < 0 || wallet_port > 65535) { FC_THROW("port is not in valid range"); } if (port_used(uint16_t(wallet_port))) // Hopefully taken by keosd return; boost::filesystem::path binPath = boost::dll::program_location(); binPath.remove_filename(); // This extra check is necessary when running cleos like this: ./cleos ... if (binPath.filename_is_dot()) binPath.remove_filename(); binPath.append("keosd"); // if cleos and keosd are in the same installation directory if (!boost::filesystem::exists(binPath)) { binPath.remove_filename().remove_filename().append("keosd").append("keosd"); } if (boost::filesystem::exists(binPath)) { namespace bp = boost::process; binPath = boost::filesystem::canonical(binPath); vector pargs; pargs.push_back("--http-server-address=127.0.0.1:" + parsed_url.port); if (wallet_unlock_timeout > 0) { pargs.push_back("--unlock-timeout=" + fc::to_string(wallet_unlock_timeout)); } ::boost::process::child keos(binPath, pargs, bp::std_in.close(), bp::std_out > bp::null, bp::std_err > bp::null); if (keos.running()) { std::cerr << binPath << " launched" << std::endl; keos.detach(); sleep(1); } else { std::cerr << "No wallet service listening on 127.0.0.1:" << std::to_string(wallet_port) << ". Failed to launch " << binPath << std::endl; } } else { std::cerr << "No wallet service listening on 127.0.0.1: " << std::to_string(wallet_port) << ". Cannot automatically start keosd because keosd was not found." << std::endl; } } CLI::callback_t obsoleted_option_host_port = [](CLI::results_t) { std::cerr << localized("Host and port options (-H, --wallet-host, etc.) have been replaced with -u/--url and --wallet-url\n" "Use for example -u http://localhost:8888 or --url https://example.invalid/\n"); exit(1); return false; }; struct register_producer_subcommand { string producer_str; string producer_key_str; string url; uint16_t loc = 0; register_producer_subcommand(CLI::App* actionRoot) { auto register_producer = actionRoot->add_subcommand("regproducer", localized("Register a new producer")); register_producer->add_option("account", producer_str, localized("The account to register as a producer"))->required(); register_producer->add_option("producer_key", producer_key_str, localized("The producer's public key"))->required(); register_producer->add_option("url", url, localized("url where info about producer can be found"), true); register_producer->add_option("location", loc, localized("relative location for purpose of nearest neighbor scheduling"), 0); add_standard_transaction_options(register_producer); register_producer->set_callback([this] { public_key_type producer_key; try { producer_key = public_key_type(producer_key_str); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid producer public key: ${public_key}", ("public_key", producer_key_str)) auto regprod_var = regproducer_variant(producer_str, producer_key, url, loc ); send_actions({create_action({permission_level{producer_str,config::active_name}}, config::system_account_name, N(regproducer), regprod_var)}); }); } }; struct create_account_subcommand { string creator; string account_name; string owner_key_str; string active_key_str; string stake_net; string stake_cpu; uint32_t buy_ram_bytes_in_kbytes = 0; string buy_ram_eos; bool transfer; bool simple; create_account_subcommand(CLI::App* actionRoot, bool s) : simple(s) { auto createAccount = actionRoot->add_subcommand( (simple ? "account" : "newaccount"), localized("Create an account, buy ram, stake for bandwidth for the account")); createAccount->add_option("creator", creator, localized("The name of the account creating the new account"))->required(); createAccount->add_option("name", account_name, localized("The name of the new account"))->required(); createAccount->add_option("OwnerKey", owner_key_str, localized("The owner public key for the new account"))->required(); createAccount->add_option("ActiveKey", active_key_str, localized("The active public key for the new account")); if (!simple) { createAccount->add_option("--stake-net", stake_net, (localized("The amount of EOS delegated for net bandwidth")))->required(); createAccount->add_option("--stake-cpu", stake_cpu, (localized("The amount of EOS delegated for CPU bandwidth")))->required(); createAccount->add_option("--buy-ram-bytes", buy_ram_bytes_in_kbytes, (localized("The amount of RAM bytes to purchase for the new account in kilobytes KiB, default is 8 KiB"))); createAccount->add_option("--buy-ram-EOS", buy_ram_eos, (localized("The amount of RAM bytes to purchase for the new account in EOS"))); createAccount->add_flag("--transfer", transfer, (localized("Transfer voting power and right to unstake EOS to receiver"))); } add_standard_transaction_options(createAccount); createAccount->set_callback([this] { if( !active_key_str.size() ) active_key_str = owner_key_str; public_key_type owner_key, active_key; try { owner_key = public_key_type(owner_key_str); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid owner public key: ${public_key}", ("public_key", owner_key_str)); try { active_key = public_key_type(active_key_str); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid active public key: ${public_key}", ("public_key", active_key_str)); auto create = create_newaccount(creator, account_name, owner_key, active_key); if (!simple) { if ( buy_ram_eos.empty() && buy_ram_bytes_in_kbytes == 0) { std::cerr << "ERROR: Either --buy-ram-EOS or --buy-ram-bytes with non-zero value is required" << std::endl; return; } action buyram = !buy_ram_eos.empty() ? create_buyram(creator, account_name, to_asset(buy_ram_eos)) : create_buyrambytes(creator, account_name, buy_ram_bytes_in_kbytes * 1024); auto net = to_asset(stake_net); auto cpu = to_asset(stake_cpu); if ( net.get_amount() == 0 && cpu.get_amount() == 0 ) { action delegate = create_delegate( creator, account_name, net, cpu, transfer); send_actions( { create, buyram, delegate } ); } else { send_actions( { create, buyram } ); } } else { send_actions( { create } ); } }); } }; struct unregister_producer_subcommand { string producer_str; unregister_producer_subcommand(CLI::App* actionRoot) { auto unregister_producer = actionRoot->add_subcommand("unregprod", localized("Unregister an existing producer")); unregister_producer->add_option("account", producer_str, localized("The account to unregister as a producer"))->required(); add_standard_transaction_options(unregister_producer); unregister_producer->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("producer", producer_str); send_actions({create_action({permission_level{producer_str,config::active_name}}, config::system_account_name, N(unregprod), act_payload)}); }); } }; struct vote_producer_proxy_subcommand { string voter_str; string proxy_str; vote_producer_proxy_subcommand(CLI::App* actionRoot) { auto vote_proxy = actionRoot->add_subcommand("proxy", localized("Vote your stake through a proxy")); vote_proxy->add_option("voter", voter_str, localized("The voting account"))->required(); vote_proxy->add_option("proxy", proxy_str, localized("The proxy account"))->required(); add_standard_transaction_options(vote_proxy); vote_proxy->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("voter", voter_str) ("proxy", proxy_str) ("producers", std::vector{}); send_actions({create_action({permission_level{voter_str,config::active_name}}, config::system_account_name, N(voteproducer), act_payload)}); }); } }; struct vote_producers_subcommand { string voter_str; vector producer_names; vote_producers_subcommand(CLI::App* actionRoot) { auto vote_producers = actionRoot->add_subcommand("prods", localized("Vote for one or more producers")); vote_producers->add_option("voter", voter_str, localized("The voting account"))->required(); vote_producers->add_option("producers", producer_names, localized("The account(s) to vote for. All options from this position and following will be treated as the producer list."))->required(); add_standard_transaction_options(vote_producers); vote_producers->set_callback([this] { std::sort( producer_names.begin(), producer_names.end() ); fc::variant act_payload = fc::mutable_variant_object() ("voter", voter_str) ("proxy", "") ("producers", producer_names); send_actions({create_action({permission_level{voter_str,config::active_name}}, config::system_account_name, N(voteproducer), act_payload)}); }); } }; struct approve_producer_subcommand { eosio::name voter; eosio::name producer_name; approve_producer_subcommand(CLI::App* actionRoot) { auto approve_producer = actionRoot->add_subcommand("approve", localized("Add one producer to list of voted producers")); approve_producer->add_option("voter", voter, localized("The voting account"))->required(); approve_producer->add_option("producer", producer_name, localized("The account to vote for"))->required(); add_standard_transaction_options(approve_producer); approve_producer->set_callback([this] { auto result = call(get_table_func, fc::mutable_variant_object("json", true) ("code", name(config::system_account_name).to_string()) ("scope", name(config::system_account_name).to_string()) ("table", "voters") ("table_key", "owner") ("lower_bound", voter.value) ("limit", 1) ); auto res = result.as(); if ( res.rows.empty() || res.rows[0]["owner"].as_string() != name(voter).to_string() ) { std::cerr << "Voter info not found for account " << voter << std::endl; return; } FC_ASSERT( 1 == res.rows.size(), "More than one voter_info for account" ); auto prod_vars = res.rows[0]["producers"].get_array(); vector prods; for ( auto& x : prod_vars ) { prods.push_back( name(x.as_string()) ); } prods.push_back( producer_name ); std::sort( prods.begin(), prods.end() ); auto it = std::unique( prods.begin(), prods.end() ); if (it != prods.end() ) { std::cerr << "Producer \"" << producer_name << "\" is already on the list." << std::endl; return; } fc::variant act_payload = fc::mutable_variant_object() ("voter", voter) ("proxy", "") ("producers", prods); send_actions({create_action({permission_level{voter,config::active_name}}, config::system_account_name, N(voteproducer), act_payload)}); }); } }; struct unapprove_producer_subcommand { eosio::name voter; eosio::name producer_name; unapprove_producer_subcommand(CLI::App* actionRoot) { auto approve_producer = actionRoot->add_subcommand("unapprove", localized("Remove one producer from list of voted producers")); approve_producer->add_option("voter", voter, localized("The voting account"))->required(); approve_producer->add_option("producer", producer_name, localized("The account to remove from voted producers"))->required(); add_standard_transaction_options(approve_producer); approve_producer->set_callback([this] { auto result = call(get_table_func, fc::mutable_variant_object("json", true) ("code", name(config::system_account_name).to_string()) ("scope", name(config::system_account_name).to_string()) ("table", "voters") ("table_key", "owner") ("lower_bound", voter.value) ("limit", 1) ); auto res = result.as(); if ( res.rows.empty() || res.rows[0]["owner"].as_string() != name(voter).to_string() ) { std::cerr << "Voter info not found for account " << voter << std::endl; return; } FC_ASSERT( 1 == res.rows.size(), "More than one voter_info for account" ); auto prod_vars = res.rows[0]["producers"].get_array(); vector prods; for ( auto& x : prod_vars ) { prods.push_back( name(x.as_string()) ); } auto it = std::remove( prods.begin(), prods.end(), producer_name ); if (it == prods.end() ) { std::cerr << "Cannot remove: producer \"" << producer_name << "\" is not on the list." << std::endl; return; } prods.erase( it, prods.end() ); //should always delete only one element fc::variant act_payload = fc::mutable_variant_object() ("voter", voter) ("proxy", "") ("producers", prods); send_actions({create_action({permission_level{voter,config::active_name}}, config::system_account_name, N(voteproducer), act_payload)}); }); } }; struct list_producers_subcommand { bool print_json = false; uint32_t limit = 50; std::string lower; list_producers_subcommand(CLI::App* actionRoot) { auto list_producers = actionRoot->add_subcommand("listproducers", localized("List producers")); list_producers->add_flag("--json,-j", print_json, localized("Output in JSON format")); list_producers->add_option("-l,--limit", limit, localized("The maximum number of rows to return")); list_producers->add_option("-L,--lower", lower, localized("lower bound value of key, defaults to first")); list_producers->set_callback([this] { auto rawResult = call(get_producers_func, fc::mutable_variant_object ("json", true)("lower_bound", lower)("limit", limit)); if ( print_json ) { std::cout << fc::json::to_pretty_string(rawResult) << std::endl; return; } auto result = rawResult.as(); if ( result.rows.empty() ) { std::cout << "No producers found" << std::endl; return; } auto weight = result.total_producer_vote_weight; if ( !weight ) weight = 1; printf("%-13s %-54s %-59s %s\n", "Producer", "Producer key", "Url", "Scaled votes"); for ( auto& row : result.rows ) printf("%-13.13s %-54.54s %-59.59s %1.4f\n", row["owner"].as_string().c_str(), row["producer_key"].as_string().c_str(), row["url"].as_string().c_str(), row["total_votes"].as_double() / weight); if ( !result.more.empty() ) std::cout << "-L " << result.more << " for more" << std::endl; }); } }; struct delegate_bandwidth_subcommand { string from_str; string receiver_str; string stake_net_amount; string stake_cpu_amount; string stake_storage_amount; bool transfer = false; delegate_bandwidth_subcommand(CLI::App* actionRoot) { auto delegate_bandwidth = actionRoot->add_subcommand("delegatebw", localized("Delegate bandwidth")); delegate_bandwidth->add_option("from", from_str, localized("The account to delegate bandwidth from"))->required(); delegate_bandwidth->add_option("receiver", receiver_str, localized("The account to receive the delegated bandwidth"))->required(); delegate_bandwidth->add_option("stake_net_quantity", stake_net_amount, localized("The amount of EOS to stake for network bandwidth"))->required(); delegate_bandwidth->add_option("stake_cpu_quantity", stake_cpu_amount, localized("The amount of EOS to stake for CPU bandwidth"))->required(); delegate_bandwidth->add_flag("--transfer", transfer, localized("Transfer voting power and right to unstake EOS to receiver")); add_standard_transaction_options(delegate_bandwidth); delegate_bandwidth->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("receiver", receiver_str) ("stake_net_quantity", to_asset(stake_net_amount)) ("stake_cpu_quantity", to_asset(stake_cpu_amount)) ("transfer", transfer) ; wdump((act_payload)); send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(delegatebw), act_payload)}); }); } }; struct undelegate_bandwidth_subcommand { string from_str; string receiver_str; string unstake_net_amount; string unstake_cpu_amount; uint64_t unstake_storage_bytes; undelegate_bandwidth_subcommand(CLI::App* actionRoot) { auto undelegate_bandwidth = actionRoot->add_subcommand("undelegatebw", localized("Undelegate bandwidth")); undelegate_bandwidth->add_option("from", from_str, localized("The account undelegating bandwidth"))->required(); undelegate_bandwidth->add_option("receiver", receiver_str, localized("The account to undelegate bandwidth from"))->required(); undelegate_bandwidth->add_option("unstake_net_quantity", unstake_net_amount, localized("The amount of EOS to undelegate for network bandwidth"))->required(); undelegate_bandwidth->add_option("unstake_cpu_quantity", unstake_cpu_amount, localized("The amount of EOS to undelegate for CPU bandwidth"))->required(); add_standard_transaction_options(undelegate_bandwidth); undelegate_bandwidth->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("receiver", receiver_str) ("unstake_net_quantity", to_asset(unstake_net_amount)) ("unstake_cpu_quantity", to_asset(unstake_cpu_amount)); send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(undelegatebw), act_payload)}); }); } }; struct list_bw_subcommand { eosio::name account; bool print_json = false; list_bw_subcommand(CLI::App* actionRoot) { auto list_bw = actionRoot->add_subcommand("listbw", localized("List delegated bandwidth")); list_bw->add_option("account", account, localized("The account delegated bandwidth"))->required(); list_bw->add_flag("--json,-j", print_json, localized("Output in JSON format") ); list_bw->set_callback([this] { //get entire table in scope of user account auto result = call(get_table_func, fc::mutable_variant_object("json", true) ("code", name(config::system_account_name).to_string()) ("scope", account.to_string()) ("table", "delband") ); if (!print_json) { auto res = result.as(); if ( !res.rows.empty() ) { std::cout << std::setw(13) << std::left << "Receiver" << std::setw(21) << std::left << "Net bandwidth" << std::setw(21) << std::left << "CPU bandwidth" << std::endl; for ( auto& r : res.rows ){ std::cout << std::setw(13) << std::left << r["to"].as_string() << std::setw(21) << std::left << r["net_weight"].as_string() << std::setw(21) << std::left << r["cpu_weight"].as_string() << std::endl; } } else { std::cerr << "Delegated bandwidth not found" << std::endl; } } else { std::cout << fc::json::to_pretty_string(result) << std::endl; } }); } }; struct buyram_subcommand { string from_str; string receiver_str; string amount; buyram_subcommand(CLI::App* actionRoot) { auto buyram = actionRoot->add_subcommand("buyram", localized("Buy RAM")); buyram->add_option("payer", from_str, localized("The account paying for RAM"))->required(); buyram->add_option("receiver", receiver_str, localized("The account receiving bought RAM"))->required(); buyram->add_option("tokens", amount, localized("The amount of EOS to pay for RAM"))->required(); add_standard_transaction_options(buyram); buyram->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("payer", from_str) ("receiver", receiver_str) ("quant", to_asset(amount)); send_actions({create_action({permission_level{from_str,config::active_name}}, config::system_account_name, N(buyram), act_payload)}); }); } }; struct sellram_subcommand { string from_str; string receiver_str; uint64_t amount; sellram_subcommand(CLI::App* actionRoot) { auto sellram = actionRoot->add_subcommand("sellram", localized("Sell RAM")); sellram->add_option("account", receiver_str, localized("The account to receive EOS for sold RAM"))->required(); sellram->add_option("bytes", amount, localized("Number of RAM bytes to sell"))->required(); add_standard_transaction_options(sellram); sellram->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("account", receiver_str) ("bytes", amount); send_actions({create_action({permission_level{receiver_str,config::active_name}}, config::system_account_name, N(sellram), act_payload)}); }); } }; struct claimrewards_subcommand { string owner; claimrewards_subcommand(CLI::App* actionRoot) { auto claim_rewards = actionRoot->add_subcommand("claimrewards", localized("Claim producer rewards")); claim_rewards->add_option("owner", owner, localized("The account to claim rewards for"))->required(); add_standard_transaction_options(claim_rewards); claim_rewards->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner); send_actions({create_action({permission_level{owner,config::active_name}}, config::system_account_name, N(claimrewards), act_payload)}); }); } }; struct regproxy_subcommand { string proxy; regproxy_subcommand(CLI::App* actionRoot) { auto register_proxy = actionRoot->add_subcommand("regproxy", localized("Register an account as a proxy (for voting)")); register_proxy->add_option("proxy", proxy, localized("The proxy account to register"))->required(); add_standard_transaction_options(register_proxy); register_proxy->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("proxy", proxy) ("isproxy", true); send_actions({create_action({permission_level{proxy,config::active_name}}, config::system_account_name, N(regproxy), act_payload)}); }); } }; struct unregproxy_subcommand { string proxy; unregproxy_subcommand(CLI::App* actionRoot) { auto unregister_proxy = actionRoot->add_subcommand("unregproxy", localized("Unregister an account as a proxy (for voting)")); unregister_proxy->add_option("proxy", proxy, localized("The proxy account to unregister"))->required(); add_standard_transaction_options(unregister_proxy); unregister_proxy->set_callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("proxy", proxy) ("isproxy", false); send_actions({create_action({permission_level{proxy,config::active_name}}, config::system_account_name, N(regproxy), act_payload)}); }); } }; struct canceldelay_subcommand { string canceling_account; string canceling_permission; string trx_id; canceldelay_subcommand(CLI::App* actionRoot) { auto cancel_delay = actionRoot->add_subcommand("canceldelay", localized("Cancel a delayed transaction")); cancel_delay->add_option("canceling_account", canceling_account, localized("Account from authorization on the original delayed transaction"))->required(); cancel_delay->add_option("canceling_permission", canceling_permission, localized("Permission from authorization on the original delayed transaction"))->required(); cancel_delay->add_option("trx_id", trx_id, localized("The transaction id of the original delayed transaction"))->required(); add_standard_transaction_options(cancel_delay); cancel_delay->set_callback([this] { const auto canceling_auth = permission_level{canceling_account, canceling_permission}; fc::variant act_payload = fc::mutable_variant_object() ("canceling_auth", canceling_auth) ("trx_id", trx_id); send_actions({create_action({canceling_auth}, config::system_account_name, N(canceldelay), act_payload)}); }); } }; void get_account( const string& accountName, bool json_format ) { auto json = call(get_account_func, fc::mutable_variant_object("account_name", accountName)); auto res = json.as(); if (!json_format) { std::cout << "privileged: " << ( res.privileged ? "true" : "false") << std::endl; constexpr size_t indent_size = 5; const string indent(indent_size, ' '); std::cout << "permissions: " << std::endl; unordered_map/*children*/> tree; vector roots; //we don't have multiple roots, but we can easily handle them here, so let's do it just in case unordered_map cache; for ( auto& perm : res.permissions ) { if ( perm.parent ) { tree[perm.parent].push_back( perm.perm_name ); } else { roots.push_back( perm.perm_name ); } auto name = perm.perm_name; //keep copy before moving `perm`, since thirst argument of emplace can be evaluated first // looks a little crazy, but should be efficient cache.insert( std::make_pair(name, std::move(perm)) ); } std::function dfs_print = [&]( account_name name, int depth ) -> void { auto& p = cache.at(name); std::cout << indent << std::string(depth*3, ' ') << name << ' ' << std::setw(5) << p.required_auth.threshold << ": "; for ( auto it = p.required_auth.keys.begin(); it != p.required_auth.keys.end(); ++it ) { if ( it != p.required_auth.keys.begin() ) { std::cout << ", "; } std::cout << it->weight << ' ' << string(it->key); } for ( auto& acc : p.required_auth.accounts ) { std::cout << acc.weight << ' ' << string(acc.permission.actor) << '@' << string(acc.permission.permission) << ", "; } std::cout << std::endl; auto it = tree.find( name ); if (it != tree.end()) { auto& children = it->second; sort( children.begin(), children.end() ); for ( auto& n : children ) { // we have a tree, not a graph, so no need to check for already visited nodes dfs_print( n, depth+1 ); } } // else it's a leaf node }; std::sort(roots.begin(), roots.end()); for ( auto r : roots ) { dfs_print( r, 0 ); } auto to_pretty_net = []( double bytes ) { string unit = "bytes"; if( bytes >= 1024*1024*1024*1024ll ){ unit = "Tb"; bytes /= 1024*1024*1024*1024ll; }else if( bytes >= 1024*1024*1024 ){ unit = "Gb"; bytes /= 1024*1024*1024; } else if( bytes >= 1024*1024 ){ unit = "Mb"; bytes /= 1024*1024; } else if( bytes >= 1024 ) { unit = "Kb"; bytes /= 1024; } std::stringstream ss; ss << setprecision(4); ss << bytes << " " << std::left << setw(5) << unit; return ss.str(); }; std::cout << "memory: " << std::endl << indent << "quota: " << std::setw(15) << to_pretty_net(res.ram_quota) << " used: " << std::setw(15) << to_pretty_net(res.ram_usage) << std::endl << std::endl; std::cout << "net bandwidth: (averaged over 3 days)" << std::endl; if ( res.total_resources.is_object() ) { asset net_own = res.delegated_bandwidth.is_object() ? asset::from_string( res.delegated_bandwidth.get_object()["net_weight"].as_string() ) : asset(0) ; auto net_others = to_asset(res.total_resources.get_object()["net_weight"].as_string()) - net_own; std::cout << indent << "staked:" << std::setw(20) << net_own << std::string(11, ' ') << "(total stake delegated from account to self)" << std::endl << indent << "delegated:" << std::setw(17) << net_others << std::string(11, ' ') << "(total staked delegated to account from others)" << std::endl; } auto to_pretty_time = []( double micro ) { string unit = "us"; if( micro > 1000000*60 ) { micro /= 1000000*60*60ll; unit = "hr"; } else if( micro > 1000000*60 ) { micro /= 1000000*60; unit = "min"; } else if( micro > 1000000 ) { micro /= 1000000; unit = "sec"; } else if( micro > 1000 ) { micro /= 1000; unit = "ms"; } std::stringstream ss; ss << setprecision(4); ss << micro<< " " << std::left << setw(5) << unit; return ss.str(); }; std::cout << std::fixed << setprecision(3); std::cout << indent << std::left << std::setw(11) << "used:" << std::right << std::setw(18) << to_pretty_net( res.net_limit.used ) << "\n"; std::cout << indent << std::left << std::setw(11) << "available:" << std::right << std::setw(18) << to_pretty_net( res.net_limit.available ) << "\n"; std::cout << indent << std::left << std::setw(11) << "limit:" << std::right << std::setw(18) << to_pretty_net( res.net_limit.max ) << "\n"; std::cout << std::endl; std::cout << "cpu bandwidth: (averaged over 3 days)" << std::endl; if ( res.total_resources.is_object() ) { asset cpu_own = res.delegated_bandwidth.is_object() ? asset::from_string( res.delegated_bandwidth.get_object()["cpu_weight"].as_string() ) : asset(0) ; auto cpu_others = to_asset(res.total_resources.get_object()["cpu_weight"].as_string()) - cpu_own; std::cout << indent << "staked:" << std::setw(20) << cpu_own << std::string(11, ' ') << "(total stake delegated from account to self)" << std::endl << indent << "delegated:" << std::setw(17) << cpu_others << std::string(11, ' ') << "(total staked delegated to account from others)" << std::endl; } std::cout << std::fixed << setprecision(3); std::cout << indent << std::left << std::setw(11) << "used:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.used ) << "\n"; std::cout << indent << std::left << std::setw(11) << "available:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.available ) << "\n"; std::cout << indent << std::left << std::setw(11) << "limit:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.max ) << "\n"; std::cout << std::endl; if ( res.voter_info.is_object() ) { auto& obj = res.voter_info.get_object(); string proxy = obj["proxy"].as_string(); if ( proxy.empty() ) { auto& prods = obj["producers"].get_array(); std::cout << "producers:"; if ( !prods.empty() ) { for ( int i = 0; i < prods.size(); ++i ) { if ( i%3 == 0 ) { std::cout << std::endl << indent; } std::cout << std::setw(16) << std::left << prods[i].as_string(); } std::cout << std::endl; } else { std::cout << indent << "" << std::endl; } } else { std::cout << "proxy:" << indent << proxy << std::endl; } } std::cout << std::endl; } else { std::cout << fc::json::to_pretty_string(json) << std::endl; } } CLI::callback_t header_opt_callback = [](CLI::results_t res) { vector::iterator itr; for (itr = res.begin(); itr != res.end(); itr++) { headers.push_back(*itr); } return true; }; int main( int argc, char** argv ) { setlocale(LC_ALL, ""); bindtextdomain(locale_domain, locale_path); textdomain(locale_domain); CLI::App app{"Command Line Interface to EOSIO Client"}; app.require_subcommand(); app.add_option( "-H,--host", obsoleted_option_host_port, localized("the host where nodeos is running") )->group("hidden"); app.add_option( "-p,--port", obsoleted_option_host_port, localized("the port where nodeos is running") )->group("hidden"); app.add_option( "--wallet-host", obsoleted_option_host_port, localized("the host where keosd is running") )->group("hidden"); app.add_option( "--wallet-port", obsoleted_option_host_port, localized("the port where keosd is running") )->group("hidden"); app.add_option( "-u,--url", url, localized("the http/https URL where nodeos is running"), true ); app.add_option( "--wallet-url", wallet_url, localized("the http/https URL where keosd is running"), true ); app.add_option( "-r,--header", header_opt_callback, localized("pass specific HTTP header; repeat this option to pass multiple headers")); app.add_flag( "-n,--no-verify", no_verify, localized("don't verify peer certificate when using HTTPS")); app.set_callback([&app]{ ensure_keosd_running(&app);}); bool verbose_errors = false; app.add_flag( "-v,--verbose", verbose_errors, localized("output verbose actions on error")); auto version = app.add_subcommand("version", localized("Retrieve version information"), false); version->require_subcommand(); version->add_subcommand("client", localized("Retrieve version information of the client"))->set_callback([] { std::cout << localized("Build version: ${ver}", ("ver", eosio::client::config::version_str)) << std::endl; }); // Create subcommand auto create = app.add_subcommand("create", localized("Create various items, on and off the blockchain"), false); create->require_subcommand(); // create key create->add_subcommand("key", localized("Create a new keypair and print the public and private keys"))->set_callback( [](){ auto pk = private_key_type::generate(); auto privs = string(pk); auto pubs = string(pk.get_public_key()); std::cout << localized("Private key: ${key}", ("key", privs) ) << std::endl; std::cout << localized("Public key: ${key}", ("key", pubs ) ) << std::endl; }); // create account auto createAccount = create_account_subcommand( create, true /*simple*/ ); // Get subcommand auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false); get->require_subcommand(); // get info get->add_subcommand("info", localized("Get current blockchain information"))->set_callback([] { std::cout << fc::json::to_pretty_string(get_info()) << std::endl; }); // get block string blockArg; auto getBlock = get->add_subcommand("block", localized("Retrieve a full block from the blockchain"), false); getBlock->add_option("block", blockArg, localized("The number or ID of the block to retrieve"))->required(); getBlock->set_callback([&blockArg] { auto arg = fc::mutable_variant_object("block_num_or_id", blockArg); std::cout << fc::json::to_pretty_string(call(get_block_func, arg)) << std::endl; }); // get account string accountName; bool print_json; auto getAccount = get->add_subcommand("account", localized("Retrieve an account from the blockchain"), false); getAccount->add_option("name", accountName, localized("The name of the account to retrieve"))->required(); getAccount->add_flag("--json,-j", print_json, localized("Output in JSON format") ); getAccount->set_callback([&]() { get_account(accountName, print_json); }); // get code string codeFilename; string abiFilename; bool code_as_wasm = false; auto getCode = get->add_subcommand("code", localized("Retrieve the code and ABI for an account"), false); getCode->add_option("name", accountName, localized("The name of the account whose code should be retrieved"))->required(); getCode->add_option("-c,--code",codeFilename, localized("The name of the file to save the contract .wast/wasm to") ); getCode->add_option("-a,--abi",abiFilename, localized("The name of the file to save the contract .abi to") ); getCode->add_flag("--wasm", code_as_wasm, localized("Save contract as wasm")); getCode->set_callback([&] { auto result = call(get_code_func, fc::mutable_variant_object("account_name", accountName)("code_as_wasm",code_as_wasm)); std::cout << localized("code hash: ${code_hash}", ("code_hash", result["code_hash"].as_string())) << std::endl; if( codeFilename.size() ){ std::cout << localized("saving ${type} to ${codeFilename}", ("type", (code_as_wasm ? "wasm" : "wast"))("codeFilename", codeFilename)) << std::endl; string code; if(code_as_wasm) { code = result["wasm"].as_string(); } else { code = result["wast"].as_string(); } std::ofstream out( codeFilename.c_str() ); out << code; } if( abiFilename.size() ) { std::cout << localized("saving abi to ${abiFilename}", ("abiFilename", abiFilename)) << std::endl; auto abi = fc::json::to_pretty_string( result["abi"] ); std::ofstream abiout( abiFilename.c_str() ); abiout << abi; } }); // get abi string filename; auto getAbi = get->add_subcommand("abi", localized("Retrieve the ABI for an account"), false); getAbi->add_option("name", accountName, localized("The name of the account whose abi should be retrieved"))->required(); getAbi->add_option("-f,--file",filename, localized("The name of the file to save the contract .abi to instead of writing to console") ); getAbi->set_callback([&] { auto result = call(get_code_func, fc::mutable_variant_object("account_name", accountName)); auto abi = fc::json::to_pretty_string( result["abi"] ); if( filename.size() ) { std::cout << localized("saving abi to ${filename}", ("filename", filename)) << std::endl; std::ofstream abiout( filename.c_str() ); abiout << abi; } else { std::cout << abi << "\n"; } }); // get table string scope; string code; string table; string lower; string upper; string table_key; bool binary = false; uint32_t limit = 10; auto getTable = get->add_subcommand( "table", localized("Retrieve the contents of a database table"), false); getTable->add_option( "contract", code, localized("The contract who owns the table") )->required(); getTable->add_option( "scope", scope, localized("The scope within the contract in which the table is found") )->required(); getTable->add_option( "table", table, localized("The name of the table as specified by the contract abi") )->required(); getTable->add_option( "-b,--binary", binary, localized("Return the value as BINARY rather than using abi to interpret as JSON") ); getTable->add_option( "-l,--limit", limit, localized("The maximum number of rows to return") ); getTable->add_option( "-k,--key", table_key, localized("The name of the key to index by as defined by the abi, defaults to primary key") ); getTable->add_option( "-L,--lower", lower, localized("JSON representation of lower bound value of key, defaults to first") ); getTable->add_option( "-U,--upper", upper, localized("JSON representation of upper bound value value of key, defaults to last") ); getTable->set_callback([&] { auto result = call(get_table_func, fc::mutable_variant_object("json", !binary) ("code",code) ("scope",scope) ("table",table) ("table_key",table_key) ("lower_bound",lower) ("upper_bound",upper) ("limit",limit) ); std::cout << fc::json::to_pretty_string(result) << std::endl; }); // currency accessors // get currency balance string symbol; auto get_currency = get->add_subcommand( "currency", localized("Retrieve information related to standard currencies"), true); get_currency->require_subcommand(); auto get_balance = get_currency->add_subcommand( "balance", localized("Retrieve the balance of an account for a given currency"), false); get_balance->add_option( "contract", code, localized("The contract that operates the currency") )->required(); get_balance->add_option( "account", accountName, localized("The account to query balances for") )->required(); get_balance->add_option( "symbol", symbol, localized("The symbol for the currency if the contract operates multiple currencies") ); get_balance->set_callback([&] { auto result = call(get_currency_balance_func, fc::mutable_variant_object ("account", accountName) ("code", code) ("symbol", symbol.empty() ? fc::variant() : symbol) ); const auto& rows = result.get_array(); for( const auto& r : rows ) { std::cout << r.as_string() << std::endl; } }); auto get_currency_stats = get_currency->add_subcommand( "stats", localized("Retrieve the stats of for a given currency"), false); get_currency_stats->add_option( "contract", code, localized("The contract that operates the currency") )->required(); get_currency_stats->add_option( "symbol", symbol, localized("The symbol for the currency if the contract operates multiple currencies") )->required(); get_currency_stats->set_callback([&] { auto result = call(get_currency_stats_func, fc::mutable_variant_object("json", false) ("code", code) ("symbol", symbol) ); std::cout << fc::json::to_pretty_string(result) << std::endl; }); // get accounts string public_key_str; auto getAccounts = get->add_subcommand("accounts", localized("Retrieve accounts associated with a public key"), false); getAccounts->add_option("public_key", public_key_str, localized("The public key to retrieve accounts for"))->required(); getAccounts->set_callback([&] { public_key_type public_key; try { public_key = public_key_type(public_key_str); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", public_key_str)) auto arg = fc::mutable_variant_object( "public_key", public_key); std::cout << fc::json::to_pretty_string(call(get_key_accounts_func, arg)) << std::endl; }); // get servants string controllingAccount; auto getServants = get->add_subcommand("servants", localized("Retrieve accounts which are servants of a given account "), false); getServants->add_option("account", controllingAccount, localized("The name of the controlling account"))->required(); getServants->set_callback([&] { auto arg = fc::mutable_variant_object( "controlling_account", controllingAccount); std::cout << fc::json::to_pretty_string(call(get_controlled_accounts_func, arg)) << std::endl; }); // get transaction string transaction_id_str; auto getTransaction = get->add_subcommand("transaction", localized("Retrieve a transaction from the blockchain"), false); getTransaction->add_option("id", transaction_id_str, localized("ID of the transaction to retrieve"))->required(); getTransaction->set_callback([&] { transaction_id_type transaction_id; try { while( transaction_id_str.size() < 64 ) transaction_id_str += "0"; transaction_id = transaction_id_type(transaction_id_str); } EOS_RETHROW_EXCEPTIONS(transaction_id_type_exception, "Invalid transaction ID: ${transaction_id}", ("transaction_id", transaction_id_str)) auto arg= fc::mutable_variant_object( "id", transaction_id); std::cout << fc::json::to_pretty_string(call(get_transaction_func, arg)) << std::endl; }); // get actions string account_name; string skip_seq_str; string num_seq_str; bool printjson = false; bool fullact = false; bool prettyact = false; bool printconsole = false; int32_t pos_seq = -1; int32_t offset = -20; auto getActions = get->add_subcommand("actions", localized("Retrieve all actions with specific account name referenced in authorization or receiver"), false); getActions->add_option("account_name", account_name, localized("name of account to query on"))->required(); getActions->add_option("pos", pos_seq, localized("sequence number of action for this account, -1 for last")); getActions->add_option("offset", offset, localized("get actions [pos,pos+offset] for positive offset or [pos-offset,pos) for negative offset")); getActions->add_flag("--json,-j", printjson, localized("print full json")); getActions->add_flag("--full", fullact, localized("don't truncate action json")); getActions->add_flag("--pretty", prettyact, localized("pretty print full action json ")); getActions->add_flag("--console", printconsole, localized("print console output generated by action ")); getActions->set_callback([&] { fc::mutable_variant_object arg; arg( "account_name", account_name ); arg( "pos", pos_seq ); arg( "offset", offset); auto result = call(get_actions_func, arg); if( printjson ) { std::cout << fc::json::to_pretty_string(result) << std::endl; } else { auto& traces = result["actions"].get_array(); uint32_t lib = result["last_irreversible_block"].as_uint64(); cout << "#" << setw(5) << "seq" << " " << setw( 24 ) << left << "when"<< " " << setw(24) << right << "contract::action" << " => " << setw(13) << left << "receiver" << " " << setw(11) << left << "trx id..." << " args\n"; cout << "================================================================================================================\n"; for( const auto& trace: traces ) { std::stringstream out; if( trace["block_num"].as_uint64() <= lib ) out << "#"; else out << "?"; out << setw(5) << trace["account_action_seq"].as_uint64() <<" "; out << setw(24) << trace["block_time"].as_string() <<" "; const auto& at = trace["action_trace"].get_object(); auto id = at["trx_id"].as_string(); const auto& receipt = at["receipt"]; auto receiver = receipt["receiver"].as_string(); const auto& act = at["act"].get_object(); auto code = act["account"].as_string(); auto func = act["name"].as_string(); string args; if( prettyact ) { args = fc::json::to_pretty_string( act["data"] ); } else { args = fc::json::to_string( act["data"] ); if( !fullact ) { args = args.substr(0,60) + "..."; } } out << std::setw(24) << std::right<< (code +"::" + func) << " => " << left << std::setw(13) << receiver; out << " " << setw(11) << (id.substr(0,8) + "..."); if( fullact || prettyact ) out << "\n"; else out << " "; out << args ;//<< "\n"; if( trace["block_num"].as_uint64() <= lib ) { dlog( "\r${m}", ("m",out.str()) ); } else { wlog( "\r${m}", ("m",out.str()) ); } if( printconsole ) { auto console = at["console"].as_string(); if( console.size() ) { stringstream out; std::stringstream ss(console); string line; std::getline( ss, line ); out << ">> " << line << "\n"; cerr << out.str(); //ilog( "\r${m} ", ("m",out.str()) ); } } } } }); /* auto getTransactions = get->add_subcommand("transactions", localized("Retrieve all transactions with specific account name referenced in their scope"), false); getTransactions->add_option("account_name", account_name, localized("name of account to query on"))->required(); getTransactions->add_option("skip_seq", skip_seq_str, localized("Number of most recent transactions to skip (0 would start at most recent transaction)")); getTransactions->add_option("num_seq", num_seq_str, localized("Number of transactions to return")); getTransactions->add_flag("--json,-j", printjson, localized("print full json")); getTransactions->set_callback([&] { fc::mutable_variant_object arg; if (skip_seq_str.empty()) { arg = fc::mutable_variant_object( "account_name", account_name); } else { uint64_t skip_seq; try { skip_seq = boost::lexical_cast(skip_seq_str); } EOS_RETHROW_EXCEPTIONS(chain_type_exception, "Invalid Skip Seq: ${skip_seq}", ("skip_seq", skip_seq_str)) if (num_seq_str.empty()) { arg = fc::mutable_variant_object( "account_name", account_name)("skip_seq", skip_seq); } else { uint64_t num_seq; try { num_seq = boost::lexical_cast(num_seq_str); } EOS_RETHROW_EXCEPTIONS(chain_type_exception, "Invalid Num Seq: ${num_seq}", ("num_seq", num_seq_str)) arg = fc::mutable_variant_object( "account_name", account_name)("skip_seq", skip_seq_str)("num_seq", num_seq); } } auto result = call(get_transactions_func, arg); if( printjson ) { std::cout << fc::json::to_pretty_string(result) << std::endl; } else { const auto& trxs = result.get_object()["transactions"].get_array(); for( const auto& t : trxs ) { const auto& tobj = t.get_object(); const auto& trx = tobj["transaction"].get_object(); const auto& data = trx["transaction"].get_object(); const auto& msgs = data["actions"].get_array(); for( const auto& msg : msgs ) { int64_t seq_num = tobj["seq_num"].as(); string id = tobj["transaction_id"].as_string(); const auto& exp = data["expiration"].as(); std::cout << tobj["seq_num"].as_string() <<"] " << id.substr(0,8) << "... " << data["expiration"].as_string() << " "; auto code = msg["account"].as_string(); auto func = msg["name"].as_string(); auto args = fc::json::to_string( msg["data"] ); std::cout << setw(26) << left << (code + "::" + func) << " " << args; std::cout << std::endl; } } } }); */ // set subcommand auto setSubcommand = app.add_subcommand("set", localized("Set or update blockchain state")); setSubcommand->require_subcommand(); // set contract subcommand string account; string contractPath; string wastPath; string abiPath; bool shouldSend = true; auto codeSubcommand = setSubcommand->add_subcommand("code", localized("Create or update the code on an account")); codeSubcommand->add_option("account", account, localized("The account to set code for"))->required(); codeSubcommand->add_option("code-file", wastPath, localized("The fullpath containing the contract WAST or WASM"))->required(); auto abiSubcommand = setSubcommand->add_subcommand("abi", localized("Create or update the abi on an account")); abiSubcommand->add_option("account", account, localized("The account to set the ABI for"))->required(); abiSubcommand->add_option("abi-file", abiPath, localized("The fullpath containing the contract WAST or WASM"))->required(); auto contractSubcommand = setSubcommand->add_subcommand("contract", localized("Create or update the contract on an account")); contractSubcommand->add_option("account", account, localized("The account to publish a contract for")) ->required(); contractSubcommand->add_option("contract-dir", contractPath, localized("The path containing the .wast and .abi")) ->required(); contractSubcommand->add_option("wast-file", wastPath, localized("The file containing the contract WAST or WASM relative to contract-dir")); // ->check(CLI::ExistingFile); auto abi = contractSubcommand->add_option("abi-file,-a,--abi", abiPath, localized("The ABI for the contract relative to contract-dir")); // ->check(CLI::ExistingFile); std::vector actions; auto set_code_callback = [&]() { std::string wast; fc::path cpath(contractPath); if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); if( wastPath.empty() ) { wastPath = (cpath / (cpath.filename().generic_string()+".wasm")).generic_string(); if (!fc::exists(wastPath)) wastPath = (cpath / (cpath.filename().generic_string()+".wast")).generic_string(); } std::cout << localized(("Reading WAST/WASM from " + wastPath + "...").c_str()) << std::endl; fc::read_file_contents(wastPath, wast); FC_ASSERT( !wast.empty(), "no wast file found ${f}", ("f", wastPath) ); vector wasm; const string binary_wasm_header("\x00\x61\x73\x6d", 4); if(wast.compare(0, 4, binary_wasm_header) == 0) { std::cout << localized("Using already assembled WASM...") << std::endl; wasm = vector(wast.begin(), wast.end()); } else { std::cout << localized("Assembling WASM...") << std::endl; wasm = wast_to_wasm(wast); } actions.emplace_back( create_setcode(account, bytes(wasm.begin(), wasm.end()) ) ); if ( shouldSend ) { std::cout << localized("Setting Code...") << std::endl; send_actions(std::move(actions), 10000, packed_transaction::zlib); } }; auto set_abi_callback = [&]() { fc::path cpath(contractPath); if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); if( abiPath.empty() ) { abiPath = (cpath / (cpath.filename().generic_string()+".abi")).generic_string(); } FC_ASSERT( fc::exists( abiPath ), "no abi file found ${f}", ("f", abiPath) ); try { actions.emplace_back( create_setabi(account, fc::json::from_file(abiPath).as()) ); } EOS_RETHROW_EXCEPTIONS(abi_type_exception, "Fail to parse ABI JSON") if ( shouldSend ) { std::cout << localized("Setting ABI...") << std::endl; send_actions(std::move(actions), 10000, packed_transaction::zlib); } }; add_standard_transaction_options(contractSubcommand, "account@active"); add_standard_transaction_options(codeSubcommand, "account@active"); add_standard_transaction_options(abiSubcommand, "account@active"); contractSubcommand->set_callback([&] { shouldSend = false; set_code_callback(); set_abi_callback(); std::cout << localized("Publishing contract...") << std::endl; send_actions(std::move(actions), 10000, packed_transaction::zlib); }); codeSubcommand->set_callback(set_code_callback); abiSubcommand->set_callback(set_abi_callback); // set account auto setAccount = setSubcommand->add_subcommand("account", localized("set or update blockchain account state"))->require_subcommand(); // set account permission auto setAccountPermission = set_account_permission_subcommand(setAccount); // set action auto setAction = setSubcommand->add_subcommand("action", localized("set or update blockchain action state"))->require_subcommand(); // set action permission auto setActionPermission = set_action_permission_subcommand(setAction); // Transfer subcommand string con = "eosio.token"; string sender; string recipient; string amount; string memo; auto transfer = app.add_subcommand("transfer", localized("Transfer EOS from account to account"), false); transfer->add_option("sender", sender, localized("The account sending EOS"))->required(); transfer->add_option("recipient", recipient, localized("The account receiving EOS"))->required(); transfer->add_option("amount", amount, localized("The amount of EOS to send"))->required(); transfer->add_option("memo", memo, localized("The memo for the transfer")); transfer->add_option("--contract,-c", con, localized("The contract which controls the token")); add_standard_transaction_options(transfer, "sender@active"); transfer->set_callback([&] { signed_transaction trx; if (tx_force_unique && memo.size() == 0) { // use the memo to add a nonce memo = generate_nonce_string(); tx_force_unique = false; } send_actions({create_transfer(con,sender, recipient, to_asset(amount), memo)}); }); // Net subcommand string new_host; auto net = app.add_subcommand( "net", localized("Interact with local p2p network connections"), false ); net->require_subcommand(); auto connect = net->add_subcommand("connect", localized("start a new connection to a peer"), false); connect->add_option("host", new_host, localized("The hostname:port to connect to."))->required(); connect->set_callback([&] { const auto& v = call(url, net_connect, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto disconnect = net->add_subcommand("disconnect", localized("close an existing connection"), false); disconnect->add_option("host", new_host, localized("The hostname:port to disconnect from."))->required(); disconnect->set_callback([&] { const auto& v = call(url, net_disconnect, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto status = net->add_subcommand("status", localized("status of existing connection"), false); status->add_option("host", new_host, localized("The hostname:port to query status of connection"))->required(); status->set_callback([&] { const auto& v = call(url, net_status, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto connections = net->add_subcommand("peers", localized("status of all existing peers"), false); connections->set_callback([&] { const auto& v = call(url, net_connections, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); // Wallet subcommand auto wallet = app.add_subcommand( "wallet", localized("Interact with local wallet"), false ); wallet->require_subcommand(); // create wallet string wallet_name = "default"; auto createWallet = wallet->add_subcommand("create", localized("Create a new wallet locally"), false); createWallet->add_option("-n,--name", wallet_name, localized("The name of the new wallet"), true); createWallet->set_callback([&wallet_name] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); const auto& v = call(wallet_url, wallet_create, wallet_name); std::cout << localized("Creating wallet: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; std::cout << localized("Save password to use in the future to unlock this wallet.") << std::endl; std::cout << localized("Without password imported keys will not be retrievable.") << std::endl; std::cout << fc::json::to_pretty_string(v) << std::endl; }); // open wallet auto openWallet = wallet->add_subcommand("open", localized("Open an existing wallet"), false); openWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to open")); openWallet->set_callback([&wallet_name] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); call(wallet_url, wallet_open, wallet_name); std::cout << localized("Opened: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; }); // lock wallet auto lockWallet = wallet->add_subcommand("lock", localized("Lock wallet"), false); lockWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to lock")); lockWallet->set_callback([&wallet_name] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); call(wallet_url, wallet_lock, wallet_name); std::cout << localized("Locked: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; }); // lock all wallets auto locakAllWallets = wallet->add_subcommand("lock_all", localized("Lock all unlocked wallets"), false); locakAllWallets->set_callback([] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); call(wallet_url, wallet_lock_all); std::cout << localized("Locked All Wallets") << std::endl; }); // unlock wallet string wallet_pw; auto unlockWallet = wallet->add_subcommand("unlock", localized("Unlock wallet"), false); unlockWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to unlock")); unlockWallet->add_option("--password", wallet_pw, localized("The password returned by wallet create")); unlockWallet->add_option( "--unlock-timeout", wallet_unlock_timeout, localized("The timeout for unlocked wallet in seconds")); unlockWallet->set_callback([&wallet_name, &wallet_pw] { if( wallet_pw.size() == 0 ) { std::cout << localized("password: "); fc::set_console_echo(false); std::getline( std::cin, wallet_pw, '\n' ); fc::set_console_echo(true); } // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)}; call(wallet_url, wallet_unlock, vs); std::cout << localized("Unlocked: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; }); // import keys into wallet string wallet_key_str; auto importWallet = wallet->add_subcommand("import", localized("Import private key into wallet"), false); importWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to import key into")); importWallet->add_option("key", wallet_key_str, localized("Private key in WIF format to import"))->required(); importWallet->set_callback([&wallet_name, &wallet_key_str] { private_key_type wallet_key; try { wallet_key = private_key_type( wallet_key_str ); } catch (...) { EOS_THROW(private_key_type_exception, "Invalid private key: ${private_key}", ("private_key", wallet_key_str)) } public_key_type pubkey = wallet_key.get_public_key(); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_key)}; call(wallet_url, wallet_import_key, vs); std::cout << localized("imported private key for: ${pubkey}", ("pubkey", std::string(pubkey))) << std::endl; }); // create a key within wallet string wallet_create_key_type; auto createKeyInWallet = wallet->add_subcommand("create_key", localized("Create private key within wallet"), false); createKeyInWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to create key into"), true); createKeyInWallet->add_option("key_type", wallet_create_key_type, localized("Key type to create (K1/R1)"), true)->set_type_name("K1/R1"); createKeyInWallet->set_callback([&wallet_name, &wallet_create_key_type] { //an empty key type is allowed -- it will let the underlying wallet pick which type it prefers fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_create_key_type)}; const auto& v = call(wallet_url, wallet_create_key, vs); std::cout << localized("Created new private key with a public key of: ") << fc::json::to_pretty_string(v) << std::endl; }); // list wallets auto listWallet = wallet->add_subcommand("list", localized("List opened wallets, * = unlocked"), false); listWallet->set_callback([] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); std::cout << localized("Wallets:") << std::endl; const auto& v = call(wallet_url, wallet_list); std::cout << fc::json::to_pretty_string(v) << std::endl; }); // list keys auto listKeys = wallet->add_subcommand("keys", localized("List of public keys from all unlocked wallets."), false); listKeys->set_callback([] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); const auto& v = call(wallet_url, wallet_public_keys); std::cout << fc::json::to_pretty_string(v) << std::endl; }); // list private keys auto listPrivKeys = wallet->add_subcommand("private_keys", localized("List of private keys from an unlocked wallet in wif or PVT_R1 format."), false); listPrivKeys->add_option("-n,--name", wallet_name, localized("The name of the wallet to list keys from"), true); listPrivKeys->add_option("--password", wallet_pw, localized("The password returned by wallet create")); listPrivKeys->set_callback([&wallet_name, &wallet_pw] { if( wallet_pw.size() == 0 ) { std::cout << localized("password: "); fc::set_console_echo(false); std::getline( std::cin, wallet_pw, '\n' ); fc::set_console_echo(true); } // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)}; const auto& v = call(wallet_url, wallet_list_keys, vs); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto stopKeosd = wallet->add_subcommand("stop", localized("Stop keosd (doesn't work with nodeos)."), false); stopKeosd->set_callback([] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); const auto& v = call(wallet_url, keosd_stop); if ( !v.is_object() || v.get_object().size() != 0 ) { //on success keosd responds with empty object std::cerr << fc::json::to_pretty_string(v) << std::endl; } else { std::cout << "OK" << std::endl; } }); // sign subcommand string trx_json_to_sign; string str_private_key; string str_chain_id; bool push_trx = false; auto sign = app.add_subcommand("sign", localized("Sign a transaction"), false); sign->add_option("transaction", trx_json_to_sign, localized("The JSON string or filename defining the transaction to sign"), true)->required(); sign->add_option("-k,--private-key", str_private_key, localized("The private key that will be used to sign the transaction")); sign->add_option("-c,--chain-id", str_chain_id, localized("The chain id that will be used to sign the transaction")); sign->add_flag( "-p,--push-transaction", push_trx, localized("Push transaction after signing")); sign->set_callback([&] { signed_transaction trx = json_from_file_or_string(trx_json_to_sign).as(); fc::optional chain_id; if( str_chain_id.size() == 0 ) { ilog( "grabbing chain_id from nodeos" ); auto info = get_info(); chain_id = info.chain_id; } else { chain_id = chain_id_type(str_chain_id); } if( str_private_key.size() == 0 ) { std::cerr << localized("private key: "); fc::set_console_echo(false); std::getline( std::cin, str_private_key, '\n' ); fc::set_console_echo(true); } auto priv_key = fc::crypto::private_key::regenerate(*utilities::wif_to_key(str_private_key)); trx.sign(priv_key, *chain_id); if(push_trx) { auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); std::cout << fc::json::to_pretty_string(trx_result) << std::endl; } else { std::cout << fc::json::to_pretty_string(trx) << std::endl; } }); // Push subcommand auto push = app.add_subcommand("push", localized("Push arbitrary transactions to the blockchain"), false); push->require_subcommand(); // push action string contract_account; string action; string data; vector permissions; auto actionsSubcommand = push->add_subcommand("action", localized("Push a transaction with a single action")); actionsSubcommand->fallthrough(false); actionsSubcommand->add_option("account", contract_account, localized("The account providing the contract to execute"), true)->required(); actionsSubcommand->add_option("action", action, localized("A JSON string or filename defining the action to execute on the contract"), true)->required(); actionsSubcommand->add_option("data", data, localized("The arguments to the contract"))->required(); add_standard_transaction_options(actionsSubcommand); actionsSubcommand->set_callback([&] { fc::variant action_args_var; try { action_args_var = json_from_file_or_string(data, fc::json::relaxed_parser); } EOS_RETHROW_EXCEPTIONS(action_type_exception, "Fail to parse action JSON data='${data}'", ("data",data)) auto arg= fc::mutable_variant_object ("code", contract_account) ("action", action) ("args", action_args_var); auto result = call(json_to_bin_func, arg); auto accountPermissions = get_account_permissions(tx_permission); send_actions({chain::action{accountPermissions, contract_account, action, result.get_object()["binargs"].as()}}); }); // push transaction string trx_to_push; auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction")); trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); trxSubcommand->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(trx_to_push); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_push)) signed_transaction trx = trx_var.as(); auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); std::cout << fc::json::to_pretty_string(trx_result) << std::endl; }); string trxsJson; auto trxsSubcommand = push->add_subcommand("transactions", localized("Push an array of arbitrary JSON transactions")); trxsSubcommand->add_option("transactions", trxsJson, localized("The JSON string or filename defining the array of the transactions to push"))->required(); trxsSubcommand->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(trxsJson); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trxsJson)) auto trxs_result = call(push_txns_func, trx_var); std::cout << fc::json::to_pretty_string(trxs_result) << std::endl; }); // multisig subcommand auto msig = app.add_subcommand("multisig", localized("Multisig contract commands"), false); msig->require_subcommand(); // multisig propose string proposal_name; string requested_perm; string transaction_perm; string proposed_transaction; string proposed_contract; string proposed_action; string proposer; unsigned int proposal_expiration_hours = 24; CLI::callback_t parse_expiration_hours = [&](CLI::results_t res) -> bool { unsigned int value_s; if (res.size() == 0 || !CLI::detail::lexical_cast(res[0], value_s)) { return false; } proposal_expiration_hours = static_cast(value_s); return true; }; auto propose_action = msig->add_subcommand("propose", localized("Propose transaction")); //auto propose_action = msig->add_subcommand("action", localized("Propose action")); add_standard_transaction_options(propose_action); propose_action->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); propose_action->add_option("requested_permissions", requested_perm, localized("The JSON string or filename defining requested permissions"))->required(); propose_action->add_option("trx_permissions", transaction_perm, localized("The JSON string or filename defining transaction permissions"))->required(); propose_action->add_option("contract", proposed_contract, localized("contract to wich deferred transaction should be delivered"))->required(); propose_action->add_option("action", proposed_action, localized("action of deferred transaction"))->required(); propose_action->add_option("data", proposed_transaction, localized("The JSON string or filename defining the action to propose"))->required(); propose_action->add_option("proposer", proposer, localized("Account proposing the transaction")); propose_action->add_option("proposal_expiration", parse_expiration_hours, localized("Proposal expiration interval in hours")); propose_action->set_callback([&] { fc::variant requested_perm_var; try { requested_perm_var = json_from_file_or_string(requested_perm); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse permissions JSON '${data}'", ("data",requested_perm)) fc::variant transaction_perm_var; try { transaction_perm_var = json_from_file_or_string(transaction_perm); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse permissions JSON '${data}'", ("data",transaction_perm)) fc::variant trx_var; try { trx_var = json_from_file_or_string(proposed_transaction); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",proposed_transaction)) transaction proposed_trx = trx_var.as(); auto arg= fc::mutable_variant_object() ("code", proposed_contract) ("action", proposed_action) ("args", trx_var); auto result = call(json_to_bin_func, arg); //std::cout << "Result: "; fc::json::to_stream(std::cout, result); std::cout << std::endl; bytes proposed_trx_serialized = result.get_object()["binargs"].as(); vector reqperm; try { reqperm = requested_perm_var.as>(); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Wrong requested permissions format: '${data}'", ("data",requested_perm_var)); vector trxperm; try { trxperm = transaction_perm_var.as>(); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Wrong transaction permissions format: '${data}'", ("data",transaction_perm_var)); auto accountPermissions = get_account_permissions(tx_permission); if (accountPermissions.empty()) { if (!proposer.empty()) { accountPermissions = vector{{proposer, config::active_name}}; } else { EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (proposer.empty()) { proposer = name(accountPermissions.at(0).actor).to_string(); } transaction trx; trx.expiration = fc::time_point_sec( fc::time_point::now() + fc::hours(proposal_expiration_hours) ); trx.ref_block_num = 0; trx.ref_block_prefix = 0; trx.max_net_usage_words = 0; trx.max_cpu_usage_ms = 0; trx.delay_sec = 0; trx.actions = { chain::action(trxperm, name(proposed_contract), name(proposed_action), proposed_trx_serialized) }; fc::to_variant(trx, trx_var); arg = fc::mutable_variant_object() ("code", "eosio.msig") ("action", "propose") ("args", fc::mutable_variant_object() ("proposer", proposer ) ("proposal_name", proposal_name) ("requested", requested_perm_var) ("trx", trx_var) ); result = call(json_to_bin_func, arg); send_actions({chain::action{accountPermissions, "eosio.msig", "propose", result.get_object()["binargs"].as()}}); }); //resolver for ABI serializer to decode actions in proposed transaction in multisig contract auto resolver = [](const name& code) -> optional { auto result = call(get_code_func, fc::mutable_variant_object("account_name", code.to_string())); if (result["abi"].is_object()) { //std::cout << "ABI: " << fc::json::to_pretty_string(result) << std::endl; return optional(abi_serializer(result["abi"].as())); } else { std::cerr << "ABI for contract " << code.to_string() << " not found. Action data will be shown in hex only." << std::endl; return optional(); } }; // multisig review auto review = msig->add_subcommand("review", localized("Review transaction")); add_standard_transaction_options(review); review->add_option("proposer", proposer, localized("proposer name (string)"))->required(); review->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); review->set_callback([&] { auto result = call(get_table_func, fc::mutable_variant_object("json", true) ("code", "eosio.msig") ("scope", proposer) ("table", "proposal") ("table_key", "") ("lower_bound", eosio::chain::string_to_name(proposal_name.c_str())) ("upper_bound", "") ("limit", 1) ); //std::cout << fc::json::to_pretty_string(result) << std::endl; fc::variants rows = result.get_object()["rows"].get_array(); if (rows.empty()) { std::cerr << "Proposal not found" << std::endl; return; } fc::mutable_variant_object obj = rows[0].get_object(); if (obj["proposal_name"] != proposal_name) { std::cerr << "Proposal not found" << std::endl; return; } auto trx_hex = obj["packed_transaction"].as_string(); vector trx_blob(trx_hex.size()/2); fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); transaction trx = fc::raw::unpack(trx_blob); fc::variant trx_var; abi_serializer abi; abi.to_variant(trx, trx_var, resolver); obj["transaction"] = trx_var; std::cout << fc::json::to_pretty_string(obj) << std::endl; }); string perm; auto approve_or_unapprove = [&](const string& action) { fc::variant perm_var; try { perm_var = json_from_file_or_string(perm); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse permissions JSON '${data}'", ("data",perm)) auto arg = fc::mutable_variant_object() ("code", "eosio.msig") ("action", action) ("args", fc::mutable_variant_object() ("proposer", proposer) ("proposal_name", proposal_name) ("level", perm_var) ); auto result = call(json_to_bin_func, arg); auto accountPermissions = tx_permission.empty() ? vector{{sender,config::active_name}} : get_account_permissions(tx_permission); send_actions({chain::action{accountPermissions, "eosio.msig", action, result.get_object()["binargs"].as()}}); }; // multisig approve auto approve = msig->add_subcommand("approve", localized("Approve proposed transaction")); add_standard_transaction_options(approve); approve->add_option("proposer", proposer, localized("proposer name (string)"))->required(); approve->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); approve->add_option("permissions", perm, localized("The JSON string of filename defining approving permissions"))->required(); approve->set_callback([&] { approve_or_unapprove("approve"); }); // multisig unapprove auto unapprove = msig->add_subcommand("unapprove", localized("Unapprove proposed transaction")); add_standard_transaction_options(unapprove); unapprove->add_option("proposer", proposer, localized("proposer name (string)"))->required(); unapprove->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); unapprove->add_option("permissions", perm, localized("The JSON string of filename defining approving permissions"))->required(); unapprove->set_callback([&] { approve_or_unapprove("unapprove"); }); // multisig cancel string canceler; auto cancel = msig->add_subcommand("cancel", localized("Cancel proposed transaction")); add_standard_transaction_options(cancel); cancel->add_option("proposer", proposer, localized("proposer name (string)"))->required(); cancel->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); cancel->add_option("canceler", canceler, localized("canceler name (string)")); cancel->set_callback([&]() { auto accountPermissions = get_account_permissions(tx_permission); if (accountPermissions.empty()) { if (!canceler.empty()) { accountPermissions = vector{{canceler, config::active_name}}; } else { EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (canceler.empty()) { canceler = name(accountPermissions.at(0).actor).to_string(); } auto arg = fc::mutable_variant_object() ("code", "eosio.msig") ("action", "cancel") ("args", fc::mutable_variant_object() ("proposer", proposer) ("proposal_name", proposal_name) ("canceler", canceler) ); auto result = call(json_to_bin_func, arg); send_actions({chain::action{accountPermissions, "eosio.msig", "cancel", result.get_object()["binargs"].as()}}); } ); // multisig exec string executer; auto exec = msig->add_subcommand("exec", localized("Execute proposed transaction")); add_standard_transaction_options(exec); exec->add_option("proposer", proposer, localized("proposer name (string)"))->required(); exec->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); exec->add_option("executer", executer, localized("account paying for execution (string)")); exec->set_callback([&] { auto accountPermissions = get_account_permissions(tx_permission); if (accountPermissions.empty()) { if (!executer.empty()) { accountPermissions = vector{{executer, config::active_name}}; } else { EOS_THROW(missing_auth_exception, "Authority is not provided (either by multisig parameter or -p)"); } } if (executer.empty()) { executer = name(accountPermissions.at(0).actor).to_string(); } auto arg = fc::mutable_variant_object() ("code", "eosio.msig") ("action", "exec") ("args", fc::mutable_variant_object() ("proposer", proposer ) ("proposal_name", proposal_name) ("executer", executer) ); auto result = call(json_to_bin_func, arg); //std::cout << "Result: " << result << std::endl; send_actions({chain::action{accountPermissions, "eosio.msig", "exec", result.get_object()["binargs"].as()}}); } ); // system subcommand auto system = app.add_subcommand("system", localized("Send eosio.system contract action to the blockchain."), false); system->require_subcommand(); auto createAccountSystem = create_account_subcommand( system, false /*simple*/ ); auto registerProducer = register_producer_subcommand(system); auto unregisterProducer = unregister_producer_subcommand(system); auto voteProducer = system->add_subcommand("voteproducer", localized("Vote for a producer")); voteProducer->require_subcommand(); auto voteProxy = vote_producer_proxy_subcommand(voteProducer); auto voteProducers = vote_producers_subcommand(voteProducer); auto approveProducer = approve_producer_subcommand(voteProducer); auto unapproveProducer = unapprove_producer_subcommand(voteProducer); auto listProducers = list_producers_subcommand(system); auto delegateBandWidth = delegate_bandwidth_subcommand(system); auto undelegateBandWidth = undelegate_bandwidth_subcommand(system); auto listBandWidth = list_bw_subcommand(system); auto biyram = buyram_subcommand(system); auto sellram = sellram_subcommand(system); auto claimRewards = claimrewards_subcommand(system); auto regProxy = regproxy_subcommand(system); auto unregProxy = unregproxy_subcommand(system); auto cancelDelay = canceldelay_subcommand(system); try { app.parse(argc, argv); } catch (const CLI::ParseError &e) { return app.exit(e); } catch (const explained_exception& e) { return 1; } catch (connection_exception& e) { if (verbose_errors) { elog("connect error: ${e}", ("e", e.to_detail_string())); } } catch (const fc::exception& e) { // attempt to extract the error code if one is present if (!print_recognized_errors(e, verbose_errors)) { // Error is not recognized if (!print_help_text(e) || verbose_errors) { elog("Failed with error: ${e}", ("e", verbose_errors ? e.to_detail_string() : e.to_string())); } } return 1; } return 0; }