/**
* Copyright 2008
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* @project loonframework
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @version 0.1
*/
package org.ripple.power.txns;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import org.ripple.power.RippleSeedAddress;
import org.ripple.power.command.AMacros;
import org.ripple.power.command.IScriptLog;
import org.ripple.power.command.DMacros;
import org.ripple.power.config.LSystem;
import org.ripple.power.utils.StringUtils;
import org.ripple.power.wallet.WalletCache;
import com.ripple.client.enums.Command;
import com.ripple.core.types.known.sle.entries.Offer;
public class RippleMacros extends AMacros {
final AccountFind find = new AccountFind();
private String address = null;
private final int PING = 0;
private final int SERVER_INFO = 1;
private final int SERVER_STATE = 2;
private final int ACCOUNT_INFO = 3;
private final int ACCOUNT_LINES = 4;
private final int ACCOUNT_OFFERS = 5;
private final int ACCOUNT_TX = 6;
private final int TRANSACTION_ENTRY = 7;
private final int TRANSACTION_TX = 8;
private final int TRANSACTION_HISTORY = 9;
private final int SEND = 10;
private final int OFFER_CREATE = 11;
private final int OFFER_CANCEL = 12;
private final int OFFER_PRICE = 13;
private final int CONVERT_PRICE = 14;
public RippleMacros() {
super("ripple.",
new String[] { "ping", "server_info", "server_state",
"account_info", "account_lines", "account_offers",
"account_tx", "transaction_entry", "tx", "tx_history",
"send", "offer_create", "offer_cancel", "offer_price",
"convert_price" });
}
@Override
public void call(final IScriptLog log, final int scriptLine,
final DMacros macros, final String message) {
setConfig(log, macros, scriptLine);
List<String> list = DMacros.commandSplit(message, false);
int size = list.size();
if (size > 0) {
JSONObject obj = new JSONObject();
final String cmd = list.get(0);
final int type = lookupCommand(cmd);
// 视为同种交易操作
if (type == SEND || type == OFFER_CREATE) {
if (size > 3) {
String curOne = list.get(1);
String curTwo = list.get(2);
int start = curOne.indexOf("/");
String secret = null;
if (start != -1) {
secret = curOne.substring(0, start);
secret = getSecret(secret);
}
if (secret == null) {
return;
}
if (size == 3) {
send(type, curOne, curTwo, secret, LSystem.getFee());
} else if (size == 4) {
String fee = list.get(3);
if (StringUtils.isNumber(fee)) {
send(type, curOne, curTwo, secret, fee);
} else {
error(new Exception(
"Transaction fees must be in digital format"));
}
}
}
return;
} else if (type == OFFER_CANCEL) {
if (size > 3) {
String secret = list.get(1);
secret = getSecret(secret);
if (secret == null) {
return;
}
long offerSequence = 0;
try {
offerSequence = Long.parseLong(list.get(2));
} catch (Exception ex) {
error(ex);
return;
}
String fee = LSystem.getFee();
if (size == 4) {
fee = list.get(3);
}
if (!StringUtils.isNumber(fee)) {
error(new Exception(
"Transaction fees must be in digital format"));
}
setSyncing(type, true);
OfferCancel.set(new RippleSeedAddress(secret),
offerSequence, fee, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
}
return;
} else if (type == OFFER_PRICE) {
if (size == 4) {
String address = list.get(1);
final String seller = list.get(2);
final String buyer = list.get(3);
address = getAddress(address);
if (address == null) {
return;
}
setSyncing(type, true);
OfferPrice.load(address, seller, buyer, new OfferPrice() {
@Override
public void sell(Offer offer) {
}
@Override
public void error(JSONObject obj) {
setSyncing(type, false);
}
@Override
public void empty() {
setSyncing(type, false);
}
@Override
public void complete(ArrayList<OfferFruit> buys,
ArrayList<OfferFruit> sells, OfferPrice price) {
log(type, String.format(
"1/%s high_buy:%s high_sell:%s spread:%s",
seller, price.highBuy, price.highSell,
price.spread));
setVar(type, "highbuy", price.highBuy);
setVar(type, "highsell", price.highSell);
setVar(type, "spread", price.spread);
setVar(type, "highbuy_value", Double
.parseDouble(price.highBuy.split("/")[0]));
setVar(type, "highsell_value", Double
.parseDouble(price.highSell.split("/")[0]));
setVar(type, "spread_value", Double
.parseDouble(price.spread.split("/")[0]));
setVar(type, "buys", buys);
setVar(type, "sells", sells);
setSyncing(type, false);
}
@Override
public void buy(Offer offer) {
}
}, false);
}
return;
} else if (type == CONVERT_PRICE) {
if (size == 4) {
setSyncing(type, true);
String amount = list.get(1);
if (StringUtils.isNumber(amount)) {
String cur1 = list.get(2);
String cur2 = list.get(3);
String result = OfferPrice.getMoneyConvert(amount,
cur1, cur2);
if (StringUtils.isNumber(result)) {
setVar(type, Double.parseDouble(result));
} else {
setVar(type, result);
}
log(type,
String.format("%s/%s == %s/%s", amount,
cur1.toUpperCase(), result,
cur2.toUpperCase()));
} else {
error(new Exception("Invalid Conversion Amount"));
}
setSyncing(type, false);
}
return;
}
switch (size) {
case 1:
setSyncing(type, true);
switch (type) {
case PING:
RippleCommand.get(Command.ping, obj, new Rollback() {
@Override
public void success(JSONObject res) {
setBaseInfo(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case SERVER_INFO:
RippleCommand.get(Command.server_info, obj, new Rollback() {
@Override
public void success(JSONObject res) {
setBaseInfo(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case SERVER_STATE:
RippleCommand.get(Command.server_state, obj,
new Rollback() {
@Override
public void success(JSONObject res) {
setBaseInfo(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
}
break;
case 2:
if (type == TRANSACTION_TX) {
setSyncing(type, true);
String parameter = list.get(1);
if (!AccountFind.is256hash(parameter)) {
error(new Exception(String.format("%s Not 256 Hash",
parameter)));
break;
}
obj.put("transaction", parameter);
RippleCommand.get(Command.tx, obj, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
} else if (type == TRANSACTION_HISTORY) {
setSyncing(type, true);
String parameter = list.get(1);
obj.put("start", Long.parseLong(parameter));
RippleCommand.get(Command.tx_history, obj, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
}
if (!checkAddress(list, 1)) {
break;
}
setSyncing(type, true);
switch (type) {
case ACCOUNT_INFO:
find.info(address, new Rollback() {
@Override
public void success(JSONObject res) {
setAccountInfo(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case ACCOUNT_LINES:
find.lines(address, new Rollback() {
@Override
public void success(JSONObject res) {
setAccountLine(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case ACCOUNT_OFFERS:
find.offer(address, new Rollback() {
@Override
public void success(JSONObject res) {
setAccountOffer(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case ACCOUNT_TX:
find.tx(address, -1,-1, 20, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
}
break;
case 3:
String parameter1 = list.get(1);
String parameter2 = list.get(2);
if (!AccountFind.is256hash(parameter1)) {
error(new Exception(String.format("%s Not 256 Hash",
parameter1)));
break;
}
setSyncing(type, true);
switch (type) {
case TRANSACTION_ENTRY:
obj.put("tx_hash", parameter1);
obj.put("ledger_index", Long.parseLong(parameter2));
RippleCommand.get(Command.transaction_entry, obj,
new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
case 2:
obj.put("tx_hash", parameter1);
obj.put("ledger_index", Long.parseLong(parameter2));
RippleCommand.get(Command.tx_history, obj, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
break;
}
break;
default:
break;
}
}
}
protected void send(final int type, final String curOne,
final String curTwo, final String secret, final String fee) {
Object sendSrc = getCurrency(curOne);
Object sendDst = getCurrency(curTwo);
if (sendSrc != null && sendDst != null && !sendSrc.equals(sendDst)) {
if (sendSrc instanceof IssuedCurrency && sendDst instanceof String) {
setSyncing(type, true);
Payment.send(secret, (String) sendDst,
((IssuedCurrency) sendSrc), fee, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
// 视为挂单
} else if (sendSrc instanceof IssuedCurrency
&& sendDst instanceof IssuedCurrency) {
setSyncing(type, true);
OfferCreate.set(new RippleSeedAddress(secret),
((IssuedCurrency) sendSrc), ((IssuedCurrency) sendDst),
fee, new Rollback() {
@Override
public void success(JSONObject res) {
setVar(type, res);
log(type, res);
setSyncing(type, false);
}
@Override
public void error(JSONObject res) {
log(type, res);
setSyncing(type, false);
}
});
}
} else {
error(new Exception("Invalid Send command"));
}
}
protected Object getCurrency(String str) {
IssuedCurrency currency = null;
int start = str.indexOf("/");
if (start != -1) {
String cur = str.substring(start + 1, str.length());
if (cur.indexOf(LSystem.nativeCurrency) != -1) {
currency = new IssuedCurrency(cur, true);
} else {
String[] split = StringUtils.split(cur, "/");
if (split.length == 3) {
String v = split[2];
String addr = getAddress(v);
if (addr == null) {
if (Gateway.getAddress(v) != null) {
ArrayList<Gateway.Item> items = Gateway
.getAddress(v).accounts;
if (items.size() > 0) {
addr = items.get(0).address;
}
}
if (addr == null) {
return null;
}
StringBuffer sbr = new StringBuffer();
for (int i = 0; i < split.length - 1; i++) {
sbr.append(split[i]);
sbr.append('/');
}
sbr.append(addr);
cur = sbr.toString();
}
currency = new IssuedCurrency(cur);
} else {
error(new Exception(String.format("%s is invalid format",
cur)));
}
}
} else {
if (AccountFind.isRippleAddress(str)) {
return str;
} else {
str = getAddress(str);
if (str == null) {
error(new Exception(String.format("%s is invalid format",
str)));
}
}
}
return currency;
}
protected String getSecret(final String name) {
String result = null;
if (AccountFind.isRippleSecret(name)) {
return name;
} else {
result = getAddress(name);
if (result == null) {
return null;
}
}
result = WalletCache.get().findSecret(result);
if (result == null) {
error(new Exception(String.format("%s is an invalid secret", name)));
}
return result;
}
protected String getAddress(String name) {
if (!AccountFind.isRippleAddress(name)) {
if (!name.startsWith("~")) {
name = "~" + name;
}
try {
name = NameFind.getAddress(name);
} catch (Exception e) {
}
if (name == null || !AccountFind.isRippleAddress(name)) {
error(new Exception(String.format("%s is an invalid address",
name)));
}
return name;
} else {
return name;
}
}
private boolean checkAddress(List<String> list, int idx) {
return (address = getAddress(list.get(idx))) != null;
}
private void setAccountOffer(int type, JSONObject obj) {
try {
if (obj.has("result")) {
JSONObject result = obj.getJSONObject("result");
String name = "offers";
if (result.has(name)) {
JSONArray lines = result.getJSONArray(name);
setVar(type, "offers.size", lines.length());
for (int i = 0; i < lines.length(); i++) {
JSONObject line = lines.getJSONObject(i);
setJsonArrayVar(type, line, name, i, "taker_pays", true);
setJsonArrayVar(type, line, name, i, "flags");
setJsonArrayVar(type, line, name, i, "taker_gets", true);
setJsonArrayVar(type, line, name, i, "seq");
}
}
}
} catch (Exception ex) {
error(ex);
}
}
private void setAccountLine(int type, JSONObject obj) {
try {
if (obj.has("result")) {
JSONObject result = obj.getJSONObject("result");
setJsonVar(type, result, "account");
String name = "lines";
if (result.has(name)) {
JSONArray lines = result.getJSONArray(name);
setVar(type, "lines.size", lines.length());
for (int i = 0; i < lines.length(); i++) {
JSONObject line = lines.getJSONObject(i);
setJsonArrayVar(type, line, name, i, "limit");
setJsonArrayVar(type, line, name, i, "balance");
setJsonArrayVar(type, line, name, i, "quality_out");
setJsonArrayVar(type, line, name, i, "account");
setJsonArrayVar(type, line, name, i, "quality_in");
setJsonArrayVar(type, line, name, i, "limit_peer");
setJsonArrayVar(type, line, name, i, "currency");
}
}
}
} catch (Exception ex) {
error(ex);
}
}
private void setAccountInfo(int type, JSONObject obj) {
try {
if (obj.has("result")) {
JSONObject result = obj.getJSONObject("result");
setJsonVar(type, result, "ledger_current_index");
if (result.has("account_data")) {
JSONObject account_data = result
.getJSONObject("account_data");
setJsonVar(type, account_data, "LedgerEntryType");
setJsonVar(type, account_data, "AccountRoot");
setJsonVar(type, account_data, "index");
setJsonVar(type, account_data, "Domain");
setJsonVar(type, account_data, "PreviousTxnID");
setJsonVar(type, account_data, "PreviousTxnLgrSeq");
setJsonVar(type, account_data, "OwnerCount");
setJsonVar(type, account_data, "Flags");
setJsonVar(type, account_data, "Sequence");
setJsonVar(type, account_data, "Balance");
}
}
} catch (Exception ex) {
error(ex);
}
}
private void setBaseInfo(int type, JSONObject obj) {
setJsonVar(type, obj, "id");
setJsonVar(type, obj, "status");
setJsonVar(type, obj, "type");
if (obj.has("result")) {
JSONObject result = obj.getJSONObject("result");
if (result.has("info")) {
JSONObject info = result.getJSONObject("info");
setJsonVar(type, info, "hostid");
setJsonVar(type, info, "server_state");
setJsonVar(type, info, "load_factor");
setJsonVar(type, info, "build_version");
setJsonVar(type, info, "validation_quorum");
setJsonVar(type, info, "io_latency_ms");
setJsonVar(type, info, "load_factor");
if (info.has("validated_ledger")) {
JSONObject validated_ledger = info
.getJSONObject("validated_ledger");
setJsonVar(type, validated_ledger, "base_fee_xrp");
setJsonVar(type, validated_ledger, "age");
setJsonVar(type, validated_ledger, "reserve_base_xrp");
setJsonVar(type, validated_ledger, "reserve_inc_xrp");
setJsonVar(type, validated_ledger, "seq");
}
setJsonVar(type, info, "validated_ledger");
setJsonVar(type, info, "pubkey_node");
}
if (result.has("state")) {
JSONObject state = result.getJSONObject("state");
setJsonVar(type, state, "build_version");
setJsonVar(type, state, "io_latency_ms");
setJsonVar(type, state, "validation_quorum");
setJsonVar(type, state, "load_base");
setJsonVar(type, state, "peers");
setJsonVar(type, state, "pubkey_node");
setJsonVar(type, state, "server_state");
if (state.has("validated_ledger")) {
JSONObject validated_ledger = state
.getJSONObject("validated_ledger");
setJsonVar(type, validated_ledger, "base_fee");
setJsonVar(type, validated_ledger, "close_time");
setJsonVar(type, validated_ledger, "reserve_base");
setJsonVar(type, validated_ledger, "reserve_inc");
setJsonVar(type, validated_ledger, "seq");
}
setJsonVar(type, state, "validation_quorum");
}
}
}
}