main.cpp 52.4 KB
Newer Older
1 2
/**
 *  @file
3
 *  @copyright defined in eos/LICENSE.txt
4 5
 *  @defgroup eosclienttool EOSIO Command Line Client Reference
 *  @brief Tool for sending transactions and querying state from @ref nodeos
6 7 8 9 10 11
 *  @ingroup eosclienttool
 */

/**
  @defgroup eosclienttool

12
  @section intro Introduction to cleos
13

14 15
  `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'.
16

17
   cleos contains documentation for all of its commands. For a list of all commands known to cleos, simply run it with no arguments:
18
```
19 20 21
$ ./cleos
Command Line Interface to EOSIO Client
Usage: programs/cleos/cleos [OPTIONS] SUBCOMMAND
22 23

Options:
24 25 26
  -h,--help                   Print this help message and exit
  -H,--host TEXT=localhost    the host where nodeos is running
  -p,--port UINT=8888         the port where nodeos is running
27
  --wallet-host TEXT=localhost
28 29
                              the host where keosd is running
  --wallet-port UINT=8888     the port where keosd is running
30
  -v,--verbose                output verbose actions on error
31 32

Subcommands:
33
  version                     Retrieve version information
34 35 36 37
  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
38
  net                         Interact with local p2p network connections
39
  wallet                      Interact with local wallet
40
  sign                        Sign a transaction
41 42 43 44 45
  push                        Push arbitrary transactions to the blockchain

```
To get help with any particular subcommand, run it with no arguments as well:
```
46
$ ./cleos create
47
Create various items, on and off the blockchain
48
Usage: ./cleos create SUBCOMMAND
49 50 51 52 53

Subcommands:
  key                         Create a new keypair and print the public and private keys
  account                     Create a new account on the blockchain

54
$ ./cleos create account
55
Create a new account on the blockchain
56
Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey
57 58 59 60

Positionals:
  creator TEXT                The name of the account creating the new account
  name TEXT                   The name of the new account
61 62
  OwnerKey TEXT               The owner public key for the new account
  ActiveKey TEXT              The active public key for the new account
63 64

Options:
65 66 67 68 69
  -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')
70 71
```
*/
72 73
#include <string>
#include <vector>
74
#include <regex>
75
#include <boost/asio.hpp>
P
Pravin 已提交
76
#include <boost/format.hpp>
77 78 79
#include <iostream>
#include <fc/variant.hpp>
#include <fc/io/json.hpp>
D
Daniel Larimer 已提交
80
#include <fc/io/console.hpp>
81
#include <fc/exception/exception.hpp>
82
#include <eosio/utilities/key_conversion.hpp>
83

84
#include <eosio/chain/config.hpp>
85
#include <eosio/chain/wast_to_wasm.hpp>
D
Daniel Larimer 已提交
86
#include <eosio/chain/transaction_trace.hpp>
D
Daniel Larimer 已提交
87
#include <eosio/chain_plugin/chain_plugin.hpp>
D
Daniel Larimer 已提交
88

89
#include <boost/range/algorithm/find_if.hpp>
90
#include <boost/range/algorithm/sort.hpp>
91
#include <boost/range/adaptor/transformed.hpp>
92
#include <boost/algorithm/string/predicate.hpp>
93
#include <boost/algorithm/string/split.hpp>
94
#include <boost/range/algorithm/copy.hpp>
D
Daniel Larimer 已提交
95
#include <boost/algorithm/string/classification.hpp>
96

97 98 99 100 101 102 103 104 105
#include <Inline/BasicTypes.h>
#include <IR/Module.h>
#include <IR/Validate.h>
#include <WAST/WAST.h>
#include <WASM/WASM.h>
#include <Runtime/Runtime.h>

#include <fc/io/fstream.hpp>

106
#include "CLI11.hpp"
107
#include "help_text.hpp"
108
#include "localize.hpp"
109
#include "config.hpp"
110
#include "httpc.hpp"
111

112
using namespace std;
P
Pravin 已提交
113 114 115 116
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::utilities;
using namespace eosio::client::help;
117
using namespace eosio::client::http;
P
Pravin 已提交
118 119
using namespace eosio::client::localize;
using namespace eosio::client::config;
120 121 122 123 124 125 126 127 128 129 130 131 132 133
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 \
  )
134

135 136 137 138
string program = "eosc";
string host = "localhost";
uint32_t port = 8888;

K
Kevin Heifner 已提交
139
// restricting use of wallet to localhost
D
Daniel Larimer 已提交
140 141
string wallet_host = "localhost";
uint32_t wallet_port = 8888;
K
Kevin Heifner 已提交
142

143 144 145 146
auto   tx_expiration = fc::seconds(30);
bool   tx_force_unique = false;
bool   tx_dont_broadcast = false;
bool   tx_skip_sign = false;
D
Daniel Larimer 已提交
147
bool   tx_print_json = false;
148

149 150
uint32_t tx_max_cpu_usage = 0;
uint32_t tx_max_net_usage = 0;
151

152
vector<string> tx_permission;
K
Kevin Heifner 已提交
153

M
Matias Romeo 已提交
154
void add_standard_transaction_options(CLI::App* cmd, string default_permission = "") {
155
   CLI::callback_t parse_exipration = [](CLI::results_t res) -> bool {
K
Kevin Heifner 已提交
156 157
      double value_s;
      if (res.size() == 0 || !CLI::detail::lexical_cast(res[0], value_s)) {
158 159
         return false;
      }
160

K
Kevin Heifner 已提交
161
      tx_expiration = fc::seconds(static_cast<uint64_t>(value_s));
162 163 164
      return true;
   };

K
Kevin Heifner 已提交
165
   cmd->add_option("-x,--expiration", parse_exipration, localized("set the time in seconds before a transaction expires, defaults to 30s"));
166
   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"));
167
   cmd->add_flag("-s,--skip-sign", tx_skip_sign, localized("Specify if unlocked wallet keys should be used to sign transaction"));
D
Daniel Larimer 已提交
168
   cmd->add_flag("-j,--json", tx_print_json, localized("print result as json"));
169 170
   cmd->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout)"));

