tangem-app-android-audited/app/src/main/java/com/tangem/wallet/CoinEngine.java
2020-03-19 15:42:48 +03:00

394 lines
No EOL
13 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.tangem.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.data.Blockchain;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.tasks.SignTask;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import co.nstant.in.cbor.CborException;
/**
* Created by Ilia on 15.02.2018.
*/
public abstract class CoinEngine {
public static class InternalAmount extends BigDecimal {
private String currency;
public InternalAmount() {
super(0);
currency = "";
}
public InternalAmount(String amountString, String currency) {
super(amountString.replace(',', '.'));
this.currency = currency;
}
public InternalAmount(long amount, String currency) {
super(amount);
this.currency = currency;
}
public InternalAmount(BigDecimal amount, String currency) {
super(amount.unscaledValue(), amount.scale());
this.currency = currency;
}
public InternalAmount(BigInteger amount, String currency) {
super(new BigDecimal(amount).unscaledValue(), new BigDecimal(amount).scale());
this.currency = currency;
}
public boolean notZero() {
return compareTo(BigDecimal.ZERO) > 0;
}
public boolean isZero() {
return compareTo(BigDecimal.ZERO) == 0;
}
public String getCurrency() {
return currency;
}
public String toValueString(int decimals) {
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
symbols.setDecimalSeparator('.');
DecimalFormat df = new DecimalFormat();
df.setDecimalFormatSymbols(symbols);
df.setMaximumFractionDigits(decimals);
df.setMinimumFractionDigits(0);
df.setGroupingUsed(false);
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
bd.setScale(decimals, ROUND_DOWN);
return df.format(bd);
}
public String toValueString() {
return toValueString(scale());
}
@Override
public String toString() {
return super.toString();
}
}
public static class Amount extends BigDecimal {
private String currency;
public Amount() {
super(0);
currency = "";
}
public Amount(String amountString, String currency) {
super(amountString.replace(',', '.'));
this.currency = currency;
}
public Amount(Long amount, String currency) {
super(amount);
this.currency = currency;
}
public Amount(BigDecimal amount, String currency) {
super(amount.unscaledValue(), amount.scale());
this.currency = currency;
}
public String getCurrency() {
return currency;
}
@Override
public String toString() {
return super.toString() + " " + currency;
}
public boolean notZero() {
return compareTo(BigDecimal.ZERO) > 0;
}
public String toDescriptionString(int decimals) {
return toValueString(decimals) + " " + currency;
}
public String toValueString(int decimals) {
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
symbols.setDecimalSeparator('.');
DecimalFormat df = new DecimalFormat();
df.setDecimalFormatSymbols(symbols);
df.setMaximumFractionDigits(decimals);
df.setMinimumFractionDigits(0);
df.setGroupingUsed(false);
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
bd.setScale(decimals, ROUND_DOWN);
return df.format(bd);
}
public String toValueString() {
return toValueString(scale());
}
public String toEquivalentString(double rateValue) {
if (rateValue > 0) {
BigDecimal biRate = new BigDecimal(rateValue);
BigDecimal exchangeCurs = biRate.multiply(this);
exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN);
return "USD " + exchangeCurs.toString();
} else {
return "";
}
}
public boolean isZero() {
return compareTo(BigDecimal.ZERO) == 0;
}
@Override
public Amount setScale(int newScale) {
return new Amount(super.setScale(newScale), currency);
}
}
protected TangemContext ctx;
public CoinEngine() {
}
public CoinEngine(TangemContext ctx) {
this.ctx = ctx;
}
public abstract boolean awaitingConfirmation();
public abstract boolean hasBalanceInfo();
public abstract boolean isBalanceNotZero();
// TODO - return string message
public abstract boolean isExtractPossible();
public abstract Uri getWalletExplorerUri();
public abstract Uri getShareWalletUri();
public Uri getShareWalletUriEx(){
if (ctx.getBlockchain() == Blockchain.BitcoinCash)
return getShareWalletUri();
else
return Uri.parse(ctx.getBlockchain().name().toLowerCase() + ":" + getShareWalletUri().toString());
}
public abstract boolean checkNewTransactionAmount(Amount amount);
public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded);
public abstract boolean validateBalance(BalanceValidator balanceValidator);
public abstract Amount getBalance();
public abstract String getBalanceHTML();
public abstract String getBalanceCurrency();
public abstract InputFilter[] getAmountInputFilters();
public abstract String evaluateFeeEquivalent(String fee);
public abstract String getFeeCurrency();
public abstract boolean isNeedCheckNode();
public abstract String getBalanceEquivalent();
public abstract boolean validateAddress(String address);
public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException, Exception;
public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception;
public abstract Amount convertToAmount(String strAmount, String currency);
public abstract InternalAmount convertToInternalAmount(Amount amount) throws Exception;
public abstract InternalAmount convertToInternalAmount(byte[] bytes) throws Exception;
public abstract byte[] convertToByteArray(InternalAmount internalAmount) throws Exception;
public abstract CoinData createCoinData();
public abstract String getUnspentInputsDescription();
public String getOfflineBalanceHTML() {
return "";
}
// public String getOfflineBalanceHTML() {
// InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
// Amount offlineAmount = convertToAmount(offlineInternalAmount);
// return offlineAmount.toDescriptionString(getDecimals());
// }
public void defineWallet() throws CardProtocol.TangemException {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKey());
ctx.getCoinData().setWallet(wallet);
} catch (Exception e) {
ctx.getCoinData().setWallet("ERROR");
throw new CardProtocol.TangemException("Can't define wallet address");
}
}
/**
* Create instance of {@link SignTask.TransactionToSign} used for transaction signing and sending
*
* Transaction processing sequence:
* 1. User enter transaction attributes
* 2. Application create instance of {@link SignTask.TransactionToSign} by call {@see constructTransaction}
* 3. Application set notification when transaction were prepared {@see setOnNeedSendTransaction} and init {@link SignTask}
* 4. User tap card and card sign transaction
* 5. Application receive {@link OnNeedSendTransaction} notification with prepared raw transaction
* 6. Application show user information that transaction ready for sending and init sending procedure by call {@see requestSendTransaction}
* 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user
*
* @param amountValue - amount of desired transaction
* @param feeValue - fee amount of desired transaction
* @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction)
* @param targetAddress - target address of transaction
* @return instance of {@link SignTask.TransactionToSign}
* @throws Exception if something goes wrong
*/
public abstract SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception;
/**
* Interface used to notify main application when new transaction is prepared to send
*/
public interface OnNeedSendTransaction {
void onTransactionPrepared(byte[] txForSend);
}
protected OnNeedSendTransaction onNeedSendTransaction;
/**
* Set notification callback when new transaction is prepared to send
*/
public void setOnNeedSendTransaction(OnNeedSendTransaction onNeedSendTransaction) {
this.onNeedSendTransaction = onNeedSendTransaction;
}
protected void notifyOnNeedSendTransaction(byte[] txForSend) throws Exception {
if (onNeedSendTransaction == null)
throw new Exception("Transaction was signed but no callback defined to send!");
onNeedSendTransaction.onTransactionPrepared(txForSend);
}
/**
* Interface used to notify/querying application during processing sequence of request to blockchain nodes/servers
*/
public interface BlockchainRequestsCallbacks {
/**
* Notification that the all requests in sequence completed
* Call after a last request completed
* If occurred error return in {@link TangemContext} {@see TangemContext.getError()}
*
* @param success -*
*/
void onComplete(Boolean success);
/**
* Notification that a new part of data received and it's possible to update view
* May call when some request in the sequence completed but there are still a few requests left
*/
void onProgress();
/**
* Return flag that allow to add new or re-requests in the sequence
* Call between requests or when request fail and before re-request
*
* @return true if not need terminate (e.g. activity is online)
*/
boolean allowAdvance();
}
/**
* Start sequence of request to blockchain nodes needed to get balance and other information (for example unspent transaction) needed to
* show current state of wallet and prepare new withdrawal transaction
* Save result in {@link CoinData}
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception;
/**
* Start sequence of request to blockchain nodes needed to get fee amount for a new transaction
* Save result in {@link CoinData} minFee, maxFee, normalFee
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception;
/**
* Start sequence of request to blockchain nodes needed to send new transaction
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception;
/**
* @return true if blockchain need multiple lines to show balance (e.g. need show additional balance information, Token count for example)
*/
public boolean needMultipleLinesForBalance() {
return false;
}
/**
* @return true to allow user select fee level - min, normal or priority
*/
public boolean allowSelectFeeLevel() {
return true;
}
/**
* @return true to allow user select include or exclude fee
*/
public boolean allowSelectFeeInclusion() {
return true;
}
public boolean isNftToken() {
return false;
}
public int pendingTransactionTimeoutInSeconds() { return 30; }
}