From d433a696e527a01c1cbef48495652335140f0bb2 Mon Sep 17 00:00:00 2001
From: Thomas Winget <tewinget@gmail.com>
Date: Tue, 17 Jun 2014 18:15:21 -0400
Subject: [PATCH] wallet RPC converted to use new transaction semantics

wallet RPC now uses wallet2::create_transactions and wallet2::commit_tx instead
of wallet2::transfer.  This made it possible to add the RPC call /transfer_split, which
will split transactions automatically if they are too large.  The old call to
/transfer will return an error stating to use /transfer_split if multiple
transactions are needed to fulfill the request.
---
 src/simplewallet/simplewallet.cpp             | 196 +++---------------
 src/simplewallet/simplewallet.h               |   1 -
 src/wallet/wallet2.cpp                        | 156 +++++++++++++-
 src/wallet/wallet2.h                          |   4 +-
 src/wallet/wallet_rpc_server.cpp              |  96 +++++++--
 src/wallet/wallet_rpc_server.h                |   5 +-
 ...fs.h => wallet_rpc_server_commands_defs.h} |  33 ++-
 7 files changed, 306 insertions(+), 185 deletions(-)
 rename src/wallet/{wallet_rpc_server_commans_defs.h => wallet_rpc_server_commands_defs.h} (83%)

diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index e747ee5e1..8ceb583be 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -786,169 +786,6 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
   return true;
 }
 