M
Matias Romeo 已提交
171 172 173 174
   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()));
175

176 177
   cmd->add_option("--max-cpu-usage", tx_max_cpu_usage, localized("set an upper limit on the cpu usage budget, in instructions-retired, 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)"));
178 179
}

K
Kevin Heifner 已提交
180 181
string generate_nonce_value() {
   return fc::to_string(fc::time_point::now().time_since_epoch().count());
182 183
}

K
Kevin Heifner 已提交
184
chain::action generate_nonce() {
185
   auto v = generate_nonce_value();
K
Kevin Heifner 已提交
186 187 188
   variant nonce = fc::mutable_variant_object()
         ("value", v);
   return chain::action( {}, config::system_account_name, "nonce", fc::raw::pack(nonce));
189
}
190

D
Daniel Larimer 已提交
191
vector<chain::permission_level> get_account_permissions(const vector<string>& permissions) {
192 193 194
   auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) {
      vector<string> pieces;
      split(pieces, p, boost::algorithm::is_any_of("@"));
195
      if( pieces.size() == 1 ) pieces.push_back( "active" );
D
Daniel Larimer 已提交
196
      return chain::permission_level{ .actor = pieces[0], .permission = pieces[1] };
197
   });
D
Daniel Larimer 已提交
198
   vector<chain::permission_level> accountPermissions;
199 200 201
   boost::range::copy(fixedPermissions, back_inserter(accountPermissions));
   return accountPermissions;
}
202

203
template<typename T>
204
fc::variant call( const std::string& server, uint16_t port,
205
                  const std::string& path,
206
                  const T& v ) { return eosio::client::http::call( server, port, path, fc::variant(v) ); }
207

208 209
template<typename T>
fc::variant call( const std::string& path,
210
                  const T& v ) { return eosio::client::http::call( host, port, path, fc::variant(v) ); }
211

P
Pravin 已提交
212 213
eosio::chain_apis::read_only::get_info_results get_info() {
  return call(host, port, get_info_func ).as<eosio::chain_apis::read_only::get_info_results>();
214 215
}

216
fc::variant determine_required_keys(const signed_transaction& trx) {
K
Kevin Heifner 已提交
217 218 219
   // TODO better error checking
   const auto& public_keys = call(wallet_host, wallet_port, wallet_public_keys);
   auto get_arg = fc::mutable_variant_object
220 221
           ("transaction", (transaction)trx)
           ("available_keys", public_keys);
K
Kevin Heifner 已提交
222
   const auto& required_keys = call(host, port, get_required_keys, get_arg);
223 224 225 226
   return required_keys["required_keys"];
}

void sign_transaction(signed_transaction& trx, fc::variant& required_keys) {
K
Kevin Heifner 已提交
227
   // TODO determine chain id
228
   fc::variants sign_args = {fc::variant(trx), required_keys, fc::variant(chain_id_type{})};
K
Kevin Heifner 已提交
229
   const auto& signed_trx = call(wallet_host, wallet_port, wallet_sign_trx, sign_args);
230
   trx = signed_trx.as<signed_transaction>();
K
Kevin Heifner 已提交
231 232
}

233
fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
M
Matias Romeo 已提交
234 235 236
   auto info = get_info();
   trx.expiration = info.head_block_time + tx_expiration;
   trx.set_reference_block(info.head_block_id);
237

M
Matias Romeo 已提交
238
   if (tx_force_unique) {
K
Kevin Heifner 已提交
239
      trx.context_free_actions.emplace_back( generate_nonce() );
M
Matias Romeo 已提交
240
   }
K
Kevin Heifner 已提交
241

242 243 244
   auto required_keys = determine_required_keys(trx);
   size_t num_keys = required_keys.is_array() ? required_keys.get_array().size() : 1;

245 246
   trx.max_kcpu_usage = (tx_max_cpu_usage + 1023)/1024;
   trx.max_net_usage_words = (tx_max_net_usage + 7)/8;
247

M
Matias Romeo 已提交
248
   if (!tx_skip_sign) {
249
      sign_transaction(trx, required_keys);
M
Matias Romeo 已提交
250 251 252 253 254 255 256
   }

   if (!tx_dont_broadcast) {
      return call(push_txn_func, packed_transaction(trx, compression));
   } else {
      return fc::variant(trx);
   }
257 258
}

259
fc::variant push_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
260 261 262
   signed_transaction trx;
   trx.actions = std::forward<decltype(actions)>(actions);

263
   return push_transaction(trx, extra_kcpu, compression);
264 265
}

D
Daniel Larimer 已提交
266 267 268 269
void print_result( const fc::variant& result ) {
      const auto& processed = result["processed"];
      const auto& transaction_id = processed["id"].as_string();
      const auto& status = processed["status"].as_string() ;
270 271
      auto net = processed["net_usage"].as_int64();
      auto cpu = processed["cpu_usage"].as_int64();
D
Daniel Larimer 已提交
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302

      cout << status << " transaction: " << transaction_id << "  " << net << " bytes  " << cpu << " cycles\n";

      const auto& actions = processed["action_traces"].get_array();
      for( const auto& at : actions ) {
         auto receiver = at["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";
         }
      }
}

