Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.savl.ripple.client.Client Maven / Gradle / Ivy
package com.savl.ripple.client;
import com.savl.ripple.client.enums.Command;
import com.savl.ripple.client.enums.Message;
import com.savl.ripple.client.enums.RPCErr;
import com.savl.ripple.client.pubsub.Publisher;
import com.savl.ripple.client.requests.Request;
import com.savl.ripple.client.responses.Response;
import com.savl.ripple.client.subscriptions.ServerInfo;
import com.savl.ripple.client.subscriptions.SubscriptionManager;
import com.savl.ripple.client.subscriptions.TrackedAccountRoot;
import com.savl.ripple.client.subscriptions.TransactionSubscriptionManager;
import com.savl.ripple.client.transactions.AccountTxPager;
import com.savl.ripple.client.transactions.TransactionManager;
import com.savl.ripple.client.transport.TransportEventHandler;
import com.savl.ripple.client.transport.WebSocketTransport;
import com.savl.ripple.client.types.AccountLine;
import com.savl.ripple.core.coretypes.AccountID;
import com.savl.ripple.core.coretypes.Issue;
import com.savl.ripple.core.coretypes.STObject;
import com.savl.ripple.core.coretypes.hash.Hash256;
import com.savl.ripple.core.coretypes.uint.UInt32;
import com.savl.ripple.core.types.known.sle.LedgerEntry;
import com.savl.ripple.core.types.known.sle.entries.AccountRoot;
import com.savl.ripple.core.types.known.sle.entries.Offer;
import com.savl.ripple.core.types.known.tx.result.TransactionResult;
import com.savl.ripple.crypto.ecdsa.IKeyPair;
import com.savl.ripple.crypto.ecdsa.Seed;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URI;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.savl.ripple.client.requests.Request.Manager;
import static com.savl.ripple.client.requests.Request.VALIDATED_LEDGER;
public class Client extends Publisher implements TransportEventHandler {
// Logger
public static final Logger logger = Logger.getLogger(Client.class.getName());
// Events
public static interface events extends Publisher.Callback {}
public static interface OnLedgerClosed extends events {}
public static interface OnConnected extends events {}
public static interface OnDisconnected extends events {}
public static interface OnSubscribed extends events {}
public static interface OnMessage extends events {}
public static interface OnSendMessage extends events {}
public static interface OnStateChange extends events {}
public static interface OnPathFind extends events {}
public static interface OnValidatedTransaction extends events {}
// Fluent binders
public Client onValidatedTransaction(OnValidatedTransaction cb) {
on(OnValidatedTransaction.class, cb);
return this;
}
public Client onLedgerClosed(OnLedgerClosed cb) {
on(OnLedgerClosed.class, cb);
return this;
}
public Client onConnected(OnConnected onConnected) {
this.on(OnConnected.class, onConnected);
return this;
}
// Event handler sugar
public Client onDisconnected(OnDisconnected cb) {
on(OnDisconnected.class, cb);
return this;
}
// ### Members
// The implementation of the WebSocket
WebSocketTransport ws;
/**
* When this is non 0, we randomly disconnect when trying to send messages
* See {@link Client#sendMessage}
*/
public double randomBugsFrequency = 0;
Random randomBugs = new Random();
// When this is set, all transactions will be routed first to this, which
// will then notify the client
TransactionSubscriptionManager transactionSubscriptionManager;
// This is in charge of executing code in the `clientThread`
protected ScheduledExecutorService service;
// All code that use the Client api, must be run on this thread
/**
See {@link Client#run}
*/
protected Thread clientThread;
protected TreeMap requests = new TreeMap();
// Keeps track of the `id` doled out to Request objects
private int cmdIDs;
// The last uri we were connected to
String previousUri;
// Every x ms, we clean up timed out requests
public long maintenanceSchedule = 10000; //ms
// Are we currently connected?
public boolean connected = false;
// If we haven't received any message from the server after x many
// milliseconds, disconnect and reconnect again.
private long reconnectDormantAfter = 20000; // ms
// ms since unix time of the last indication of an alive connection
private long lastConnection = -1; // -1 means null
// Did we disconnect manually? If not, try and reconnect
private boolean manuallyDisconnected = false;
// Tracks the serverInfo we are currently connected to
public ServerInfo serverInfo = new ServerInfo();
private HashMap accounts = new HashMap();
// Handles [un]subscription requests, also on reconnect
public SubscriptionManager subscriptions = new SubscriptionManager();
// Constructor
public Client(WebSocketTransport ws) {
this.ws = ws;
ws.setHandler(this);
prepareExecutor();
// requires executor, so call after prepareExecutor
scheduleMaintenance();
subscriptions.on(SubscriptionManager.OnSubscribed.class, new SubscriptionManager.OnSubscribed() {
@Override
public void called(JSONObject subscription) {
if (!connected)
return;
subscribe(subscription);
}
});
}
// ### Getters
private int reconnectDelay() {
return 1000;
}
public boolean isManuallyDisconnected() {
return manuallyDisconnected;
}
// ### Setters
public void reconnectDormantAfter(long reconnectDormantAfter) {
this.reconnectDormantAfter = reconnectDormantAfter;
}
public Client transactionSubscriptionManager(TransactionSubscriptionManager transactionSubscriptionManager) {
this.transactionSubscriptionManager = transactionSubscriptionManager;
return this;
}
// ### Helpers
public static void log(Level level, String fmt, Object... args) {
if (logger.isLoggable(level)) {
logger.log(level, fmt, args);
}
}
public static String prettyJSON(JSONObject object) {
return object.toString(4);
}
public static JSONObject parseJSON(String s) {
return new JSONObject(s);
}
/* --------------------------- CONNECT / RECONNECT -------------------------- */
/**
* After calling this method, all subsequent interaction with the api should
* be called via posting Runnable() run blocks to the Executor.
*
* Essentially, all ripple-lib-java api interaction
* should happen on the one thread.
*
* @see #onMessage(org.json.JSONObject)
*/
public Client connect(final String uri) {
manuallyDisconnected = false;
schedule(50, new Runnable() {
@Override
public void run() {
doConnect(uri);
}
});
return this;
}
public void doConnect(String uri) {
log(Level.INFO, "Connecting to " + uri);
previousUri = uri;
ws.connect(URI.create(uri));
}
public void disconnect() {
manuallyDisconnected = true;
ws.disconnect();
// our disconnect handler should do the rest
}
private void emitOnDisconnected() {
// This ensures that the callback method onDisconnect is
// called before a new connection is established this keeps
// the symmetry of connect-> disconnect -> reconnect
emit(OnDisconnected.class, this);
}
/**
* This will detect stalled connections When connected we are subscribed to
* a ledger, and ledgers should be at most 20 seconds apart.
*
* This also
*/
private void scheduleMaintenance() {
schedule(maintenanceSchedule, new Runnable() {
@Override
public void run() {
try {
manageTimedOutRequests();
int defaultValue = -1;
if (!manuallyDisconnected) {
if (connected && lastConnection != defaultValue) {
long time = new Date().getTime();
long msSince = time - lastConnection;
if (msSince > reconnectDormantAfter) {
lastConnection = defaultValue;
reconnect();
}
}
}
} finally {
scheduleMaintenance();
}
}
});
}
public void reconnect() {
disconnect();
connect(previousUri);
}
void manageTimedOutRequests() {
long now = System.currentTimeMillis();
ArrayList timedOut = new ArrayList();
for (Request request : requests.values()) {
if (request.sendTime != 0) {
long since = now - request.sendTime;
if (since >= Request.TIME_OUT) {
timedOut.add(request);
}
}
}
for (Request request : timedOut) {
request.emit(Request.OnTimeout.class, request.response);
requests.remove(request.id);
}
}
// ### Handler binders binder
public void connect(final String s, final OnConnected onConnected) {
run(new Runnable() {
public void run() {
connect(s);
once(OnConnected.class, onConnected);
}
});
}
public void disconnect(final OnDisconnected onDisconnected) {
run(new Runnable() {
public void run() {
Client.this.once(OnDisconnected.class, onDisconnected);
disconnect();
}
});
}
public void whenConnected(boolean nextTick, final OnConnected onConnected) {
if (connected) {
if (nextTick) {
schedule(0, new Runnable() {
@Override
public void run() {
onConnected.called(Client.this);
}
});
} else {
onConnected.called(this);
}
} else {
once(OnConnected.class, onConnected);
}
}
public void nowOrWhenConnected(OnConnected onConnected) {
whenConnected(false, onConnected);
}
public void nextTickOrWhenConnected(OnConnected onConnected) {
whenConnected(true, onConnected);
}
public void dispose() {
ws = null;
}
/* -------------------------------- EXECUTOR -------------------------------- */
public void run(Runnable runnable) {
// What if we are already in the client thread?? What happens then ?
if (runningOnClientThread()) {
runnable.run();
} else {
service.submit(errorHandling(runnable));
}
}
public void schedule(long ms, Runnable runnable) {
service.schedule(errorHandling(runnable), ms, TimeUnit.MILLISECONDS);
}
public boolean runningOnClientThread() {
return clientThread != null && Thread.currentThread().getId() == clientThread.getId();
}
protected void prepareExecutor() {
service = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
clientThread = new Thread(r);
return clientThread;
}
});
}
public static abstract class ThrowingRunnable implements Runnable {
public abstract void throwingRun() throws Exception;
@Override
public void run() {
try {
throwingRun();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private Runnable errorHandling(final Runnable runnable) {
return new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Exception e) {
onException(e);
}
}
};
}
protected void onException(Exception e) {
e.printStackTrace(System.out);
if (logger.isLoggable(Level.WARNING)) {
log(Level.WARNING, "Exception {0}", e);
}
}
private void resetReconnectStatus() {
lastConnection = new Date().getTime();
}
private void updateServerInfo(JSONObject msg) {
serverInfo.update(msg);
}
/* ------------------------- TRANSPORT EVENT HANDLER ------------------------ */
/**
* This is to ensure we run everything on {@link Client#clientThread}
*/
@Override
public void onMessage(final JSONObject msg) {
resetReconnectStatus();
run(new Runnable() {
@Override
public void run() {
onMessageInClientThread(msg);
}
});
}
@Override
public void onConnecting(int attempt) {
}
@Override
public void onError(Exception error) {
onException(error);
}
@Override
public void onDisconnected(boolean willReconnect) {
run(new Runnable() {
@Override
public void run() {
doOnDisconnected();
}
});
}
@Override
public void onConnected() {
run(new Runnable() {
public void run() {
doOnConnected();
}
});
}
/* ----------------------- CLIENT THREAD EVENT HANDLER ---------------------- */
public void onMessageInClientThread(JSONObject msg) {
Message type = Message.valueOf(msg.optString("type", null));
try {
emit(OnMessage.class, msg);
if (logger.isLoggable(Level.FINER)) {
log(Level.FINER, "Receive `{0}`: {1}", type, prettyJSON(msg));
}
switch (type) {
case serverStatus:
updateServerInfo(msg);
break;
case ledgerClosed:
updateServerInfo(msg);
// TODO
emit(OnLedgerClosed.class, serverInfo);
break;
case response:
onResponse(msg);
break;
case transaction:
onTransaction(msg);
break;
case path_find:
emit(OnPathFind.class, msg);
break;
default:
unhandledMessage(msg);
break;
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// This seems to be swallowed higher up, (at least by the
// Java-WebSocket transport implementation)
throw new RuntimeException(e);
} finally {
emit(OnStateChange.class, this);
}
}
private void doOnDisconnected() {
logger.entering(getClass().getName(), "doOnDisconnected");
connected = false;
emitOnDisconnected();
if (!manuallyDisconnected) {
schedule(reconnectDelay(), new Runnable() {
@Override
public void run() {
connect(previousUri);
}
});
} else {
logger.fine("Currently disconnecting, so will not reconnect");
}
logger.entering(getClass().getName(), "doOnDisconnected");
}
private void doOnConnected() {
resetReconnectStatus();
logger.entering(getClass().getName(), "doOnConnected");
connected = true;
emit(OnConnected.class, this);
subscribe(prepareSubscription());
logger.exiting(getClass().getName(), "doOnConnected");
}
void unhandledMessage(JSONObject msg) {
log(Level.WARNING, "Unhandled message: " + msg);
}
void onResponse(JSONObject msg) {
Request request = requests.remove(msg.optInt("id", -1));
if (request == null) {
log(Level.WARNING, "Response without a request: {0}", msg);
return;
}
request.handleResponse(msg);
}
void onTransaction(JSONObject msg) {
TransactionResult tr = new TransactionResult(msg, TransactionResult
.Source
.transaction_subscription_notification);
if (tr.validated) {
if (transactionSubscriptionManager != null) {
transactionSubscriptionManager.notifyTransactionResult(tr);
} else {
onTransactionResult(tr);
}
}
}
public void onTransactionResult(TransactionResult tr) {
log(Level.INFO, "Transaction {0} is validated", tr.hash);
Map affected = tr.modifiedRoots();
if (affected != null) {
Hash256 transactionHash = tr.hash;
UInt32 transactionLedgerIndex = tr.ledgerIndex;
for (Map.Entry entry : affected.entrySet()) {
Account account = accounts.get(entry.getKey());
if (account != null) {
STObject rootUpdates = entry.getValue();
account.getAccountRoot()
.updateFromTransaction(
transactionHash, transactionLedgerIndex, rootUpdates);
}
}
}
Account initator = accounts.get(tr.initiatingAccount());
if (initator != null) {
log(Level.INFO, "Found initiator {0}, notifying transactionManager", initator);
initator.transactionManager().notifyTransactionResult(tr);
} else {
log(Level.INFO, "Can't find initiating account!");
}
emit(OnValidatedTransaction.class, tr);
}
public void sendMessage(JSONObject object) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "Send: {0}", prettyJSON(object));
}
emit(OnSendMessage.class, object);
ws.sendMessage(object);
if (randomBugsFrequency != 0) {
if (randomBugs.nextDouble() > (1D - randomBugsFrequency)) {
disconnect();
connect(previousUri);
String msg = "I disconnected you, now I'm gonna throw, " +
"deal with it suckah! ;)";
logger.warning(msg);
throw new RuntimeException(msg);
}
}
}
/* -------------------------------- ACCOUNTS -------------------------------- */
public Account accountFromSeed(String masterSeed) {
IKeyPair kp = Seed.fromBase58(masterSeed).keyPair();
return account(AccountID.fromKeyPair(kp), kp);
}
public Account account(final AccountID id, IKeyPair keyPair) {
if (accounts.containsKey(id)) {
return accounts.get(id);
} else {
TrackedAccountRoot accountRoot = accountRoot(id);
Account account = new Account(
id,
keyPair,
accountRoot,
new TransactionManager(this, accountRoot, id, keyPair)
);
accounts.put(id, account);
subscriptions.addAccount(id);
return account;
}
}
private TrackedAccountRoot accountRoot(AccountID id) {
TrackedAccountRoot accountRoot = new TrackedAccountRoot();
requestAccountRoot(id, accountRoot);
return accountRoot;
}
private void requestAccountRoot(final AccountID id,
final TrackedAccountRoot accountRoot) {
makeManagedRequest(Command.ledger_entry, new Manager() {
@Override
public boolean retryOnUnsuccessful(Response r) {
return r == null || r.rpcerr == null || r.rpcerr != RPCErr.entryNotFound;
}
@Override
public void cb(Response response, JSONObject jsonObject) throws JSONException {
if (response.succeeded) {
accountRoot.setFromJSON(jsonObject);
} else {
log(Level.INFO, "Unfunded account: {0}", response.message);
accountRoot.setUnfundedAccount(id);
}
}
}, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
request.json("account_root", id);
}
@Override
public JSONObject buildTypedResponse(Response response) {
return response.result.getJSONObject("node");
}
});
}
/* ------------------------------ SUBSCRIPTIONS ----------------------------- */
private void subscribe(JSONObject subscription) {
Request request = newRequest(Command.subscribe);
request.json(subscription);
request.on(Request.OnSuccess.class, new Request.OnSuccess() {
@Override
public void called(Response response) {
// TODO ... make sure this isn't just an account subscription
serverInfo.update(response.result);
emit(OnSubscribed.class, serverInfo);
}
});
request.request();
}
private JSONObject prepareSubscription() {
subscriptions.pauseEventEmissions();
subscriptions.addStream(SubscriptionManager.Stream.ledger);
subscriptions.addStream(SubscriptionManager.Stream.server);
subscriptions.unpauseEventEmissions();
return subscriptions.allSubscribed();
}
/* ------------------------------ REQUESTS ------------------------------ */
public Request newRequest(Command cmd) {
return new Request(cmd, cmdIDs++, this);
}
public void sendRequest(final Request request) {
Logger reqLog = Request.logger;
try {
requests.put(request.id, request);
request.bumpSendTime();
sendMessage(request.toJSON());
// Better safe than sorry
} catch (Exception e) {
if (reqLog.isLoggable(Level.WARNING)) {
reqLog.log(Level.WARNING, "Exception when trying to request: {0}", e);
}
nextTickOrWhenConnected(new OnConnected() {
@Override
public void called(Client args) {
sendRequest(request);
}
});
}
}
// ### Managed Requests API
public Request makeManagedRequest(final Command cmd, final Manager manager, final Request.Builder builder) {
final Request request = newRequest(cmd);
final boolean[] responded = new boolean[]{false};
request.once(Request.OnTimeout.class, new Request.OnTimeout() {
@Override
public void called(Response args) {
if (!responded[0] && manager.retryOnUnsuccessful(null)) {
logRetry(request, "Request timed out");
request.clearAllListeners();
queueRetry(50, cmd, manager, builder);
}
}
});
final OnDisconnected cb = new OnDisconnected() {
@Override
public void called(Client c) {
if (!responded[0] && manager.retryOnUnsuccessful(null)) {
logRetry(request, "Client disconnected");
request.clearAllListeners();
queueRetry(50, cmd, manager, builder);
}
}
};
once(OnDisconnected.class, cb);
request.once(Request.OnResponse.class, new Request.OnResponse() {
@Override
public void called(final Response response) {
responded[0] = true;
Client.this.removeListener(OnDisconnected.class, cb);
if (response.succeeded) {
final T t = builder.buildTypedResponse(response);
manager.cb(response, t);
} else {
if (manager.retryOnUnsuccessful(response)) {
queueRetry(50, cmd, manager, builder);
} else {
manager.cb(response, null);
}
}
}
});
builder.beforeRequest(request);
manager.beforeRequest(request);
request.request();
return request;
}
private void queueRetry(int ms,
final Command cmd,
final Manager manager,
final Request.Builder builder) {
schedule(ms, new Runnable() {
@Override
public void run() {
makeManagedRequest(cmd, manager, builder);
}
});
}
private void logRetry(Request request, String reason) {
if (logger.isLoggable(Level.WARNING)) {
log(Level.WARNING, previousUri + ": " + reason + ", muting listeners " +
"for " + request.json() + "and trying again");
}
}
// ### Managed Requests
public AccountTxPager accountTxPager(AccountID accountID) {
return new AccountTxPager(this, accountID, null);
}
public void requestLedgerEntry(final Hash256 index, final Number ledger_index, final Manager cb) {
makeManagedRequest(Command.ledger_entry, cb, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
if (ledger_index != null) {
request.json("ledger_index", ledgerIndex(ledger_index));
}
request.json("index", index.toJSON());
}
@Override
public LedgerEntry buildTypedResponse(Response response) {
String node_binary = response.result.optString("node_binary");
STObject node = STObject.translate.fromHex(node_binary);
node.put(Hash256.index, index);
return (LedgerEntry) node;
}
});
}
private Object ledgerIndex(Number ledger_index) {
long l = ledger_index.longValue();
if (l == VALIDATED_LEDGER) {
return "validated";
}
return l;
}
public void requestAccountInfo(final AccountID addy, final Manager manager) {
makeManagedRequest(Command.account_info, manager, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
request.json("account", addy);
}
@Override
public AccountRoot buildTypedResponse(Response response) {
JSONObject root = response.result.optJSONObject("account_data");
return (AccountRoot) STObject.fromJSONObject(root);
}
});
}
public void requestLedgerData(final long ledger_index, final Manager> manager) {
makeManagedRequest(Command.ledger_data, manager, new Request.Builder>() {
@Override
public void beforeRequest(Request request) {
request.json("ledger_index", ledger_index);
request.json("binary", true);
}
@Override
public ArrayList buildTypedResponse(Response response) {
JSONArray state = response.result.getJSONArray("state");
ArrayList result = new ArrayList();
for (int i = 0; i < state.length(); i++) {
JSONObject stateObject = state.getJSONObject(i);
LedgerEntry le = (LedgerEntry) STObject.fromHex(stateObject.getString("data"));
le.index(Hash256.fromHex(stateObject.getString("index")));
result.add(le);
}
return result;
}
});
}
public void requestAccountLines(final AccountID addy, final Manager> manager) {
makeManagedRequest(Command.account_lines, manager, new Request.Builder>() {
@Override
public void beforeRequest(Request request) {
request.json("account", addy);
}
@Override
public ArrayList buildTypedResponse(Response response) {
ArrayList lines = new ArrayList();
JSONArray array = response.result.optJSONArray("lines");
for (int i = 0; i < array.length(); i++) {
JSONObject line = array.optJSONObject(i);
lines.add(AccountLine.fromJSON(addy, line));
}
return lines;
}
});
}
public void requestHostID(final Callback callback) {
makeManagedRequest(Command.server_info, new Manager() {
@Override
public void cb(Response response, String hostid) throws JSONException {
callback.called(hostid);
}
@Override
public boolean retryOnUnsuccessful(Response r) {
return true;
}
}, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
}
@Override
public String buildTypedResponse(Response response) {
JSONObject info = response.result.getJSONObject("info");
return info.getString("hostid");
}
});
}
public void requestBookOffers(final Number ledger_index,
final Issue get,
final Issue pay,
final Manager> cb) {
makeManagedRequest(Command.book_offers, cb, new Request.Builder>() {
@Override
public void beforeRequest(Request request) {
request.json("taker_gets", get.toJSON());
request.json("taker_pays", pay.toJSON());
if (ledger_index != null) {
request.json("ledger_index", ledger_index);
}
}
@Override
public ArrayList buildTypedResponse(Response response) {
ArrayList offers = new ArrayList();
JSONArray offersJson = response.result.getJSONArray("offers");
for (int i = 0; i < offersJson.length(); i++) {
JSONObject jsonObject = offersJson.getJSONObject(i);
STObject object = STObject.fromJSONObject(jsonObject);
offers.add((Offer) object);
}
return offers;
}
});
}
public void requestLedger(final Number ledger_index, final Manager cb) {
makeManagedRequest(Command.ledger, cb, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
request.json("ledger_index", ledgerIndex(ledger_index));
}
@Override
public JSONObject buildTypedResponse(Response response) {
return response.result.optJSONObject("ledger");
}
});
}
public void requestTransaction(final Hash256 hash, final Manager cb) {
makeManagedRequest(Command.tx, cb, new Request.Builder() {
@Override
public void beforeRequest(Request request) {
request.json("binary", true);
request.json("transaction", hash);
}
@Override
public TransactionResult buildTypedResponse(Response response) {
return new TransactionResult(response.result, TransactionResult.Source.request_tx_binary);
}
});
}
// TODO: some nice way of using lambdas for the common case
// sending null, is one way of indicating `some` kind of error
// but that's kind of lame.
@Deprecated // before it even got started
public void requestTransaction(final Hash256 hash, final Callback cb) {
requestTransaction(hash, new Manager() {
@Override
public void cb(Response response, TransactionResult transactionResult) throws JSONException {
if (response.succeeded) {
cb.called(transactionResult);
} else {
throw new RuntimeException("Failed" + response.message);
}
}
});
}
// ### Request builders
// These all return Request
public Request submit(String tx_blob, boolean fail_hard) {
Request req = newRequest(Command.submit);
req.json("tx_blob", tx_blob);
req.json("fail_hard", fail_hard);
return req;
}
public Request accountLines(AccountID account) {
Request req = newRequest(Command.account_lines);
req.json("account", account.address);
return req;
}
public Request accountInfo(AccountID account) {
Request req = newRequest(Command.account_info);
req.json("account", account.address);
return req;
}
public Request ping() {
return newRequest(Command.ping);
}
public Request subscribeAccount(AccountID... accounts) {
Request request = newRequest(Command.subscribe);
JSONArray accounts_arr = new JSONArray();
for (AccountID acc : accounts) {
accounts_arr.put(acc);
}
request.json("accounts", accounts_arr);
return request;
}
public Request subscribeBookOffers(Issue get, Issue pay) {
Request request = newRequest(Command.subscribe);
JSONObject book = new JSONObject();
JSONArray books = new JSONArray(new Object[] { book });
book.put("snapshot", true);
book.put("taker_gets", get.toJSON());
book.put("taker_pays", pay.toJSON());
request.json("books", books);
return request;
}
public Request requestBookOffers(Issue get, Issue pay) {
Request request = newRequest(Command.book_offers);
request.json("taker_gets", get.toJSON());
request.json("taker_pays", pay.toJSON());
return request;
}
}