-// split_amounts(vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
-//
-// split amount for each dst in dsts into num_splits parts
-// and make num_splits new vector<crypt...> instances to hold these new amounts
-std::vector<std::vector<cryptonote::tx_destination_entry>> simple_wallet::split_amounts(
-    std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
-{
-  std::vector<std::vector<cryptonote::tx_destination_entry>> retVal;
-
-  if (num_splits <= 1)
-  {
-    retVal.push_back(dsts);
-    return retVal;
-  }
-
-  // for each split required
-  for (size_t i=0; i < num_splits; i++)
-  {
-    std::vector<cryptonote::tx_destination_entry> new_dsts;
-
-    // for each destination
-    for (size_t j=0; j < dsts.size(); j++)
-    {
-      cryptonote::tx_destination_entry de;
-      uint64_t amount;
-
-      amount = dsts[j].amount;
-      amount = amount / num_splits;
-
-      // if last split, add remainder
-      if (i + 1 == num_splits)
-      {
-        amount += dsts[j].amount % num_splits;
-      }
-      
-      de.addr = dsts[j].addr;
-      de.amount = amount;
-
-      new_dsts.push_back(de);
-    }
-
-    retVal.push_back(new_dsts);
-  }
-
-  return retVal;
-}
-
-//----------------------------------------------------------------------------------------------------
-// separated the call(s) to wallet2::transfer into their own function
-//
-// this function will make multiple calls to wallet2::transfer if multiple
-// transactions will be required
-void simple_wallet::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra)
-{
-  // for now, limit to 30 attempts.  TODO: discuss a good number to limit to.
-  const size_t MAX_ATTEMPTS = 30;
-
-  // failsafe split attempt counter
-  size_t attempt_count = 0;
-
-  for(attempt_count = 1; ;attempt_count++)
-  {
-    auto split_values = split_amounts(dsts, attempt_count);
-
-    // Throw if split_amounts comes back with a vector of size different than it should
-    if (split_values.size() != attempt_count)
-    {
-      throw std::runtime_error("Splitting transactions returned a number of potential tx not equal to what was requested");
-    }
-
-    std::vector<tools::wallet2::pending_tx> ptx_vector;
-    try
-    {
-      // for each new destination vector (i.e. for each new tx)
-      for (auto & dst_vector : split_values)
-      {
-        cryptonote::transaction tx;
-        tools::wallet2::pending_tx ptx;
-        m_wallet->transfer(dst_vector, fake_outs_count, unlock_time, fee, extra, tx, ptx);
-        ptx_vector.push_back(ptx);
-
-        // mark transfers to be used as "spent"
-        BOOST_FOREACH(tools::wallet2::transfer_container::iterator it, ptx.selected_transfers)
-          it->m_spent = true;
-      }
-
-      // if we made it this far, we've selected our transactions.  committing them will mark them spent,
-      // so this is a failsafe in case they don't go through
-      // unmark pending tx transfers as spent
-      for (auto & ptx : ptx_vector)
-      {
-        // mark transfers to be used as not spent
-        BOOST_FOREACH(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
-          it2->m_spent = false;
-
-      }
-
-      // prompt user to confirm splits (if needed)
-      if (attempt_count > 1)
-      {
-        std::string prompt_str = "Your transaction needs to be split into ";
-        prompt_str += std::to_string(attempt_count);
-        prompt_str += " transactions.  This will result in a fee of ";
-        prompt_str += print_money(attempt_count * DEFAULT_FEE);
-        prompt_str += ".  Is this okay?  (Y/Yes/N/No)";
-        std::string accepted = command_line::input_line(prompt_str);
-        if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
-        {
-          fail_msg_writer() << "Transaction cancelled.";
-          return;
-        }
-      }
-
-      // if we made it this far, we're OK to actually send the transactions
-      while (!ptx_vector.empty())
-      {
-        auto & ptx = ptx_vector.back();
-        m_wallet->commit_tx(ptx);
-        success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(ptx.tx);
-        // if no exception, remove element from vector
-        ptx_vector.pop_back();
-      }
-
-      return;
-
-    }
-    // only catch this here, other exceptions need to pass through to the calling function
-    catch (const tools::error::tx_too_big& e)
-    {
-
-      // unmark pending tx transfers as spent
-      for (auto & ptx : ptx_vector)
-      {
-        // mark transfers to be used as not spent
-        BOOST_FOREACH(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
-          it2->m_spent = false;
-
-      }
-
-      if (attempt_count >= MAX_ATTEMPTS)
-      {
-        throw;
-      }
-    }
-    catch (...)
-    {
-      // in case of some other exception, make sure any tx in queue are marked unspent again
-
-      // unmark pending tx transfers as spent
-      for (auto & ptx : ptx_vector)
-      {
-        // mark transfers to be used as not spent
-        BOOST_FOREACH(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
-          it2->m_spent = false;
-
-      }
-
-      throw;
-    }
-  }
-  //success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx);
-}
-
 //----------------------------------------------------------------------------------------------------
 bool simple_wallet::transfer(const std::vector<std::string> &args_)
 {
@@ -1015,7 +852,38 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
 
   try
   {
-    create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, DEFAULT_FEE, extra);
+    // figure out what tx will be necessary
+    auto ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, DEFAULT_FEE, extra);
+
+    // if more than one tx necessary, prompt user to confirm
+    if (ptx_vector.size() > 1)
+    {
+        std::string prompt_str = "Your transaction needs to be split into ";
+        prompt_str += std::to_string(ptx_vector.size());
+        prompt_str += " transactions.  This will result in a fee of ";
+        prompt_str += print_money(ptx_vector.size() * DEFAULT_FEE);
+        prompt_str += ".  Is this okay?  (Y/Yes/N/No)";
+        std::string accepted = command_line::input_line(prompt_str);
+        if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
+        {
+          fail_msg_writer() << "Transaction cancelled.";
+
+          // would like to return false, because no tx made, but everything else returns true
+          // and I don't know what returning false might adversely affect.  *sigh*
+          return true; 
+        }
+    }
+
+    // actually commit the transactions
+    while (!ptx_vector.empty())
+    {
+      auto & ptx = ptx_vector.back();
+      m_wallet->commit_tx(ptx);
+      success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(ptx.tx);
+
+      // if no exception, remove element from vector
+      ptx_vector.pop_back();
+    }
   }
   catch (const tools::error::daemon_busy&)
   {
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 8a6ac389a..e919cfeda 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -57,7 +57,6 @@ namespace cryptonote
     std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
         std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
     );
-    void create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra);
     bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
     bool save(const std::vector<std::string> &args);
     bool set_log(const std::vector<std::string> &args);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 7dfbc7f7f..5b284c619 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -43,6 +43,9 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
 
 namespace tools
 {
+// for now, limit to 30 attempts.  TODO: discuss a good number to limit to.
+const size_t MAX_SPLIT_ATTEMPTS = 30;
+
 //----------------------------------------------------------------------------------------------------
 void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit)
 {
@@ -697,6 +700,56 @@ void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts
   pending_tx ptx;
   transfer(dsts, fake_outputs_count, unlock_time, fee, extra, tx, ptx);
 }
+
+namespace {
+// split_amounts(vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
+//
+// split amount for each dst in dsts into num_splits parts
+// and make num_splits new vector<crypt...> instances to hold these new amounts
+std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
+    std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
+{
+  std::vector<std::vector<cryptonote::tx_destination_entry>> retVal;
+
+  if (num_splits <= 1)
+  {
+    retVal.push_back(dsts);
+    return retVal;
+  }
+
+  // for each split required
+  for (size_t i=0; i < num_splits; i++)
+  {
+    std::vector<cryptonote::tx_destination_entry> new_dsts;
+
+    // for each destination
+    for (size_t j=0; j < dsts.size(); j++)
+    {
+      cryptonote::tx_destination_entry de;
+      uint64_t amount;
+
+      amount = dsts[j].amount;
+      amount = amount / num_splits;
+
+      // if last split, add remainder
+      if (i + 1 == num_splits)
+      {
+        amount += dsts[j].amount % num_splits;
+      }
+      
+      de.addr = dsts[j].addr;
+      de.amount = amount;
+
+      new_dsts.push_back(de);
+    }
+
+    retVal.push_back(new_dsts);
+  }
+
+  return retVal;
+}
+} // anonymous namespace
+
 //----------------------------------------------------------------------------------------------------
 // take a pending tx and actually send it to the daemon
 void wallet2::commit_tx(pending_tx& ptx)
@@ -718,10 +771,105 @@ void wallet2::commit_tx(pending_tx& ptx)
     it->m_spent = true;
 
   LOG_PRINT_L0("Transaction successfully sent. <" << get_transaction_hash(ptx.tx) << ">" << ENDL
-                << "Commission: " << print_money(ptx.fee+ptx.dust) << " (dust: " << print_money(ptx.dust) << ")" << ENDL
-                << "Balance: " << print_money(balance()) << ENDL
-                << "Unlocked: " << print_money(unlocked_balance()) << ENDL
-                << "Please, wait for confirmation for your balance to be unlocked.");
+            << "Commission: " << print_money(ptx.fee+ptx.dust) << " (dust: " << print_money(ptx.dust) << ")" << ENDL
+            << "Balance: " << print_money(balance()) << ENDL
+            << "Unlocked: " << print_money(unlocked_balance()) << ENDL
+            << "Please, wait for confirmation for your balance to be unlocked.");
 }
 