using std::cout;
303
void send_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
D
Daniel Larimer 已提交
304 305 306 307 308 309 310
   auto result = push_actions( move(actions), extra_kcpu, compression);

   if( tx_print_json ) {
      cout << fc::json::to_pretty_string( result );
   } else {
      print_result( result );
   }
311 312
}

313
void send_transaction( signed_transaction& trx, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none  ) {
D
Daniel Larimer 已提交
314
   auto result = push_transaction(trx, extra_kcpu, compression);
315

D
Daniel Larimer 已提交
316 317 318 319 320 321
   if( tx_print_json ) {
      cout << fc::json::to_pretty_string( result );
   } else {
      auto trace = result["processed"].as<eosio::chain::transaction_trace>();
      print_result( result );
   }
322
}
323

M
Matias Romeo 已提交
324 325 326 327
chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) {
   return action {
      tx_permission.empty() ? vector<chain::permission_level>{{creator,config::active_name}} : get_account_permissions(tx_permission),
      contracts::newaccount{
328 329 330
         .creator      = creator,
         .name         = newaccount,
         .owner        = eosio::chain::authority{1, {{owner, 1}}, {}},
M
Matias Romeo 已提交
331 332 333 334
         .active       = eosio::chain::authority{1, {{active, 1}}, {}},
         .recovery     = eosio::chain::authority{1, {}, {{{creator, config::active_name}, 1}}}
      }
   };
335
}
336

M
Matias Romeo 已提交
337 338 339 340 341 342 343
chain::action create_transfer(const name& sender, const name& recipient, uint64_t amount, const string& memo ) {

   auto transfer = fc::mutable_variant_object
      ("from", sender)
      ("to", recipient)
      ("quantity", asset(amount))
      ("memo", memo);
344

M
Matias Romeo 已提交
345 346 347 348
   auto args = fc::mutable_variant_object
      ("code", name(config::system_account_name))
      ("action", "transfer")
      ("args", transfer);
349

M
Matias Romeo 已提交
350
   auto result = call(json_to_bin_func, args);
351

M
Matias Romeo 已提交
352 353 354 355
   return action {
      tx_permission.empty() ? vector<chain::permission_level>{{sender,config::active_name}} : get_account_permissions(tx_permission),
      config::system_account_name, "transfer", result.get_object()["binargs"].as<bytes>()
   };
356
}
D
Daniel Larimer 已提交
357

M
Matias Romeo 已提交
358
chain::action create_setabi(const name& account, const contracts::abi_def& abi) {
359
   return action {
M
Matias Romeo 已提交
360 361 362 363
      tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
      contracts::setabi{
         .account   = account,
         .abi       = abi
364 365
      }
   };
366 367
}

M
Matias Romeo 已提交
368
chain::action create_setcode(const name& account, const bytes& code) {
369
   return action {
M
Matias Romeo 已提交
370 371 372 373 374
      tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
      contracts::setcode{
         .account   = account,
         .vmtype    = 0,
         .vmversion = 0,
375
         .code      = code
376 377 378 379 380 381
      }
   };
}

chain::action create_updateauth(const name& account, const name& permission, const name& parent, const authority& auth) {
   return action { tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
D
Daniel Larimer 已提交
382
                   contracts::updateauth{account, permission, parent, auth}};
383 384
}

385 386
chain::action create_deleteauth(const name& account, const name& permission) {
   return action { tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
D
Daniel Larimer 已提交
387
                   contracts::deleteauth{account, permission}};
388 389
}

D
Daniel Larimer 已提交
390
chain::action create_linkauth(const name& account, const name& code, const name& type, const name& requirement) {
391
   return action { tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
D
Daniel Larimer 已提交
392
                   contracts::linkauth{account, code, type, requirement}};
393 394
}

D
Daniel Larimer 已提交
395
chain::action create_unlinkauth(const name& account, const name& code, const name& type) {
396
   return action { tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
D
Daniel Larimer 已提交
397
                   contracts::unlinkauth{account, code, type}};
398 399
}

400 401
fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser)
{
402 403
   regex r("^[ \t]*[\{\[]");
   if ( !regex_search(file_or_str, r) && is_regular_file(file_or_str) ) {
404 405 406 407 408 409
      return fc::json::from_file(file_or_str, ptype);
   } else {
      return fc::json::from_string(file_or_str, ptype);
   }
}

410
struct set_account_permission_subcommand {
411 412 413 414 415
   string accountStr;
   string permissionStr;
   string authorityJsonOrFile;
   string parentStr;

416
   set_account_permission_subcommand(CLI::App* accountCmd) {
417 418 419 420 421
      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] 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\")"));
422

M
Matias Romeo 已提交
423
      add_standard_transaction_options(permissions, "account@active");
