diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 57cb67315f..19986c9567 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -2967,6 +2967,9 @@
True
+
+ True
+
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index f91c2c3253..03058285dc 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -3651,6 +3651,9 @@
ripple\rpc\handlers
+
+ ripple\rpc\handlers
+
ripple\rpc\handlers
diff --git a/src/ripple/app/paths/RippleState.h b/src/ripple/app/paths/RippleState.h
index 175ab301dc..fac14266c5 100644
--- a/src/ripple/app/paths/RippleState.h
+++ b/src/ripple/app/paths/RippleState.h
@@ -42,7 +42,7 @@ public:
using pointer = std::shared_ptr ;
public:
- RippleState() = delete;
+ RippleState () = delete;
virtual ~RippleState() = default;
diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp
index c1a1cc19ce..7151e6e2a9 100644
--- a/src/ripple/net/impl/RPCCall.cpp
+++ b/src/ripple/net/impl/RPCCall.cpp
@@ -790,6 +790,46 @@ private:
return jvRequest;
}
+ // parse gateway balances
+ // gateway_balances [] [ [ ]]
+
+ Json::Value parseGatewayBalances (Json::Value const& jvParams)
+ {
+ unsigned int index = 0;
+ const unsigned int size = jvParams.size ();
+
+ Json::Value jvRequest;
+
+ std::string param = jvParams[index++].asString ();
+ if (param.empty ())
+ return RPC::make_param_error ("Invalid first parameter");
+
+ if (param[0] != 'r')
+ {
+ if (param.size() == 64)
+ jvRequest[jss::ledger_hash] = param;
+ else
+ jvRequest[jss::ledger_index] = param;
+
+ if (size <= index)
+ return RPC::make_param_error ("Invalid hotwallet");
+
+ param = jvParams[index++].asString ();
+ }
+
+ jvRequest[jss::account] = param;
+
+ if (index < size)
+ {
+ Json::Value& hotWallets =
+ (jvRequest["hotwallet"] = Json::arrayValue);
+ while (index < size)
+ hotWallets.append (jvParams[index++].asString ());
+ }
+
+ return jvRequest;
+ }
+
public:
//--------------------------------------------------------------------------
@@ -858,6 +898,7 @@ public:
{ "consensus_info", &RPCParser::parseAsIs, 0, 0 },
{ "feature", &RPCParser::parseFeature, 0, 2 },
{ "fetch_info", &RPCParser::parseFetchInfo, 0, 1 },
+ { "gateway_balances", &RPCParser::parseGatewayBalances , 1, -1 },
{ "get_counts", &RPCParser::parseGetCounts, 0, 1 },
{ "json", &RPCParser::parseJson, 2, 2 },
{ "ledger", &RPCParser::parseLedger, 0, 2 },
diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h
index 14319b8978..f251eb9e43 100644
--- a/src/ripple/protocol/JsonFields.h
+++ b/src/ripple/protocol/JsonFields.h
@@ -79,8 +79,10 @@ JSS ( age ); // out: UniqueNodeList, NetworkOPs
JSS ( alternatives ); // out: PathRequest, RipplePathFind
JSS ( amendment_blocked ); // out: NetworkOPs
JSS ( asks ); // out: Subscribe
+JSS ( assets ); // out: GatewayBalances
JSS ( authorized ); // out: AccountLines
JSS ( balance ); // out: AccountLines
+JSS ( balances ); // out: GatewayBalances
JSS ( base ); // out: LogLevel
JSS ( base_fee ); // out: NetworkOPs
JSS ( base_fee_xrp ); // out: NetworkOPs
@@ -252,6 +254,7 @@ JSS ( node_reads_total ); // out: GetCounts
JSS ( node_writes ); // out: GetCounts
JSS ( node_written_bytes ); // out: GetCounts
JSS ( nodes ); // out: LedgerEntrySet, PathState
+JSS ( obligations ); // out: GatewayBalances
JSS ( offer ); // in: LedgerEntry
JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe
JSS ( offline ); // in: TransactionSign
diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/ripple/rpc/handlers/GatewayBalances.cpp
new file mode 100644
index 0000000000..425f3b33b8
--- /dev/null
+++ b/src/ripple/rpc/handlers/GatewayBalances.cpp
@@ -0,0 +1,229 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2014 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+#include
+
+namespace ripple {
+
+// Query:
+// 1) Specify ledger to query.
+// 2) Specify issuer account (cold wallet) in "account" field.
+// 3) Specify accounts that hold gateway assets (such as hot wallets)
+// using "hotwallet" field which should be either a string (if just
+// one wallet) or an array of strings (if more than one).
+
+// Response:
+// 1) Array, "obligations", indicating the total obligations of the
+// gateway in each currency. Obligations to specified hot wallets
+// are not counted here.
+// 2) Object, "balances", indicating balances in each account
+// that holds gateway assets. (Those specified in the "hotwallet"
+// field.)
+// 3) Object of "assets" indicating accounts that owe the gateway.
+// (Gateways typically do not hold positive balances. This is unusual.)
+
+// gateway_balances [] [ [ hotWallets;
+
+ if (params.isMember ("hotwallet"))
+ {
+ Json::Value const& hw = params["hotwallet"];
+ bool valid = true;
+
+ auto addHotWallet = [&valid, &hotWallets](Json::Value const& j)
+ {
+ if (j.isString())
+ {
+ RippleAddress ra;
+ if (! ra.setAccountPublic (j.asString ()) &&
+ ! ra.setAccountID (j.asString()))
+ {
+ valid = false;
+ }
+ else
+ hotWallets.insert (ra.getAccountID ());
+ }
+ else
+ {
+ valid = false;
+ }
+ };
+
+ if (hw.isArray())
+ {
+ for (unsigned i = 0; i < hw.size(); ++i)
+ addHotWallet (hw[i]);
+ }
+ else if (hw.isString())
+ {
+ addHotWallet (hw);
+ }
+ else
+ {
+ valid = false;
+ }
+
+ if (! valid)
+ {
+ result[jss::error] = "invalidHotWallet";
+ return result;
+ }
+
+ }
+
+ std::map sums;
+ std::map > hotBalances;
+ std::map > assets;
+
+ // Traverse the cold wallet's trust lines
+ forEachItem(*ledger, accountID, getApp().getSLECache(),
+ [&](std::shared_ptr const& sle)
+ {
+ auto rs = RippleState::makeItem (accountID, sle);
+
+ if (!rs)
+ return;
+
+ int balSign = rs->getBalance().signum();
+ if (balSign == 0)
+ return;
+
+ auto const& peer = rs->getAccountIDPeer();
+
+ // Here, a negative balance means the cold wallet owes (normal)
+ // A positive balance means the cold wallet has an asset (unusual)
+
+ if (hotWallets.count (peer) > 0)
+ {
+ // This is a specified hot wallt
+ hotBalances[peer].push_back (-rs->getBalance ());
+ }
+ else if (balSign > 0)
+ {
+ // This is a gateway asset
+ assets[peer].push_back (rs->getBalance ());
+ }
+ else
+ {
+ // normal negative balance, obligation to customer
+ auto& bal = sums[rs->getBalance().getCurrency()];
+ if (bal == zero)
+ {
+ // This is needed to set the currency code correctly
+ bal = -rs->getBalance();
+ }
+ else
+ bal -= rs->getBalance();
+ }
+ });
+
+ if (! sums.empty())
+ {
+ Json::Value& j = (result [jss::obligations] = Json::objectValue);
+ for (auto const& e : sums)
+ {
+ j[to_string (e.first)] = e.second.getText ();
+ }
+ }
+
+ if (! hotBalances.empty())
+ {
+ Json::Value& j = (result [jss::balances] = Json::objectValue);
+ for (auto const& account : hotBalances)
+ {
+ Json::Value& balanceArray = (j[to_string (account.first)] = Json::arrayValue);
+ for (auto const& balance : account.second)
+ {
+ Json::Value& entry = balanceArray.append (Json::objectValue);
+ entry[jss::currency] = to_string (balance.issue ().currency);
+ entry[jss::value] = balance.getText();
+ }
+ }
+ }
+
+ if (! assets.empty())
+ {
+ Json::Value& j = (result [jss::assets] = Json::objectValue);
+
+ for (auto const& account : assets)
+ {
+ Json::Value& balanceArray = (j[to_string (account.first)] = Json::arrayValue);
+ for (auto const& balance : account.second)
+ {
+ Json::Value& entry = balanceArray.append (Json::objectValue);
+ entry[jss::currency] = to_string (balance.issue ().currency);
+ entry[jss::value] = balance.getText();
+ }
+ }
+ }
+
+ return result;
+}
+
+} // ripple
diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h
index 0ccebad638..44bc746358 100644
--- a/src/ripple/rpc/handlers/Handlers.h
+++ b/src/ripple/rpc/handlers/Handlers.h
@@ -37,6 +37,7 @@ Json::Value doConnect (RPC::Context&);
Json::Value doConsensusInfo (RPC::Context&);
Json::Value doFeature (RPC::Context&);
Json::Value doFetchInfo (RPC::Context&);
+Json::Value doGatewayBalances (RPC::Context&);
Json::Value doGetCounts (RPC::Context&);
Json::Value doInternal (RPC::Context&);
Json::Value doLedgerAccept (RPC::Context&);
diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp
index 019f1c2d92..1703b417b9 100644
--- a/src/ripple/rpc/impl/Handler.cpp
+++ b/src/ripple/rpc/impl/Handler.cpp
@@ -109,6 +109,7 @@ HandlerTable HANDLERS({
{ "can_delete", byRef (&doCanDelete), Role::ADMIN, NO_CONDITION },
{ "connect", byRef (&doConnect), Role::ADMIN, NO_CONDITION },
{ "consensus_info", byRef (&doConsensusInfo), Role::ADMIN, NO_CONDITION },
+ { "gateway_balances", byRef (&doGatewayBalances), Role::USER, NO_CONDITION },
{ "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION },
{ "internal", byRef (&doInternal), Role::ADMIN, NO_CONDITION },
{ "feature", byRef (&doFeature), Role::ADMIN, NO_CONDITION },
diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp
index ec30fc67b4..65255522a4 100644
--- a/src/ripple/unity/rpcx.cpp
+++ b/src/ripple/unity/rpcx.cpp
@@ -49,6 +49,7 @@
#include
#include
#include
+#include
#include
#include
#include