+void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector)
+{
+  for (auto & ptx : ptx_vector)
+  {
+    commit_tx(ptx);
+  }
+}
+
+//----------------------------------------------------------------------------------------------------
+// separated the call(s) to wallet2::transfer into their own function
+//
+// this function will make multiple calls to wallet2::transfer if multiple
+// transactions will be required
+std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra)
+{
+
+  // failsafe split attempt counter
+  size_t attempt_count = 0;
+
+  for(attempt_count = 1; ;attempt_count++)
+  {
+    auto split_values = split_amounts(dsts, attempt_count);
+
+    // Throw if split_amounts comes back with a vector of size different than it should
+    if (split_values.size() != attempt_count)
+    {
+      throw std::runtime_error("Splitting transactions returned a number of potential tx not equal to what was requested");
+    }
+
+    std::vector<pending_tx> ptx_vector;
+    try
+    {
+      // for each new destination vector (i.e. for each new tx)
+      for (auto & dst_vector : split_values)
+      {
+        cryptonote::transaction tx;
+        pending_tx ptx;
+        transfer(dst_vector, fake_outs_count, unlock_time, fee, extra, tx, ptx);
+        ptx_vector.push_back(ptx);
+
+        // mark transfers to be used as "spent"
+        BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers)
+          it->m_spent = true;
+      }
+
+      // if we made it this far, we've selected our transactions.  committing them will mark them spent,
+      // so this is a failsafe in case they don't go through
+      // unmark pending tx transfers as spent
+      for (auto & ptx : ptx_vector)
+      {
+        // mark transfers to be used as not spent
+        BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
+          it2->m_spent = false;
+
+      }
+
+      // if we made it this far, we're OK to actually send the transactions
+      return ptx_vector;
+
+    }
+    // only catch this here, other exceptions need to pass through to the calling function
+    catch (const tools::error::tx_too_big& e)
+    {
+
+      // unmark pending tx transfers as spent
+      for (auto & ptx : ptx_vector)
+      {
+        // mark transfers to be used as not spent
+        BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
+          it2->m_spent = false;
+
+      }
+
+      if (attempt_count >= MAX_SPLIT_ATTEMPTS)
+      {
+        throw;
+      }
+    }
+    catch (...)
+    {
+      // in case of some other exception, make sure any tx in queue are marked unspent again
+
+      // unmark pending tx transfers as spent
+      for (auto & ptx : ptx_vector)
+      {
+        // mark transfers to be used as not spent
+        BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers)
+          it2->m_spent = false;
+
+      }
+
+      throw;
+    }
+  }
+}
 }
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index bbedff961..1f5ae7062 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -138,7 +138,9 @@ namespace tools
     void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx);
     void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra);
     void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx);