424 425

      permissions->set_callback([this] {
426 427
         name account = name(accountStr);
         name permission = name(permissionStr);
428
         bool is_delete = boost::iequals(authorityJsonOrFile, "null");
429

430
         if (is_delete) {
M
Matias Romeo 已提交
431
            send_actions({create_deleteauth(account, permission)});
432
         } else {
D
Daniel Larimer 已提交
433
            authority auth;
434
            if (boost::istarts_with(authorityJsonOrFile, "EOS")) {
435 436
               try {
                  auth = authority(public_key_type(authorityJsonOrFile));
437
               } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", authorityJsonOrFile))
438
            } else {
439
               try {
440 441
                  auth = json_from_file_or_string(authorityJsonOrFile).as<authority>();
               } EOS_RETHROW_EXCEPTIONS(authority_type_exception, "Fail to parse Authority JSON '${data}'", ("data",authorityJsonOrFile))
442

443 444
            }

445
            name parent;
446
            if (parentStr.size() == 0 && permissionStr != "owner") {
447
               // see if we can auto-determine the proper parent
K
Kevin Heifner 已提交
448
               const auto account_result = call(get_account_func, fc::mutable_variant_object("account_name", accountStr));
449
               const auto& existing_permissions = account_result.get_object()["permissions"].get_array();
450 451
               auto permissionPredicate = [this](const auto& perm) {
                  return perm.is_object() &&
452
                        perm.get_object().contains("permission") &&
453
                        boost::equals(perm.get_object()["permission"].get_string(), permissionStr);
454 455 456 457
               };

               auto itr = boost::find_if(existing_permissions, permissionPredicate);
               if (itr != existing_permissions.end()) {
458
                  parent = name((*itr).get_object()["parent"].get_string());
459 460
               } else {
                  // if this is a new permission and there is no parent we default to "active"
461
                  parent = name(config::active_name);
462

463 464
               }
            } else {
465
               parent = name(parentStr);
466
            }
467

M
Matias Romeo 已提交
468
            send_actions({create_updateauth(account, permission, parent, auth)});
469
         }
470 471
      });
   }
472

473
};
474

475
struct set_action_permission_subcommand {
476 477 478 479 480
   string accountStr;
   string codeStr;
   string typeStr;
   string requirementStr;

481
   set_action_permission_subcommand(CLI::App* actionRoot) {
482 483 484 485 486
      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();
487

M
Matias Romeo 已提交
488
      add_standard_transaction_options(permissions, "account@active");
489 490

      permissions->set_callback([this] {
491 492 493
         name account = name(accountStr);
         name code = name(codeStr);
         name type = name(typeStr);
494
         bool is_delete = boost::iequals(requirementStr, "null");
495

496
         if (is_delete) {
M
Matias Romeo 已提交
497
            send_actions({create_unlinkauth(account, code, type)});
498
         } else {
499
            name requirement = name(requirementStr);
M
Matias Romeo 已提交
500
            send_actions({create_linkauth(account, code, type, requirement)});
501
         }
502 503 504
      });
   }
};
505

506
int main( int argc, char** argv ) {
D
Daniel Larimer 已提交
507
   fc::path binPath = argv[0];
508
   if (binPath.is_relative()) {
509
      binPath = relative(binPath, current_path());
510 511 512 513 514 515
   }

   setlocale(LC_ALL, "");
   bindtextdomain(locale_domain, locale_path);
   textdomain(locale_domain);

516
   CLI::App app{"Command Line Interface to EOSIO Client"};
517
   app.require_subcommand();
518 519
   app.add_option( "-H,--host", host, localized("the host where nodeos is running"), true );
   app.add_option( "-p,--port", port, localized("the port where nodeos is running"), true );
520 521
   app.add_option( "--wallet-host", wallet_host, localized("the host where keosd is running"), true );
   app.add_option( "--wallet-port", wallet_port, localized("the port where keosd is running"), true );
522

523
   bool verbose_errors = false;
D
Daniel Larimer 已提交
524
   app.add_flag( "-v,--verbose", verbose_errors, localized("output verbose actions on error"));
525

526 527 528 529 530 531 532
   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;
   });

533
   // Create subcommand
534
   auto create = app.add_subcommand("create", localized("Create various items, on and off the blockchain"), false);
N
Nathan Hourt 已提交
535 536 537
   create->require_subcommand();

   // create key
D
Daniel Larimer 已提交
538 539 540 541 542 543
   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;
N
Nathan Hourt 已提交
544 545 546 547
   });

   // create account
   string creator;
548
   string account_name;
549 550
   string owner_key_str;
   string active_key_str;
M
Matias Romeo 已提交
551

552 553
   auto createAccount = create->add_subcommand("account", localized("Create a new account on the blockchain"), false);
   createAccount->add_option("creator", creator, localized("The name of the account creating the new account"))->required();
554
   createAccount->add_option("name", account_name, localized("The name of the new account"))->required();
555 556
   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"))->required();
M
Matias Romeo 已提交
557
   add_standard_transaction_options(createAccount, "creator@active");
N
Nathan Hourt 已提交
558
   createAccount->set_callback([&] {
559 560 561
      public_key_type owner_key, active_key;
      try {
         owner_key = public_key_type(owner_key_str);
562
      } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid owner public key: ${public_key}", ("public_key", owner_key_str))
563
      try {
564
         active_key = public_key_type(active_key_str);
565
      } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid active public key: ${public_key}", ("public_key", active_key_str))
M
Matias Romeo 已提交
566
      send_actions({create_newaccount(creator, account_name, owner_key, active_key)});
567 568
   });

569
   // Get subcommand
570
   auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false);
N
Nathan Hourt 已提交
571 572 573
   get->require_subcommand();

   // get info
574
   get->add_subcommand("info", localized("Get current blockchain information"))->set_callback([] {
N
Nathan Hourt 已提交
575 576 577 578 579
      std::cout << fc::json::to_pretty_string(get_info()) << std::endl;
   });

   // get block
   string blockArg;
580 581
   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();
N
Nathan Hourt 已提交
582 583 584 585 586 587 588
   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;
589 590
   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();
N
Nathan Hourt 已提交
591 592
   getAccount->set_callback([&] {
      std::cout << fc::json::to_pretty_string(call(get_account_func,
K
Kevin Heifner 已提交
593
                                                   fc::mutable_variant_object("account_name", accountName)))
N
Nathan Hourt 已提交
594 595 596
                << std::endl;
   });

D
Daniel Larimer 已提交
597 598 599
   // get code
   string codeFilename;
   string abiFilename;
600 601 602 603
   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 to") );
   getCode->add_option("-a,--abi",abiFilename, localized("The name of the file to save the contract .abi to") );
