package com.greenaddress.greenbits.ui; import com.greenaddress.greenapi.GATx; import com.greenaddress.greenapi.JSONMap; import com.greenaddress.greenbits.GaService; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Sha256Hash; import java.io.Serializable; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; public class TransactionItem implements Serializable { public enum TYPE { OUT, IN, REDEPOSIT } public final TYPE type; private final int currentBlock; private final Integer blockHeight; public final long amount; public final String counterparty; public final String receivedOn; public final JSONMap receivedOnEp; public final boolean instant; public final boolean replaceable; public final Sha256Hash txHash; public final String doubleSpentBy; public final Date date; public String memo; public boolean spvVerified; public final boolean isSpent; public final long fee; public final int size; public final List<Sha256Hash> replacedHashes; public final String data; public final List<JSONMap> eps; public String toString() { return String.format("%s %s %s %s", date.toString(), type.name(), amount, counterparty); } public int getConfirmations() { if (blockHeight != null) return currentBlock - blockHeight + 1; return 0; } public boolean hasEnoughConfirmations() { return getConfirmations() >= 6; } public TransactionItem(final GaService service, final JSONMap m, final int currentBlock) throws ParseException { instant = m.getBool("instant"); doubleSpentBy = m.get("double_spent_by"); this.currentBlock = currentBlock; fee = m.getLong("fee"); size = m.get("size"); replacedHashes = new ArrayList<>(); data = m.get("data"); txHash = m.getHash("txhash"); memo = m.get("memo", null); blockHeight = m.get("block_height", null); final List<JSONMap> recipients = new ArrayList<>(); String tmpCounterparty = null; long tmpAmount = 0; boolean tmpIsSpent = true; String tmpReceivedOn = null; JSONMap tmpReceivedOnEp = null; boolean hasConfidentialRecipients = false; eps = m.get("eps"); for (final JSONMap ep : eps) { final String socialDestination = ep.get("social_destination", null); boolean externalSocial = false; if (socialDestination != null) { final Integer scriptType = ep.get("script_type"); externalSocial = scriptType != GATx.P2SH_FORTIFIED_OUT && scriptType != GATx.P2SH_P2WSH_FORTIFIED_OUT; final JSONMap socialMap = m.getMap("social_destination"); if (socialMap == null) { // Old unconverted social_destination string value tmpCounterparty = socialDestination; } else { // New style JSON map of social info final String socialType = socialMap.get("type"); if (socialType.equals("voucher")) tmpCounterparty = "Voucher"; else tmpCounterparty = socialMap.get("name"); } } final Boolean isRelevant = ep.get("is_relevant"); final Boolean isCredit = ep.get("is_credit"); final boolean confidential; confidential = ep.getBool("confidential") || // Confidential own output ep.get("value") == null; // Confidential foreign output if (isCredit && (!isRelevant || socialDestination != null) && ep.get("ad") != null) { // Elements fees if (confidential) hasConfidentialRecipients = true; else recipients.add(ep); } if (!isRelevant) continue; if (!isCredit) { if (ep.get("value") != null) tmpAmount -= ep.getLong("value"); continue; } if (!externalSocial) { if (ep.get("value") != null) tmpAmount += ep.getLong("value"); if (!ep.getBool("is_spent")) tmpIsSpent = false; } if (tmpReceivedOn != null) tmpReceivedOn += ", " + ep.get("ad"); else { tmpReceivedOn = ep.get("ad"); if (ep.getBool("confidential")) tmpReceivedOnEp = ep; // Needed for regenerating the confidential address } } if (tmpAmount >= 0) { type = TransactionItem.TYPE.IN; for (final JSONMap ep : eps) if (!ep.getBool("is_credit")) { final String socialSource = ep.get("social_source"); if (socialSource != null) tmpCounterparty = socialSource; } } else { tmpReceivedOn = null; // don't show change addresses if (recipients.isEmpty() && !hasConfidentialRecipients) type = TransactionItem.TYPE.REDEPOSIT; else { type = TransactionItem.TYPE.OUT; if (tmpCounterparty == null) { if (!recipients.isEmpty()) tmpCounterparty = recipients.get(0).get("ad"); else if (hasConfidentialRecipients) tmpCounterparty = "Confidential address"; } if (recipients.size() > 1) tmpCounterparty += ", ..."; } } amount = tmpAmount; counterparty = tmpCounterparty; isSpent = tmpIsSpent; receivedOn = tmpReceivedOn; receivedOnEp = tmpReceivedOnEp; spvVerified = service.isSPVVerified(txHash); date = m.getDate("created_at"); // FIXME: Implement RBF for instant transactions replaceable = !GaService.IS_ELEMENTS && !instant && m.getBool("rbf_optin") && type != TransactionItem.TYPE.IN; } final Coin getFeePerKilobyte() { if (size <= 0) return Coin.ZERO; final double perKb = fee * 1000.0 / size; return Coin.valueOf((long) Math.ceil(perKb)); } }