-    void commit_tx(pending_tx& ptx);
+    void commit_tx(pending_tx& ptx_vector);
+    void commit_tx(std::vector<pending_tx>& ptx_vector);
+    std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra);
     bool check_connection();
     void get_transfers(wallet2::transfer_container& incoming_transfers) const;
     void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments) const;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 95c71fc68..ec31d4625 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -10,6 +10,7 @@ using namespace epee;
 #include "common/command_line.h"
 #include "cryptonote_core/cryptonote_format_utils.h"
 #include "cryptonote_core/account.h"
+#include "wallet_rpc_server_commands_defs.h"
 #include "misc_language.h"
 #include "string_tools.h"
 #include "crypto/hash.h"
@@ -85,12 +86,11 @@ namespace tools
     }
     return true;
   }
-  //------------------------------------------------------------------------------------------------------------------------------
-  bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx)
-  {
 
-    std::vector<cryptonote::tx_destination_entry> dsts;
-    for (auto it = req.destinations.begin(); it != req.destinations.end(); it++)
+  //------------------------------------------------------------------------------------------------------------------------------
+  bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, const std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t> extra, epee::json_rpc::error& er)
+  {
+    for (auto it = destinations.begin(); it != destinations.end(); it++)
     {
       cryptonote::tx_destination_entry de;
       if(!get_account_address_from_str(de.addr, it->address))
@@ -103,11 +103,11 @@ namespace tools
       dsts.push_back(de);
     }
 
-    std::vector<uint8_t> extra;
-    if (!req.payment_id.empty()) {
+    if (!payment_id.empty())
+    {
 
       /* Just to clarify */
-      const std::string& payment_id_str = req.payment_id;
+      const std::string& payment_id_str = payment_id;
 
       crypto::hash payment_id;
       /* Parse payment ID */
@@ -128,13 +128,85 @@ namespace tools
       }
 
     }
+    return true;
+  }
+
+  //------------------------------------------------------------------------------------------------------------------------------
+  bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx)
+  {
+
+    std::vector<cryptonote::tx_destination_entry> dsts;
+    std::vector<uint8_t> extra;
+
+    // validate the transfer requested and populate dsts & extra
+    if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er))
+    {
+      return false;
+    }
 
     try
     {
-      cryptonote::transaction tx;
-      wallet2::pending_tx ptx;
-      m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, tx, ptx);
-      res.tx_hash = boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(tx));
+      std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra);
+
+      // reject proposed transactions if there are more than one.  see on_transfer_split below.
+      if (ptx_vector.size() != 1)
+      {
+        er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
+        er.message = "Transaction would be too large.  try /transfer_split.";
+        return false;
+      }
+
+      m_wallet.commit_tx(ptx_vector);
+
+      // populate response with tx hash
+      res.tx_hash = boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(ptx_vector.back().tx));
+      return true;
+    }
+    catch (const tools::error::daemon_busy& e)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
+      er.message = e.what();
+      return false;
+    }
+    catch (const std::exception& e)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
+      er.message = e.what();
+      return false;
+    }
+    catch (...)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+      er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+      return false;
+    }
+    return true;
+  }
+  //------------------------------------------------------------------------------------------------------------------------------
+  bool wallet_rpc_server::on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er, connection_context& cntx)
+  {
+
+    std::vector<cryptonote::tx_destination_entry> dsts;
+    std::vector<uint8_t> extra;
+
+    // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types.
+    if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er))
+    {
+      return false;
+    }
+
+    try
+    {
+      std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra);
+
+      m_wallet.commit_tx(ptx_vector);
+
+      // populate response with tx hashes
+      for (auto & ptx : ptx_vector)
+      {
+        res.tx_hash_list.push_back(boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(ptx.tx)));
+      }
+
       return true;
     }
     catch (const tools::error::daemon_busy& e)
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index b8373d27d..f9893566a 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -7,7 +7,7 @@
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/variables_map.hpp>
 #include "net/http_server_impl_base.h"