D
Daniel Larimer 已提交
604
   getCode->set_callback([&] {
K
Kevin Heifner 已提交
605
      auto result = call(get_code_func, fc::mutable_variant_object("account_name", accountName));
D
Daniel Larimer 已提交
606

607
      std::cout << localized("code hash: ${code_hash}", ("code_hash", result["code_hash"].as_string())) << std::endl;
D
Daniel Larimer 已提交
608 609

      if( codeFilename.size() ){
610
         std::cout << localized("saving wast to ${codeFilename}", ("codeFilename", codeFilename)) << std::endl;
D
Daniel Larimer 已提交
611 612 613 614 615
         auto code = result["wast"].as_string();
         std::ofstream out( codeFilename.c_str() );
         out << code;
      }
      if( abiFilename.size() ) {
616
         std::cout << localized("saving abi to ${abiFilename}", ("abiFilename", abiFilename)) << std::endl;
D
Daniel Larimer 已提交
617 618 619 620 621 622
         auto abi  = fc::json::to_pretty_string( result["abi"] );
         std::ofstream abiout( abiFilename.c_str() );
         abiout << abi;
      }
   });

623
   // get table
D
Daniel Larimer 已提交
624 625 626 627 628
   string scope;
   string code;
   string table;
   string lower;
   string upper;
629
   string table_key;
D
Daniel Larimer 已提交
630 631
   bool binary = false;
   uint32_t limit = 10;
632
   auto getTable = get->add_subcommand( "table", localized("Retrieve the contents of a database table"), false);
633 634
   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();
635 636 637
   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") );
638
   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") );
639 640
   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") );
D
Daniel Larimer 已提交
641 642 643 644

   getTable->set_callback([&] {
      auto result = call(get_table_func, fc::mutable_variant_object("json", !binary)
                         ("code",code)
645
                         ("scope",scope)
D
Daniel Larimer 已提交
646
                         ("table",table)
647 648 649 650
                         ("table_key",table_key)
                         ("lower_bound",lower)
                         ("upper_bound",upper)
                         ("limit",limit)
D
Daniel Larimer 已提交
651 652 653 654 655 656
                         );

      std::cout << fc::json::to_pretty_string(result)
                << std::endl;
   });

657 658 659 660
   // currency accessors
   // get currency balance
   string symbol;
   auto get_currency = get->add_subcommand( "currency", localized("Retrieve information related to standard currencies"), true);
661
   get_currency->require_subcommand();
662 663 664 665 666
   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([&] {
667 668
      auto result = call(get_currency_balance_func, fc::mutable_variant_object("json", false)
         ("account", accountName)
669
         ("code", code)
D
Daniel Larimer 已提交
670
         ("symbol", symbol=="*" ? fc::variant(symbol) : fc::variant() )
671 672
      );

673
      const auto& rows = result.get_array();
674
      if (symbol.empty()) {
D
Daniel Larimer 已提交
675 676 677 678 679
         for( const auto& r : rows ) {
            std::cout << r.as_string()
                      << std::endl;
         }
         /*
680 681
         std::cout << fc::json::to_pretty_string(rows)
                   << std::endl;
D
Daniel Larimer 已提交
682
                   */
683
      } else if ( rows.size() > 0 ){
D
Daniel Larimer 已提交
684
         std::cout << rows[0].as_string()
685 686 687
                   << std::endl;
      }
   });
D
Daniel Larimer 已提交
688

689 690 691 692
   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") );
   get_currency_stats->set_callback([&] {
693
      auto result = call(get_currency_stats_func, fc::mutable_variant_object("json", false)
694
         ("code", code)
695
         ("symbol", symbol)
696 697 698
      );

      if (symbol.empty()) {
699
         std::cout << fc::json::to_pretty_string(result)
700 701
                   << std::endl;
      } else {
702 703 704
         const auto& mapping = result.get_object();
         std::cout << fc::json::to_pretty_string(mapping[symbol])
                   << std::endl;
705 706
      }
   });
D
Daniel Larimer 已提交
707

708
   // get accounts
709
   string public_key_str;
710
   auto getAccounts = get->add_subcommand("accounts", localized("Retrieve accounts associated with a public key"), false);
711
   getAccounts->add_option("public_key", public_key_str, localized("The public key to retrieve accounts for"))->required();
712
   getAccounts->set_callback([&] {
713 714 715
      public_key_type public_key;
      try {
         public_key = public_key_type(public_key_str);
716
      } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", public_key_str))
717
      auto arg = fc::mutable_variant_object( "public_key", public_key);
718 719 720
      std::cout << fc::json::to_pretty_string(call(get_key_accounts_func, arg)) << std::endl;
   });

721

722 723
   // get servants
   string controllingAccount;
724 725
   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();
726
   getServants->set_callback([&] {
727
      auto arg = fc::mutable_variant_object( "controlling_account", controllingAccount);
728 729 730
      std::cout << fc::json::to_pretty_string(call(get_controlled_accounts_func, arg)) << std::endl;
   });

N
Nathan Hourt 已提交
731
   // get transaction
732
   string transaction_id_str;
733
   auto getTransaction = get->add_subcommand("transaction", localized("Retrieve a transaction from the blockchain"), false);
734
   getTransaction->add_option("id", transaction_id_str, localized("ID of the transaction to retrieve"))->required();
N
Nathan Hourt 已提交
735
   getTransaction->set_callback([&] {
736 737 738
      transaction_id_type transaction_id;
      try {
         transaction_id = transaction_id_type(transaction_id_str);
739
      } EOS_RETHROW_EXCEPTIONS(transaction_id_type_exception, "Invalid transaction ID: ${transaction_id}", ("transaction_id", transaction_id_str))
740
      auto arg= fc::mutable_variant_object( "transaction_id", transaction_id);
N
Nathan Hourt 已提交
741 742
      std::cout << fc::json::to_pretty_string(call(get_transaction_func, arg)) << std::endl;
   });