-#include "wallet_rpc_server_commans_defs.h"
+#include "wallet_rpc_server_commands_defs.h"
 #include "wallet2.h"
 #include "common/command_line.h"
 namespace tools
@@ -38,6 +38,7 @@ namespace tools
         MAP_JON_RPC_WE("getbalance",         on_getbalance,         wallet_rpc::COMMAND_RPC_GET_BALANCE)
         MAP_JON_RPC_WE("getaddress",         on_getaddress,         wallet_rpc::COMMAND_RPC_GET_ADDRESS)
         MAP_JON_RPC_WE("transfer",           on_transfer,           wallet_rpc::COMMAND_RPC_TRANSFER)
+        MAP_JON_RPC_WE("transfer_split",     on_transfer_split,     wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
         MAP_JON_RPC_WE("store",              on_store,              wallet_rpc::COMMAND_RPC_STORE)
         MAP_JON_RPC_WE("get_payments",       on_get_payments,       wallet_rpc::COMMAND_RPC_GET_PAYMENTS)
         MAP_JON_RPC_WE("incoming_transfers", on_incoming_transfers, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS)
@@ -47,7 +48,9 @@ namespace tools
       //json_rpc
       bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er, connection_context& cntx);
       bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx);
+      bool validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, const std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t> extra, epee::json_rpc::error& er);
       bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx);
+      bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er, connection_context& cntx);
       bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, connection_context& cntx);
       bool on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx);
       bool on_incoming_transfers(const wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::response& res, epee::json_rpc::error& er, connection_context& cntx);
diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
similarity index 83%
rename from src/wallet/wallet_rpc_server_commans_defs.h
rename to src/wallet/wallet_rpc_server_commands_defs.h
index 7ffbcfc18..33130da06 100644
--- a/src/wallet/wallet_rpc_server_commans_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -52,7 +52,7 @@ namespace wallet_rpc
     };
   };
 
-  struct trnsfer_destination
+  struct transfer_destination
   {
     uint64_t amount;
     std::string address;
@@ -66,7 +66,7 @@ namespace wallet_rpc
   {
     struct request
     {
-      std::list<trnsfer_destination> destinations;
+      std::list<transfer_destination> destinations;
       uint64_t fee;
       uint64_t mixin;
       uint64_t unlock_time;
@@ -91,6 +91,35 @@ namespace wallet_rpc
     };
   };
 
+  struct COMMAND_RPC_TRANSFER_SPLIT
+  {
+    struct request
+    {
+      std::list<transfer_destination> destinations;
+      uint64_t fee;
+      uint64_t mixin;
+      uint64_t unlock_time;
+      std::string payment_id;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(destinations)
+        KV_SERIALIZE(fee)
+        KV_SERIALIZE(mixin)
+        KV_SERIALIZE(unlock_time)
+        KV_SERIALIZE(payment_id)
+      END_KV_SERIALIZE_MAP()
+    };
+
+    struct response
+    {
+      std::list<std::string> tx_hash_list;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(tx_hash_list)
+      END_KV_SERIALIZE_MAP()
+    };
+  };
+
   struct COMMAND_RPC_STORE
   {
     struct request