743

744
   // get transactions
745 746
   string skip_seq_str;
   string num_seq_str;
D
Daniel Larimer 已提交
747
   bool printjson = false;
748
   auto getTransactions = get->add_subcommand("transactions", localized("Retrieve all transactions with specific account name referenced in their scope"), false);
749
   getTransactions->add_option("account_name", account_name, localized("name of account to query on"))->required();
750 751
   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"));
D
Daniel Larimer 已提交
752
   getTransactions->add_flag("--json,-j", printjson, localized("print full json"));
753
   getTransactions->set_callback([&] {
754 755 756 757 758 759 760
      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<uint64_t>(skip_seq_str);
761
         } EOS_RETHROW_EXCEPTIONS(chain_type_exception, "Invalid Skip Seq: ${skip_seq}", ("skip_seq", skip_seq_str))
762 763 764 765 766 767
         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<uint64_t>(num_seq_str);
768
            } EOS_RETHROW_EXCEPTIONS(chain_type_exception, "Invalid Num Seq: ${num_seq}", ("num_seq", num_seq_str))
769 770 771
            arg = fc::mutable_variant_object( "account_name", account_name)("skip_seq", skip_seq_str)("num_seq", num_seq);
         }
      }
D
Daniel Larimer 已提交
772
      auto result = call(get_transactions_func, arg);
D
Daniel Larimer 已提交
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
      if( printjson ) {
         std::cout << fc::json::to_pretty_string(call(get_transactions_func, arg)) << 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["data"].get_object();
            const auto& msgs = data["actions"].get_array();

            for( const auto& msg : msgs ) {
               int64_t seq_num  = tobj["seq_num"].as<int64_t>();
               string  id       = tobj["transaction_id"].as_string();
               const auto& exp  = data["expiration"].as<fc::time_point_sec>();
               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;
            }
         }
D
Daniel Larimer 已提交
797 798
      }

799 800
   });

801
   // set subcommand
802
   auto setSubcommand = app.add_subcommand("set", localized("Set or update blockchain state"));
803 804 805
   setSubcommand->require_subcommand();

   // set contract subcommand
N
Nathan Hourt 已提交
806
   string account;
807
   string contractPath;
N
Nathan Hourt 已提交
808 809
   string wastPath;
   string abiPath;
810
   auto contractSubcommand = setSubcommand->add_subcommand("contract", localized("Create or update the contract on an account"));
811 812 813 814 815 816 817 818 819
   contractSubcommand->add_option("account", account, localized("The account to publish a contract for"))
                     ->required();
   contractSubcommand->add_option("contract-dir", contractPath, localized("The 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);

820

M
Matias Romeo 已提交
821
   add_standard_transaction_options(contractSubcommand, "account@active");
N
Nathan Hourt 已提交
822 823
   contractSubcommand->set_callback([&] {
      std::string wast;
824
      std::cout << localized("Reading WAST...") << std::endl;
825
      fc::path cpath(contractPath);
826

827 828
      if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path();

B
Bucky Kittinger 已提交
829
      if( wastPath.empty() )
830 831 832 833
      {
         wastPath = (cpath / (cpath.filename().generic_string()+".wast")).generic_string();
      }

B
Bucky Kittinger 已提交
834
      if( abiPath.empty() )
835 836 837
      {
         abiPath = (cpath / (cpath.filename().generic_string()+".abi")).generic_string();
      }
838 839
      
      
N
Nathan Hourt 已提交
840
      fc::read_file_contents(wastPath, wast);
B
Bucky Kittinger 已提交
841
      FC_ASSERT( !wast.empty(), "no wast file found ${f}", ("f", wastPath) );
842
      vector<uint8_t> wasm;
843
      const string binary_wasm_header("\x00\x61\x73\x6d", 4);
844
      if(wast.compare(0, 4, binary_wasm_header) == 0) {
845
         std::cout << localized("Using already assembled WASM...") << std::endl;
846
         wasm = vector<uint8_t>(wast.begin(), wast.end());
847
      }
848 849
      else {
         std::cout << localized("Assembling WASM...") << std::endl;
850
         wasm = wast_to_wasm(wast);
851
      }
N
Nathan Hourt 已提交
852

M
Matias Romeo 已提交
853 854
      std::vector<chain::action> actions;
      actions.emplace_back( create_setcode(account, bytes(wasm.begin(), wasm.end()) ) );
855

D
Daniel Larimer 已提交
856 857 858 859 860
      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<contracts::abi_def>()) );
      } EOS_RETHROW_EXCEPTIONS(abi_type_exception,  "Fail to parse ABI JSON")
K
Kevin Heifner 已提交
861

862
      std::cout << localized("Publishing contract...") << std::endl;
D
Daniel Larimer 已提交
863 864
      send_actions(std::move(actions), 10000, packed_transaction::zlib);
      /*
865 866 867 868 869
      auto result = push_actions(std::move(actions), 10000, packed_transaction::zlib);

      if( tx_dont_broadcast ) {
         std::cout << fc::json::to_pretty_string(result) << "\n";
      }
D
Daniel Larimer 已提交
870
      */
N
Nathan Hourt 已提交
871
   });
N
Nathan Hourt 已提交
872

873
   // set account
874
   auto setAccount = setSubcommand->add_subcommand("account", localized("set or update blockchain account state"))->require_subcommand();
875 876

   // set account permission
877
   auto setAccountPermission = set_account_permission_subcommand(setAccount);
878 879

   // set action
880
   auto setAction = setSubcommand->add_subcommand("action", localized("set or update blockchain action state"))->require_subcommand();
881

882
   // set action permission
883
   auto setActionPermission = set_action_permission_subcommand(setAction);
884

N
Nathan Hourt 已提交
885
   // Transfer subcommand
N
Nathan Hourt 已提交
886 887 888
   string sender;
   string recipient;
   uint64_t amount;
889
   string memo;
890 891 892 893 894
   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"));
K
Kevin Heifner 已提交
895

M
Matias Romeo 已提交
896
   add_standard_transaction_options(transfer, "sender@active");
N
Nathan Hourt 已提交
897
   transfer->set_callback([&] {
898 899 900 901 902
      signed_transaction trx;
      if (tx_force_unique && memo.size() == 0) {
         // use the memo to add a nonce
         memo = generate_nonce_value();
         tx_force_unique = false;
903
      }
904

M
Matias Romeo 已提交
905
      send_actions({create_transfer(sender, recipient, amount, memo)});
N
Nathan Hourt 已提交
906
   });
N
Nathan Hourt 已提交
907

908
   // Net subcommand
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
   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(host, port, 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(host, port, 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(host, port, 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(host, port, net_connections, new_host);
      std::cout << fc::json::to_pretty_string(v) << std::endl;
   });


K
Kevin Heifner 已提交
940 941

   // Wallet subcommand
942
   auto wallet = app.add_subcommand( "wallet", localized("Interact with local wallet"), false );
943
   wallet->require_subcommand();
K
Kevin Heifner 已提交
944
   // create wallet
D
Daniel Larimer 已提交
945
   string wallet_name = "default";
946 947
   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);
K
Kevin Heifner 已提交
948 949
   createWallet->set_callback([&wallet_name] {
      const auto& v = call(wallet_host, wallet_port, wallet_create, wallet_name);
950 951 952
      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;
K
Kevin Heifner 已提交
953 954 955 956
      std::cout << fc::json::to_pretty_string(v) << std::endl;
   });

   // open wallet
957 958
   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"));
K
Kevin Heifner 已提交
959
   openWallet->set_callback([&wallet_name] {
D
Daniel Larimer 已提交
960 961
      /*const auto& v = */call(wallet_host, wallet_port, wallet_open, wallet_name);
      //std::cout << fc::json::to_pretty_string(v) << std::endl;
962
      std::cout << localized("Opened: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl;
K
Kevin Heifner 已提交
963 964 965
   });

   // lock wallet
966 967
   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"));
K
Kevin Heifner 已提交
968
   lockWallet->set_callback([&wallet_name] {
D
Daniel Larimer 已提交
969
      /*const auto& v = */call(wallet_host, wallet_port, wallet_lock, wallet_name);
970
      std::cout << localized("Locked: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl;
D
Daniel Larimer 已提交
971 972
      //std::cout << fc::json::to_pretty_string(v) << std::endl;

K
Kevin Heifner 已提交
973 974 975
   });

   // lock all wallets
976
   auto locakAllWallets = wallet->add_subcommand("lock_all", localized("Lock all unlocked wallets"), false);
K
Kevin Heifner 已提交
977
   locakAllWallets->set_callback([] {
D
Daniel Larimer 已提交
978 979
      /*const auto& v = */call(wallet_host, wallet_port, wallet_lock_all);
      //std::cout << fc::json::to_pretty_string(v) << std::endl;
980
      std::cout << localized("Locked All Wallets") << std::endl;
K
Kevin Heifner 已提交
981 982 983 984
   });

   // unlock wallet
   string wallet_pw;
985 986 987
   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"));
K
Kevin Heifner 已提交
988
   unlockWallet->set_callback([&wallet_name, &wallet_pw] {
D
Daniel Larimer 已提交
989
      if( wallet_pw.size() == 0 ) {
990
         std::cout << localized("password: ");
D
Daniel Larimer 已提交
991 992 993 994 995 996
         fc::set_console_echo(false);
         std::getline( std::cin, wallet_pw, '\n' );
         fc::set_console_echo(true);
      }


K
Kevin Heifner 已提交
997
      fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)};
D
Daniel Larimer 已提交
998
      /*const auto& v = */call(wallet_host, wallet_port, wallet_unlock, vs);
999
      std::cout << localized("Unlocked: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl;
1000
      //std::cout << fc::json::to_pretty_string(v) << std::endl;
K
Kevin Heifner 已提交
1001 1002 1003
   });

   // import keys into wallet
1004
   string wallet_key_str;
1005 1006
   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"));
1007 1008 1009 1010 1011 1012 1013 1014 1015
   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();
D
Daniel Larimer 已提交
1016

K
Kevin Heifner 已提交
1017 1018
      fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_key)};
      const auto& v = call(wallet_host, wallet_port, wallet_import_key, vs);
1019
      std::cout << localized("imported private key for: ${pubkey}", ("pubkey", std::string(pubkey))) << std::endl;
D
Daniel Larimer 已提交
1020
      //std::cout << fc::json::to_pretty_string(v) << std::endl;
K
Kevin Heifner 已提交
1021 1022 1023
   });

   // list wallets
1024
   auto listWallet = wallet->add_subcommand("list", localized("List opened wallets, * = unlocked"), false);
K
Kevin Heifner 已提交
1025
   listWallet->set_callback([] {
1026
      std::cout << localized("Wallets:") << std::endl;
K
Kevin Heifner 已提交
1027 1028 1029 1030 1031
      const auto& v = call(wallet_host, wallet_port, wallet_list);
      std::cout << fc::json::to_pretty_string(v) << std::endl;
   });

   // list keys
1032
   auto listKeys = wallet->add_subcommand("keys", localized("List of private keys from all unlocked wallets in wif format."), false);
K
Kevin Heifner 已提交
1033 1034 1035 1036 1037
   listKeys->set_callback([] {
      const auto& v = call(wallet_host, wallet_port, wallet_list_keys);
      std::cout << fc::json::to_pretty_string(v) << std::endl;
   });

1038 1039 1040 1041 1042 1043 1044
   // sign subcommand
   string trx_json_to_sign;
   string str_private_key;
   bool push_trx = false;

   auto sign = app.add_subcommand("sign", localized("Sign a transaction"), false);
   sign->add_option("transaction", trx_json_to_sign,
1045
                                 localized("The JSON string or filename defining the transaction to sign"), true)->required();
1046 1047 1048 1049
   sign->add_option("-k,--private-key", str_private_key, localized("The private key that will be used to sign the transaction"));
   sign->add_flag( "-p,--push-transaction", push_trx, localized("Push transaction after signing"));

   sign->set_callback([&] {
1050
      signed_transaction trx = json_from_file_or_string(trx_json_to_sign).as<signed_transaction>();
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069

      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_type{});

      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;
      }
   });

N
Nathan Hourt 已提交
1070
   // Push subcommand
1071
   auto push = app.add_subcommand("push", localized("Push arbitrary transactions to the blockchain"), false);
N
Nathan Hourt 已提交
1072 1073
   push->require_subcommand();

1074
   // push action
N
Nathan Hourt 已提交
1075 1076 1077
   string contract;
   string action;
   string data;
1078
   vector<string> permissions;
1079
   auto actionsSubcommand = push->add_subcommand("action", localized("Push a transaction with a single action"));
D
Daniel Larimer 已提交
1080 1081
   actionsSubcommand->fallthrough(false);
   actionsSubcommand->add_option("contract", contract,
1082
                                 localized("The account providing the contract to execute"), true)->required();
1083 1084
   actionsSubcommand->add_option("action", action,
                                 localized("A JSON string or filename defining the action to execute on the contract"), true)->required();
D
Daniel Larimer 已提交
1085
   actionsSubcommand->add_option("data", data, localized("The arguments to the contract"))->required();
1086

D
Daniel Larimer 已提交
1087 1088
   add_standard_transaction_options(actionsSubcommand);
   actionsSubcommand->set_callback([&] {
1089 1090
      fc::variant action_args_var;
      try {
1091 1092
         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))
1093

N
Nathan Hourt 已提交
1094 1095 1096
      auto arg= fc::mutable_variant_object
                ("code", contract)
                ("action", action)
1097
                ("args", action_args_var);
N
Nathan Hourt 已提交
1098 1099
      auto result = call(json_to_bin_func, arg);

1100
      auto accountPermissions = get_account_permissions(tx_permission);
1101

M
Matias Romeo 已提交
1102
      send_actions({chain::action{accountPermissions, contract, action, result.get_object()["binargs"].as<bytes>()}});
N
Nathan Hourt 已提交
1103 1104 1105
   });

   // push transaction
1106
   string trx_to_push;
1107
   auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction"));
1108
   trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required();
1109

N
Nathan Hourt 已提交
1110
   trxSubcommand->set_callback([&] {
1111 1112
      fc::variant trx_var;
      try {
1113 1114
         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))
A
Andrianto Lie 已提交
1115
      signed_transaction trx = trx_var.as<signed_transaction>();
1116
      auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none));
N
Nathan Hourt 已提交
1117 1118
      std::cout << fc::json::to_pretty_string(trx_result) << std::endl;
   });
1119

1120 1121

   string trxsJson;
1122
   auto trxsSubcommand = push->add_subcommand("transactions", localized("Push an array of arbitrary JSON transactions"));
1123
   trxsSubcommand->add_option("transactions", trxsJson, localized("The JSON string or filename defining the array of the transactions to push"))->required();
1124
   trxsSubcommand->set_callback([&] {
1125 1126
      fc::variant trx_var;
      try {
1127 1128
         trx_var = json_from_file_or_string(trxsJson);
      } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trxsJson))
1129
      auto trxs_result = call(push_txns_func, trx_var);
1130 1131 1132
      std::cout << fc::json::to_pretty_string(trxs_result) << std::endl;
   });

1133 1134 1135 1136
   try {
       app.parse(argc, argv);
   } catch (const CLI::ParseError &e) {
       return app.exit(e);
1137 1138
   } catch (const explained_exception& e) {
      return 1;
1139
   } catch (const fc::exception& e) {
N
Nathan Hourt 已提交
1140
      auto errorString = e.to_detail_string();
1141 1142
      if (errorString.find("Connection refused") != string::npos) {
         if (errorString.find(fc::json::to_string(port)) != string::npos) {
1143
            std::cerr << localized("Failed to connect to nodeos at ${ip}:${port}; is nodeos running?", ("ip", host)("port", port)) << std::endl;
1144
         } else if (errorString.find(fc::json::to_string(wallet_port)) != string::npos) {
1145
            std::cerr << localized("Failed to connect to keosd at ${ip}:${port}; is keosd running?", ("ip", wallet_host)("port", wallet_port)) << std::endl;
1146
         } else {
1147
            std::cerr << localized("Failed to connect") << std::endl;
1148 1149 1150 1151
         }

         if (verbose_errors) {
            elog("connect error: ${e}", ("e", errorString));
1152 1153
         }
      } else {
1154
         // attempt to extract the error code if one is present
1155 1156 1157 1158 1159
         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()));
            }
1160
         }
1161
      }
1162 1163 1164 1165
      return 1;
   }

   return 0;
1166
}