Updated on 2026-08-14

This commit is contained in:
Tangem 2018-10-26 10:59:36 +03:00
parent 61ef397705
commit bae8bd0e6c
30 changed files with 1686 additions and 1276 deletions

View file

@ -12,6 +12,7 @@ import com.tangem.domain.wallet.Issuer;
import com.tangem.domain.wallet.LocalStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.Manufacturer;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.util.Util;
import org.spongycastle.jce.ECNamedCurveTable;
@ -584,8 +585,9 @@ public class CardProtocol {
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
CoinEngine engineCoin = CoinEngineFactory.create(mCard.getBlockchain());
String wallet = engineCoin.calculateAddress(mCard, pkUncompressed);
TangemContext ctx=new TangemContext(mCard);
CoinEngine engineCoin = CoinEngineFactory.create(ctx);
String wallet = engineCoin.calculateAddress(pkUncompressed);
mCard.setWallet(wallet);
//mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed));

View file

@ -13,6 +13,10 @@ public class BalanceValidator {
return firstLine;
}
public void setFirstLine(String value) {
firstLine=value;
}
public String getSecondLine(Boolean recommend) {
if (!recommend) return secondLine;
if (score > 89) {
@ -26,6 +30,14 @@ public class BalanceValidator {
}
}
public void setSecondLine(String value) {
secondLine=value;
}
public void setScore(int score) {
this.score = score;
}
public int getColor() {
if (score > 89) {
return R.color.confirmed;
@ -38,73 +50,14 @@ public class BalanceValidator {
}
}
public void Check(TangemCard card, Boolean attest) {
public void Check(TangemContext ctx, Boolean attest) {
firstLine = "Verification failed";
secondLine = "";
TangemCard card=ctx.getCard();
CoinEngine engine=CoinEngineFactory.create(ctx);
if (card.getBlockchain() == Blockchain.Bitcoin || card.getBlockchain() == Blockchain.BitcoinTestNet) {
if (((card.getOfflineBalance() == null) && !card.isBalanceReceived()) || (!card.isBalanceReceived() && (card.getRemainingSignatures() != card.getMaxSignatures()))) {
score = 0;
firstLine = "Unknown balance";
secondLine = "Balance cannot be verified. Swipe down to refresh.";
return;
}
// Workaround before new back-end
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
// firstLine = "Verified balance";
// secondLine = "Balance confirmed in blockchain. ";
// secondLine += "Verified note identity. ";
// return;
// }
if (card.getBalanceUnconfirmed() != 0) {
score = 0;
firstLine = "Transaction in progress";
secondLine = "Wait for confirmation in blockchain";
return;
}
if (card.isBalanceReceived() && card.isBalanceEqual()) {
score = 100;
firstLine = "Verified balance";
secondLine = "Balance confirmed in blockchain";
if (card.getBalance() == 0) {
firstLine = "Empty wallet";
secondLine = "";
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
// {
// score = 80;
// firstLine = "Unguaranteed balance";
// secondLine = "Potential unsent transaction. Redeem immediately if accept. ";
// return;
// }
if ((card.getOfflineBalance() != null) && !card.isBalanceReceived() && (card.getRemainingSignatures() == card.getMaxSignatures()) && card.getBalance() != 0) {
score = 80;
firstLine = "Verified offline balance";
secondLine = "Can't obtain balance from blockchain. Restore internet connection to be more confident. ";
}
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
// secondLine += "Not all nodes have returned balance. Swipe down or tap again. ";
// if(score <= 0)
// return;
// }
//
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
// score = 0;
// firstLine = "Disputed balance";
// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later.";
// return;
// }
if (ctx.getBlockchain() == Blockchain.Bitcoin || ctx.getBlockchain() == Blockchain.BitcoinTestNet) {
if( !engine.validateBalance(this) ) return;
} else if ((card.getBlockchain() == Blockchain.Ethereum) || (card.getBlockchain() == Blockchain.Token)) {
if (card.getBalance() == null) {

View file

@ -84,7 +84,7 @@ public enum Blockchain {
return resourceId;
}
public int getLogoImageResource(String blockchainID, String symbolName) {
public static int getLogoImageResource(String blockchainID, String symbolName) {
switch (blockchainID) {
case "BTC":
return R.drawable.ic_logo_bitcoin;

View file

@ -1,105 +1,116 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.FormatUtil;
import com.tangem.util.Util;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.tangem.util.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class BtcCashEngine extends CoinEngine {
public String getNode(TangemCard mCard) {
return "35.157.238.5";
public BtcData btcData = null;
public BtcCashEngine(TangemContext context) throws Exception {
super(context);
if (context.getCoinData() == null) {
btcData = new BtcData();
context.setCoinData(btcData);
} else if (context.getCoinData() instanceof BtcData) {
btcData = (BtcData) context.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for BtcEngine");
}
}
public int getNodePort(TangemCard mCard) {
return 51001;
public BtcCashEngine() {
}
public void switchNode(TangemCard mCard) {
private void checkBlockchainDataExists() throws Exception {
if (btcData == null) throw new Exception("No blockchain data");
}
public boolean inOutPutVisible() {
@Override
public boolean awaitingConfirmation(){
if( btcData==null ) return false;
return btcData.getBalanceUnconfirmed() != 0;
}
@Override
public String getBalanceHTML() {
if (hasBalanceInfo()) {
Amount balance = convertToAmount(btcData.getBalanceInInternalUnits());
return balance.toString();
} else {
return "";
}
}
@Override
public String getBalanceCurrencyHTML() {
return "mBCH";
}
@Override
public boolean isBalanceAlterNotZero() {
return true;
}
public boolean awaitingConfirmation(TangemCard card) {
return card.getBalanceUnconfirmed() != 0;
@Override
public boolean isBalanceNotZero() {
if( btcData==null ) return false;
if (btcData.getBalanceInInternalUnits() == null) return false;
return btcData.getBalanceInInternalUnits().notZero();
}
public String getBalanceWithAlter(TangemCard mCard) {
return getBalance(mCard);
}
public boolean isBalanceAlterNotZero(TangemCard card) {
return true;
}
public Long getBalanceLong(TangemCard mCard) {
return mCard.getBalance();
}
public boolean isBalanceNotZero(TangemCard card) {
return card.getBalance() > 0;
}
public boolean checkAmount(TangemCard card, String amount) throws Exception {
DecimalFormat decimalFormat = GetDecimalFormat();
BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount);
// Convert Balance to BigDecimal
BigDecimal maxValue = new BigDecimal(getBalanceValue(card));
maxValue = maxValue.divide(new BigDecimal(1000));
//if (use_mCurrency) {
amountValue = amountValue.divide(new BigDecimal(1000));
//}
if (amountValue.compareTo(maxValue) > 0) {
@Override
public boolean checkAmount(Amount amount) throws Exception {
checkBlockchainDataExists();
if (amount.compareTo(convertToAmount(btcData.getBalanceInInternalUnits())) > 0) {
return false;
}
return true;
}
public boolean hasBalanceInfo(TangemCard card) {
return card.hasBalanceInfo();
@Override
public boolean hasBalanceInfo(){
if( btcData==null ) return false;
return btcData.hasBalanceInfo();
}
public String getBalanceCurrency(TangemCard card) {
@Override
public boolean checkUnspentTransaction() throws Exception {
checkBlockchainDataExists();
return btcData.getUnspentTransactions().size() != 0;
}
@Override
public String getFeeCurrencyHTML() {
return "mBCH";
}
public boolean checkUnspentTransaction(TangemCard mCard) {
return mCard.getUnspentTransactions().size() != 0;
}
public String getFeeCurrency() {
return "mBCH";
}
public boolean validateAddress(String address, TangemCard card) {
@Override
public boolean validateAddress(String address) {
if (address == null || address.isEmpty()) {
return false;
}
@ -134,44 +145,46 @@ public class BtcCashEngine extends CoinEngine {
return false;
}
if (card.getBlockchain() != Blockchain.BitcoinCashTestNet && card.getBlockchain() != Blockchain.BitcoinCash) {
if (ctx.getBlockchain() != Blockchain.BitcoinCashTestNet && ctx.getBlockchain() != Blockchain.BitcoinCash) {
return false;
}
if (card.getBlockchain() == Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3"))) {
if (ctx.getBlockchain() == Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3"))) {
return false;
}
return true;
}
public int getTokenDecimals(TangemCard card) {
return 0;
}
public String getContractAddress(TangemCard card) {
return "";
}
@Override
public boolean isNeedCheckNode() {
return true;
}
public Uri getShareWalletUriExplorer(TangemCard mCard) {
return Uri.parse((mCard.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + mCard.getWallet());
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse((ctx.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + ctx.getCard().getWallet());
}
public Uri getShareWalletUri(TangemCard mCard) {
return Uri.parse("bitcoincash:" + mCard.getWallet());
@Override
public Uri getShareWalletUri() {
return Uri.parse("bitcoincash:" + ctx.getCard().getWallet());
}
public boolean checkAmountValue(TangemCard card, String amountValue, String feeValue, Long minFeeInInternalUnits, Boolean incfee) {
Long fee;
Long amount;
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[] { new DecimalDigitsInputFilter(8) };
}
@Override
public boolean checkAmountValue(String amountValue, String feeValue, InternalAmount minFeeInInternalUnits, Boolean isIncludeFee) {
InternalAmount fee;
InternalAmount amount;
try {
amount = card.internalUnitsFromString(amountValue);
fee = card.internalUnitsFromString(feeValue);
checkBlockchainDataExists();
amount = convertToInternalAmount(new Amount(amountValue, ctx.getBlockchain()));
fee = convertToInternalAmount(new Amount(feeValue, ctx.getBlockchain()));
} catch (Exception e) {
e.printStackTrace();
return false;
@ -180,64 +193,106 @@ public class BtcCashEngine extends CoinEngine {
if (fee == null || amount == null)
return false;
if (fee == 0 || amount == 0)
if (fee.isZero() || amount.isZero())
return false;
if (incfee && amount > card.getBalance())
if (isIncludeFee && amount.compareTo(btcData.getBalanceInInternalUnits())>0)
return false;
if (!incfee && amount + fee > card.getBalance())
if (!isIncludeFee && amount.add(fee).compareTo(btcData.getBalanceInInternalUnits())>0)
return false;
return true;
}
public String evaluateFeeEquivalent(TangemCard mCard, String fee) {
return getAmountEquivalentDescriptor(mCard, fee);
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
// Workaround before new back-end
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
// firstLine = "Verified balance";
// secondLine = "Balance confirmed in blockchain. ";
// secondLine += "Verified note identity. ";
// return;
// }
if (btcData.getBalanceUnconfirmed() != 0) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
if (btcData.isBalanceReceived() && btcData.isBalanceEqual()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (btcData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
// {
// score = 80;
// firstLine = "Unguaranteed balance";
// secondLine = "Potential unsent transaction. Redeem immediately if accept. ";
// return;
// }
if ((ctx.getCard().getOfflineBalance() != null) && !btcData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && btcData.getBalanceInInternalUnits().notZero() ) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
// secondLine += "Not all nodes have returned balance. Swipe down or tap again. ";
// if(score <= 0)
// return;
// }
//
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
// score = 0;
// firstLine = "Disputed balance";
// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later.";
// return;
// }
return true;
}
@Override
public String getBalanceEquivalent(TangemCard mCard) {
Double balance = Double.NaN;
try {
Long val = mCard.getBalance();
balance = mCard.amountFromInternalUnits(val);
} catch (Exception ex) {
mCard.setRate(0);
}
return mCard.getAmountEquivalentDescription(balance);
public Amount getBalance() {
return convertToAmount(btcData.getBalanceInInternalUnits());
}
public String getBalance(TangemCard mCard) {
if (mCard.hasBalanceInfo()) {
Double balance = mCard.amountFromInternalUnits(mCard.getBalance());
return mCard.getAmountDescription(balance);
} else {
return "";
}
@Override
public String evaluateFeeEquivalent(String fee) {
return getAmountEquivalentDescriptor(ctx.getCard(), fee);
}
public String getBalanceValue(TangemCard mCard) {
if (mCard.hasBalanceInfo()) {
Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier());
String output = FormatUtil.DoubleToString(balance);
//String pattern = "#0.000"; // If you like 4 zeros
//DecimalFormat myFormatter = new DecimalFormat(pattern);
//String output = myFormatter.format(balance);
return output;
//return Double.toString(balance);
} else {
return "0";
}
@Override
public String getBalanceEquivalent() {
if( btcData==null ) return "";
return btcData.getAmountEquivalentDescription(getBalance());
}
public String calculateAddress(TangemCard mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
byte netSelectionByte;
switch (mCard.getBlockchain()) {
switch (ctx.getBlockchain()) {
case BitcoinCash:
netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet
break;
@ -266,29 +321,49 @@ public class BtcCashEngine extends CoinEngine {
BB.put(hash4[3]);
return org.bitcoinj.core.Base58.encode(BB.array());
}
@Override
public String convertByteArrayToAmount(TangemCard mCard, byte[] bytes) throws Exception {
public Amount convertToAmount(InternalAmount internalAmount) {
return null;
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) throws Exception {
return null;
}
@Override
public InternalAmount convertToInternalAmount(byte[] bytes) {
if (bytes == null) return null;
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return FormatUtil.DoubleToString(mCard.amountFromInternalUnits(Util.byteArrayToLong(reversed)));
return new InternalAmount(Util.byteArrayToLong(reversed));
}
@Override
public byte[] convertAmountToByteArray(TangemCard card, String amount) throws Exception {
byte[] bytes = Util.longToByteArray(card.internalUnitsFromString(amount));
public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception {
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return reversed;
}
@Override
public String getAmountDescription(TangemCard mCard, String amount) throws Exception {
return mCard.getAmountDescription(Double.parseDouble(amount));
public CoinData createCoinData() {
return new BtcData();
}
@Override
public String getUnspentInputsDescription() {
return btcData.getUnspentInputsDescription();
}
// @Override
// public String getAmountDescription(TangemCard mCard, String amount) throws Exception {
// return mCard.getAmountDescription(Double.parseDouble(amount));
// }
public static String getAmountEquivalentDescriptionBTC(Double amount, float rate) {
if ((rate > 0) && (amount > 0)) {
return String.format("USD%.2f", amount * rate);
@ -298,18 +373,21 @@ public class BtcCashEngine extends CoinEngine {
}
public String getAmountEquivalentDescriptor(TangemCard mCard, String value) {
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value), mCard.getRate());
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value), btcData.getRate());
}
public byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, TangemCard mCard, CardProtocol protocol) throws Exception {
@Override
public byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, CardProtocol protocol) throws Exception {
String myAddress = mCard.getWallet();
byte[] pbKey = mCard.getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
checkBlockchainDataExists();
String myAddress = ctx.getCard().getWallet();
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
String outputAddress = toValue;
String changeAddress = myAddress;
// Build script for our address
List<TangemCard.UnspentTransaction> rawTxList = mCard.getUnspentTransactions();
List<BtcData.UnspentTransaction> rawTxList = btcData.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
@ -345,7 +423,7 @@ public class BtcCashEngine extends CoinEngine {
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
@ -353,8 +431,8 @@ public class BtcCashEngine extends CoinEngine {
}
byte[] signFromCard = null;
if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
byte[] signFromCard;
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
@ -364,20 +442,18 @@ public class BtcCashEngine extends CoinEngine {
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
} else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
return BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
}
}

View file

@ -0,0 +1,123 @@
package com.tangem.domain.wallet;
import android.os.Bundle;
import android.util.Log;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class BtcData extends CoinData {
public BtcData() {
super();
}
private Long balanceConfirmed, balanceUnconfirmed;
public String getUnspentInputsDescription() {
int gatheredUnspents = 0;
for (int i=0; i<unspentTransactions.size(); i++) {
if (unspentTransactions.get(i).Raw.length() > 1) gatheredUnspents++;
}
return String.valueOf(unspentTransactions.size()) + " unspents (" + String.valueOf(gatheredUnspents) + " received)";
}
public static class UnspentTransaction {
public String txID;
public Integer Amount;
public Integer Height;
public String Raw = "";
public Bundle getAsBundle() {
Bundle B = new Bundle();
B.putString("txID", txID);
B.putInt("Amount", Amount);
B.putInt("Height", Height);
B.putString("Raw", Raw);
return B;
}
public void loadFromBundle(Bundle B) {
txID = B.getString("txID");
Amount = B.getInt("Amount");
Height = B.getInt("Height");
Raw = B.getString("Raw");
}
}
private List<UnspentTransaction> unspentTransactions = null;
public List<UnspentTransaction> getUnspentTransactions() {
if (unspentTransactions == null) unspentTransactions = new ArrayList<>();
return unspentTransactions;
}
@Override
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
if (B.containsKey("BalanceConfirmed")) balanceConfirmed = B.getLong("BalanceConfirmed");
else balanceConfirmed = null;
if (B.containsKey("BalanceUnconfirmed"))
balanceUnconfirmed = B.getLong("BalanceUnconfirmed");
else balanceUnconfirmed = null;
if (B.containsKey("UnspentTransactions")) {
unspentTransactions = new ArrayList<>();
Bundle BB = B.getBundle("UnspentTransactions");
Integer i = 0;
while (BB.containsKey(i.toString())) {
UnspentTransaction t = new UnspentTransaction();
t.loadFromBundle(BB.getBundle(i.toString()));
unspentTransactions.add(t);
i++;
}
}
}
@Override
public void saveToBundle(Bundle B) {
super.saveToBundle(B);
try {
if (unspentTransactions != null) {
Bundle BB = new Bundle();
for (Integer i = 0; i < unspentTransactions.size(); i++) {
BB.putBundle(i.toString(), unspentTransactions.get(i).getAsBundle());
}
B.putBundle("UnspentTransactions", BB);
}
if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed);
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
}
@Override
public void clearInfo() {
super.clearInfo();
balanceConfirmed = null;
balanceUnconfirmed = null;
unspentTransactions = null;
}
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).add(BigDecimal.valueOf(balanceUnconfirmed)));
}
public Long getBalanceUnconfirmed() {
return balanceUnconfirmed;
}
public void setBalanceConfirmed(Long balance) {
this.balanceConfirmed = balance;
}
public void setBalanceUnconfirmed(Long balance) {
this.balanceUnconfirmed = balance;
}
public boolean hasBalanceInfo() {
return balanceConfirmed != null || balanceUnconfirmed != null;
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.domain.BitcoinNode;
import com.tangem.domain.BitcoinNodeTestNet;
@ -8,12 +9,12 @@ import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.FormatUtil;
import com.tangem.util.Util;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
@ -21,12 +22,27 @@ import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static com.tangem.util.FormatUtil.stringToBigDecimal;
public class BtcEngine extends CoinEngine {
public BtcData btcData = null;
public BtcEngine(TangemContext context) throws Exception {
super(context);
if (context.getCoinData() == null) {
btcData = new BtcData();
context.setCoinData(btcData);
} else if (context.getCoinData() instanceof BtcData) {
btcData = (BtcData) context.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for BtcEngine");
}
}
public BtcEngine() {
super();
}
private static String[] getBitcoinServiceHosts() {
return new String[]{
BitcoinNode.n1.getHost(),
@ -79,80 +95,70 @@ public class BtcEngine extends CoinEngine {
static int serviceIndex = 0;
public String getNode(TangemCard card) {
switch (card.getBlockchain()) {
case Bitcoin:
return getBitcoinServiceHosts()[serviceIndex]; //
private void checkBlockchainDataExists() throws Exception {
if (btcData == null) throw new Exception("No blockchain data");
}
@Override
public boolean awaitingConfirmation(){
if( btcData==null ) return false;
return btcData.getBalanceUnconfirmed() != 0;
}
@Override
public String getBalanceHTML() {
if (hasBalanceInfo()) {
Amount balance = convertToAmount(btcData.getBalanceInInternalUnits());
return balance.toString();
} else {
return "";
}
return null;
}
public int getNodePort(TangemCard card) {
switch (card.getBlockchain()) {
case Bitcoin:
return getBitcoinServicePorts()[serviceIndex];//8080;
}
return 8080;
}
public void switchNode(TangemCard card) {
serviceIndex++;
if (serviceIndex > getBitcoinServiceHosts().length - 1) serviceIndex = 0;
}
public boolean inOutPutVisible() {
return true;
}
public boolean awaitingConfirmation(TangemCard card) {
return card.getBalanceUnconfirmed() != 0;
}
public String getBalanceWithAlter(TangemCard card) {
return getBalance(card);
}
public boolean isBalanceAlterNotZero(TangemCard card) {
return true;
}
public Long getBalanceLong(TangemCard card) {
return card.getBalance();
}
public boolean isBalanceNotZero(TangemCard card) {
if (card.getBalance() == null) return false;
return card.getBalance() > 0;
}
public boolean checkAmount(TangemCard card, String amount) throws Exception {
// DecimalFormat decimalFormat = GetDecimalFormat();
// BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount);
BigDecimal amountValue = stringToBigDecimal(amount, Locale.US);
BigDecimal maxValue = stringToBigDecimal(getBalanceValue(card), Locale.US);
if (amountValue.compareTo(maxValue) > 0) {
return false;
}
return true;
}
public boolean hasBalanceInfo(TangemCard card) {
return card.hasBalanceInfo();
}
public String getBalanceCurrency(TangemCard card) {
@Override
public String getBalanceCurrencyHTML() {
return "BTC";
}
public boolean checkUnspentTransaction(TangemCard card) {
return card.getUnspentTransactions().size() != 0;
@Override
public String getOfflineBalanceHTML() {
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
Amount offlineAmount = convertToAmount(offlineInternalAmount);
return offlineAmount.toString();
}
public String getFeeCurrency() {
@Override
public boolean isBalanceAlterNotZero() {
return true;
}
@Override
public boolean isBalanceNotZero() {
if( btcData==null ) return false;
if (btcData.getBalanceInInternalUnits() == null) return false;
return btcData.getBalanceInInternalUnits().notZero();
}
@Override
public boolean hasBalanceInfo(){
if( btcData==null ) return false;
return btcData.hasBalanceInfo();
}
@Override
public boolean checkUnspentTransaction() throws Exception {
checkBlockchainDataExists();
return btcData.getUnspentTransactions().size() != 0;
}
@Override
public String getFeeCurrencyHTML() {
return "BTC";
}
public boolean validateAddress(String address, TangemCard card) {
@Override
public boolean validateAddress(String address) {
if (address == null || address.isEmpty()) {
return false;
}
@ -187,11 +193,11 @@ public class BtcEngine extends CoinEngine {
return false;
}
if (card.getBlockchain() != Blockchain.BitcoinTestNet && card.getBlockchain() != Blockchain.Bitcoin) {
if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin) {
return false;
}
if (card.getBlockchain() == Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) {
if (ctx.getBlockchain() == Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) {
return false;
}
@ -199,37 +205,48 @@ public class BtcEngine extends CoinEngine {
}
public int getTokenDecimals(TangemCard card) {
return 0;
}
public String getContractAddress(TangemCard card) {
return "";
}
@Override
public boolean isNeedCheckNode() {
return true;
}
public Uri getShareWalletUriExplorer(TangemCard card) {
return Uri.parse((card.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + card.getWallet());
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCard().getWallet());
}
public Uri getShareWalletUri(TangemCard card) {
if (card.getDenomination() != null) {
return Uri.parse("bitcoin:" + card.getWallet() + "?amount=" + BTCUtils.satoshiToBtc(card.getDenomination()));
@Override
public Uri getShareWalletUri() {
if (ctx.getCard().getDenomination() != null) {
return Uri.parse("bitcoin:" + ctx.getCard().getWallet() + "?amount=" + BTCUtils.satoshiToBtc(ctx.getCard().getDenomination()));
} else {
return Uri.parse("bitcoin:" + card.getWallet());
return Uri.parse("bitcoin:" + ctx.getCard().getWallet());
}
}
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[] { new DecimalDigitsInputFilter(8) };
}
@Override
public boolean checkNewTransactionAmount(Amount amount){
if( btcData==null ) return false;
if (amount.compareTo(convertToAmount(btcData.getBalanceInInternalUnits())) > 0) {
return false;
}
return true;
}
@Override
public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee, InternalAmount minFeeInInternalUnits) {
InternalAmount fee;
InternalAmount amount;
public boolean checkAmountValue(TangemCard card, String amountValue, String feeValue, Long minFeeInInternalUnits, Boolean isIncludeFee) {
Long fee;
Long amount;
try {
amount = card.internalUnitsFromString(amountValue);
fee = card.internalUnitsFromString(feeValue);
checkBlockchainDataExists();
amount = convertToInternalAmount(amountValue);
fee = convertToInternalAmount(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
@ -238,63 +255,105 @@ public class BtcEngine extends CoinEngine {
if (fee == null || amount == null)
return false;
if (fee == 0 || amount == 0)
if (fee.isZero() || amount.isZero())
return false;
if (isIncludeFee && amount > card.getBalance())
if (isIncludeFee && amount.compareTo(btcData.getBalanceInInternalUnits())>0)
return false;
if (!isIncludeFee && amount + fee > card.getBalance())
if (!isIncludeFee && amount.add(fee).compareTo(btcData.getBalanceInInternalUnits())>0)
return false;
return true;
}
public String evaluateFeeEquivalent(TangemCard card, String fee) {
return getAmountEquivalentDescriptor(card, fee);
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
// Workaround before new back-end
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
// firstLine = "Verified balance";
// secondLine = "Balance confirmed in blockchain. ";
// secondLine += "Verified note identity. ";
// return;
// }
if (btcData.getBalanceUnconfirmed() != 0) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
if (btcData.isBalanceReceived() && btcData.isBalanceEqual()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (btcData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
// {
// score = 80;
// firstLine = "Unguaranteed balance";
// secondLine = "Potential unsent transaction. Redeem immediately if accept. ";
// return;
// }
if ((ctx.getCard().getOfflineBalance() != null) && !btcData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && btcData.getBalanceInInternalUnits().notZero() ) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
// secondLine += "Not all nodes have returned balance. Swipe down or tap again. ";
// if(score <= 0)
// return;
// }
//
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
// score = 0;
// firstLine = "Disputed balance";
// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later.";
// return;
// }
return true;
}
@Override
public String getBalanceEquivalent(TangemCard card) {
Double balance = Double.NaN;
try {
Long val = card.getBalance();
balance = card.amountFromInternalUnits(val);
} catch (Exception ex) {
card.setRate(0);
}
return card.getAmountEquivalentDescription(balance);
public Amount getBalance() {
return convertToAmount(btcData.getBalanceInInternalUnits());
}
public String getBalance(TangemCard card) {
if (card.hasBalanceInfo()) {
Double balance = card.amountFromInternalUnits(card.getBalance());
return card.getAmountDescription(balance);
} else {
return "";
}
@Override
public String evaluateFeeEquivalent(String fee) {
return getAmountEquivalentDescriptor(ctx.getCard(), fee);
}
public String getBalanceValue(TangemCard card) {
if (card.hasBalanceInfo()) {
Double balance = card.getBalance() / (card.getBlockchain().getMultiplier());
String output = FormatUtil.DoubleToString(balance);
//String pattern = "#0.000"; // If you like 4 zeros
//DecimalFormat myFormatter = new DecimalFormat(pattern);
//String output = myFormatter.format(balance);
return output;
//return Double.toString(balance);
} else {
return "0";
}
@Override
public String getBalanceEquivalent() {
if( btcData==null ) return "";
return btcData.getAmountEquivalentDescription(getBalance());
}
public String calculateAddress(TangemCard card, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
byte netSelectionByte;
switch (card.getBlockchain()) {
switch (ctx.getBlockchain()) {
case Bitcoin:
netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet
break;
@ -326,26 +385,54 @@ public class BtcEngine extends CoinEngine {
}
@Override
public String convertByteArrayToAmount(TangemCard card, byte[] bytes) throws Exception {
if (bytes == null) return "";
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return FormatUtil.DoubleToString(card.amountFromInternalUnits(Util.byteArrayToLong(reversed)));
public Amount convertToAmount(InternalAmount internalAmount) {
//TODO
return null;
}
@Override
public byte[] convertAmountToByteArray(TangemCard card, String amount) throws Exception {
byte[] bytes = Util.longToByteArray(card.internalUnitsFromString(amount));
public Amount convertToAmount(String strAmount) {
//TODO
return null;
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) throws Exception {
//TODO
return null;
}
@Override
public InternalAmount convertToInternalAmount(byte[] bytes) {
if (bytes == null) return null;
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return new InternalAmount(Util.byteArrayToLong(reversed));
}
@Override
public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception {
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return reversed;
}
@Override
public String getAmountDescription(TangemCard card, String amount) throws Exception {
return card.getAmountDescription(Double.parseDouble(amount));
public CoinData createCoinData() {
return new BtcData();
}
@Override
public String getUnspentInputsDescription() {
return btcData.getUnspentInputsDescription();
}
// @Override
// public String getAmountDescription(TangemCard card, String amount) throws Exception {
// return card.getAmountDescription(Double.parseDouble(amount));
// }
public static String getAmountEquivalentDescriptionBTC(Double amount, float rate) {
if ((rate > 0) && (amount > 0)) {
return String.format("USD%.2f", amount * rate);
@ -355,18 +442,22 @@ public class BtcEngine extends CoinEngine {
}
public String getAmountEquivalentDescriptor(TangemCard card, String value) {
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value), card.getRate());
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value), btcData.getRate());
}
public byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, TangemCard card, CardProtocol protocol) throws Exception {
@Override
public byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, CardProtocol protocol) throws Exception {
String myAddress = card.getWallet();
byte[] pbKey = card.getWalletPublicKey();
checkBlockchainDataExists();
String myAddress = ctx.getCard().getWallet();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
String outputAddress = toValue;
String changeAddress = myAddress;
// Build script for our address
List<TangemCard.UnspentTransaction> rawTxList = card.getUnspentTransactions();
List<BtcData.UnspentTransaction> rawTxList = btcData.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
@ -402,7 +493,7 @@ public class BtcEngine extends CoinEngine {
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if (card.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || card.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
@ -410,7 +501,7 @@ public class BtcEngine extends CoinEngine {
}
byte[] signFromCard;
if (card.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || card.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
@ -420,21 +511,19 @@ public class BtcEngine extends CoinEngine {
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
} else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, card.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, card.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
return BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
}
}

View file

@ -0,0 +1,175 @@
package com.tangem.domain.wallet;
import android.os.Bundle;
import android.util.Log;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicInteger;
public class CoinData {
public CoinData() {
}
public boolean isBalanceReceived() {
return balanceReceived;
}
private boolean balanceReceived = false;
public void setBalanceReceived(boolean value) {
balanceReceived = value;
}
public void loadFromBundle(Bundle B) {
if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived"));
validationNodeDescription = B.getString("validationNodeDescription");
if (B.containsKey("FailedBalance"))
failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual"));
if (B.containsKey("rate"))
rate = B.getFloat("rate");
if (B.containsKey("rateAlter"))
rateAlter = B.getFloat("rateAlter");
}
public void saveToBundle(Bundle B) {
try {
if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
if (failedBalanceRequestCounter != null)
B.putInt("FailedBalance", failedBalanceRequestCounter.get());
B.putFloat("rate", rate);
B.putFloat("rateAlter", rateAlter);
B.putBoolean("balanceReceived", balanceReceived);
B.putString("validationNodeDescription", validationNodeDescription);
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
}
public Bundle asBundle() {
Bundle bundle = new Bundle();
saveToBundle(bundle);
return bundle;
}
public static CoinData fromBundle(Blockchain blockchain, Bundle bundle) {
CoinEngine engine=CoinEngineFactory.create(blockchain);
if( engine==null ) return null;
CoinData result = engine.createCoinData();
result.loadFromBundle(bundle);
return result;
}
//TODO - move all to special engines
private float rate = 0;
private float rateAlter = 0;
public float getRate() {
return rate;
}
public float getRateAlter() {
return rateAlter;
}
public void setRate(float rate) {
this.rate = rate;
}
public void setRateAlter(float rate) {
this.rateAlter = rate;
}
// public Double amountFromInternalUnits(Long internalAmount) {
// // TODO java.lang.NullPointerException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
// return ((double) internalAmount) /
// getBlockchain().getMultiplier();
// }
//
// public Double amountFromInternalUnits(Integer internalAmount) {
// return ((double) internalAmount) / getBlockchain().getMultiplier();
// }
//
// // TODO разобраться с балансами, привести к единому интерфейсу
// public Long internalUnitsFromString(String caption) {
// try {
// return FormatUtil.ConvertStringToLong(caption);
// } catch (Exception e) {
// return null;
// }
// }
//
// public String getAmountDescription(Double amount) {
// String output = FormatUtil.DoubleToString(amount);
// return output + " " + getBlockchain().getCurrency();
// }
public String getAmountEquivalentDescription(CoinEngine.Amount amount) {
if (getBlockchain() == Blockchain.Ethereum || getBlockchain() == Blockchain.EthereumTestNet || getBlockchain() == Blockchain.Token) {
return EthEngine.getAmountEquivalentDescriptionETH(amount, rate);
}
return BtcEngine.getAmountEquivalentDescriptionBTC(amount.doubleValue(), rate);
}
public boolean getAmountEquivalentDescriptionAvailable() {
return rate > 0;
}
public void clearInfo() {
setIsBalanceEqual(false);
}
private AtomicInteger failedBalanceRequestCounter;
public int incFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
failedBalanceRequestCounter = new AtomicInteger(0);
return failedBalanceRequestCounter.incrementAndGet();
}
public void resetFailedBalanceRequestCounter() {
failedBalanceRequestCounter = new AtomicInteger(0);
}
public int getFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
return 0;
return failedBalanceRequestCounter.get();
}
private Boolean balanceEqual;
public Boolean isBalanceEqual() {
return balanceEqual;
}
public void setIsBalanceEqual(boolean isEqual) {
balanceEqual = isEqual;
}
private String validationNodeDescription = "";
public String getValidationNodeDescription() {
return validationNodeDescription;
}
public void setValidationNodeDescription(String validationNodeDescription) {
this.validationNodeDescription = validationNodeDescription;
}
}

View file

@ -1,9 +1,13 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.domain.cardReader.CardProtocol;
import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -13,66 +17,150 @@ import java.security.NoSuchProviderException;
public abstract class CoinEngine {
public abstract String getNode(TangemCard card);
@Nullable
public static final String EXTRA_ENGINE = "CoinEngine";
public abstract int getNodePort(TangemCard card);
public static class InternalAmount extends BigDecimal {
public InternalAmount() {
super(0);
}
public abstract void switchNode(TangemCard card);
public InternalAmount(String amountString) {
super(amountString);
}
public abstract boolean awaitingConfirmation(TangemCard card);
public InternalAmount(long amount) {
super(amount);
}
public abstract boolean hasBalanceInfo(TangemCard card);
public InternalAmount(BigDecimal amount) {
super(amount.unscaledValue(), amount.scale());
}
public abstract boolean isBalanceNotZero(TangemCard card);
public boolean notZero()
{
return compareTo(BigDecimal.ZERO)>0;
}
public abstract boolean isBalanceAlterNotZero(TangemCard card);
public boolean isZero() {
return compareTo(BigDecimal.ZERO)==0;
}
}
public abstract boolean checkAmount(TangemCard card, String amount) throws Exception;
public static class Amount extends BigDecimal {
private Blockchain blockchain;
public abstract int getTokenDecimals(TangemCard card);
public Amount() {
super(0);
blockchain = Blockchain.Unknown;
}
public abstract String getContractAddress(TangemCard card);
public Amount(String amountString, Blockchain blockchain) {
super(amountString);
this.blockchain = blockchain;
}
public abstract byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, TangemCard mCard, CardProtocol protocol) throws Exception;
public Amount(Long amount, Blockchain blockchain) {
super(amount);
this.blockchain=blockchain;
}
public abstract boolean checkUnspentTransaction(TangemCard card);
public String getCurrency() {
return blockchain.getCurrency();
}
public abstract Uri getShareWalletUriExplorer(TangemCard card);
@Override
public String toString() {
return super.toString() + " " + blockchain.getCurrency();
}
public abstract Long getBalanceLong(TangemCard card);
public boolean notZero()
{
return compareTo(BigDecimal.ZERO)>0;
}
public abstract Uri getShareWalletUri(TangemCard card);
public String getStringToEdit() {
return super.toString();
}
public abstract String evaluateFeeEquivalent(TangemCard card, String fee);
}
public abstract boolean checkAmountValue(TangemCard card, String amount, String fee, Long minFeeInInternalUnits, Boolean incfee);
protected TangemContext ctx;
public abstract boolean inOutPutVisible();
public CoinEngine() {
public abstract String getBalance(TangemCard card);
}
public abstract String getBalanceWithAlter(TangemCard card);
public CoinEngine(TangemContext ctx) {
this.ctx = ctx;
}
public abstract String getBalanceCurrency(TangemCard card);
public abstract boolean awaitingConfirmation();
public abstract String getFeeCurrency();
public abstract boolean hasBalanceInfo();
public abstract boolean isBalanceNotZero();
public abstract boolean isBalanceAlterNotZero();
public abstract byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, CardProtocol protocol) throws Exception;
public abstract boolean checkUnspentTransaction() throws Exception;
public abstract Uri getShareWalletUriExplorer();
public abstract Uri getShareWalletUri();
// public abstract Long getBalanceLong(TangemCard card);
public abstract boolean checkNewTransactionAmount(Amount amount);
// TODO - move minFeeInInternalUnits to CoinData
public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded, InternalAmount minFeeInInternalUnits);
public abstract boolean validateBalance(BalanceValidator balanceValidator);
public abstract Amount getBalance();
public abstract String getBalanceHTML();
public abstract String getBalanceCurrencyHTML();
public abstract InputFilter[] getAmountInputFilters();
public abstract String getOfflineBalanceHTML();
public abstract String evaluateFeeEquivalent(String fee);
public abstract String getFeeCurrencyHTML();
public abstract boolean isNeedCheckNode();
public abstract String getBalanceEquivalent(TangemCard card);
public abstract String getBalanceEquivalent();
public abstract String getBalanceValue(TangemCard card);
// public abstract String getBalanceValue(TangemCard card);
public abstract String getAmountDescription(TangemCard card, String amount) throws Exception;
// public abstract String getAmountDescription(TangemCard card, String amount) throws Exception;
public abstract String getAmountEquivalentDescriptor(TangemCard card, String value);
// public abstract String getAmountEquivalentDescriptor(TangemCard card, String value);
public abstract boolean validateAddress(String address, TangemCard card);
public abstract boolean validateAddress(String address);
public abstract String calculateAddress(TangemCard card, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException;
public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException;
public abstract String convertByteArrayToAmount(TangemCard card, byte[] bytes) throws Exception;
public abstract Amount convertToAmount(InternalAmount internalAmount);
public abstract Amount convertToAmount(String strAmount);
public abstract byte[] convertAmountToByteArray(TangemCard card, String amount) throws Exception;
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 CoinEngine swithToBaseEngine();
public abstract CoinData createCoinData();
public abstract String getUnspentInputsDescription();
}

View file

@ -5,17 +5,37 @@ package com.tangem.domain.wallet;
*/
public class CoinEngineFactory {
public static CoinEngine create(Blockchain chain) {
if (Blockchain.BitcoinCash == chain || Blockchain.BitcoinCashTestNet == chain) {
return new BtcCashEngine();
} else if (Blockchain.Bitcoin == chain || Blockchain.BitcoinTestNet == chain) {
return new BtcEngine(); //TODO: ВРЕМЕНГГО!!!!
} else if (Blockchain.Ethereum == chain || Blockchain.EthereumTestNet == chain) {
return new EthEngine();
} else if (Blockchain.Token == chain) {
return new TokenEngine();
public static CoinEngine create(Blockchain blockchain) {
switch (blockchain) {
case Bitcoin:
case BitcoinTestNet:
return new BtcEngine();
case BitcoinCash:
case BitcoinCashTestNet:
return new BtcCashEngine();
case Ethereum:
case EthereumTestNet:
return new EthEngine();
case Token:
return new TokenEngine();
default:
return null;
}
}
public static CoinEngine create(TangemContext context) {
CoinEngine result;
if (Blockchain.BitcoinCash == context.getBlockchain() || Blockchain.BitcoinCashTestNet == context.getBlockchain()) {
result = new BtcCashEngine(context);
} else if (Blockchain.Bitcoin == context.getBlockchain() || Blockchain.BitcoinTestNet == context.getBlockchain()) {
result = new BtcEngine(context);
} else if (Blockchain.Ethereum == context.getBlockchain() || Blockchain.EthereumTestNet == context.getBlockchain()) {
result = new EthEngine(context);
} else if (Blockchain.Token == context.getBlockchain()) {
result = new TokenEngine(context);
} else {
return null;
}
return result;
}
}

View file

@ -0,0 +1,114 @@
package com.tangem.domain.wallet;
import android.os.Bundle;
import android.util.Log;
import java.math.BigDecimal;
import java.math.BigInteger;
public class EthData extends CoinData
{
private CoinEngine.InternalAmount balance=null;
private CoinEngine.InternalAmount balanceAlter=null;
private BigInteger countConfirmedTX = null;
private BigInteger countUnconfirmedTX = BigInteger.valueOf(0);
public BigInteger getConfirmedTXCount() {
if (countConfirmedTX == null) {
countConfirmedTX = BigInteger.valueOf(0);
}
return countConfirmedTX;
}
public void setConfirmedTXCount(BigInteger count) {
countConfirmedTX = count;
}
public BigInteger getUnconfirmedTXCount() {
if (countUnconfirmedTX == null) {
countUnconfirmedTX = BigInteger.valueOf(0);
}
return countUnconfirmedTX;
}
public void setUnconfirmedTXCount(BigInteger count) {
countUnconfirmedTX = count;
}
public String getAmountInGwei(String amount) {
BigInteger d = new BigInteger(amount, 10);
BigInteger m = d.divide(BigInteger.valueOf(1000000000L));
return m.toString(10);
}
@Override
public void clearInfo() {
super.clearInfo();
balance = null;
balanceAlter = null;
}
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
return balance;
}
public CoinEngine.InternalAmount getBalanceAlterInInternalUnits() {
return balanceAlter;
}
public void setBalanceInInternalUnits(CoinEngine.InternalAmount value) {
balance = value;
}
public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) {
balanceAlter = value;
}
public Long getBalanceETH() {
BigDecimal b = null;
if (balance != null) {
b = balance; // Returns ETH / token balance
} else if (balanceAlter != null) {
b = balanceAlter; // or ETH balance if there're no tokens on Token card
}
if (b != null) {
return b.longValue(); // Will leave only lower 64 bits for ETH and Tokens
} else {
return null;
}
}
@Override
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
balance = new CoinEngine.InternalAmount(B.getString("BalanceDecimal"));
balanceAlter =new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"));
if (B.containsKey("confirmTx"))
countConfirmedTX = new BigInteger(B.getString("confirmTx"), 16);
if (B.containsKey("unconfirmTx"))
countUnconfirmedTX = new BigInteger(B.getString("unconfirmTx"), 16);
}
@Override
public void saveToBundle(Bundle B) {
try {
B.putString("BalanceDecimal", balance.toString());
B.putString("BalanceDecimalAlter", balance.toString());
B.putString("confirmTx", getConfirmedTXCount().toString(16));
B.putString("unconfirmTx", getUnconfirmedTXCount().toString(16));
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
}
}

View file

@ -1,12 +1,14 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import org.bitcoinj.core.ECKey;
@ -17,7 +19,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Date;
import static com.tangem.util.FormatUtil.GetDecimalFormat;
@ -27,30 +28,68 @@ import static com.tangem.util.FormatUtil.GetDecimalFormat;
public class EthEngine extends CoinEngine {
public String getNode(TangemCard mCard) {
return "abc1.hsmiths.com";
public EthData ethData = null;
public EthEngine(TangemContext ctx) throws Exception {
super(ctx);
if (ctx.getCoinData() == null) {
ethData = new EthData();
ctx.setCoinData(ethData);
} else if (ctx.getCoinData() instanceof BtcData) {
ethData = (EthData) ctx.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for BtcEngine");
}
}
public int getNodePort(TangemCard mCard) {
return 60001;
public EthEngine() {
super();
}
public void switchNode(TangemCard mCard) {
}
public boolean inOutPutVisible() {
@Override
public boolean awaitingConfirmation(){
return false;
}
public String getBalanceCurrency(TangemCard card) {
@Override
public String getBalanceHTML() {
if (hasBalanceInfo()) {
Amount balance = convertToAmount(ethData.getBalanceInInternalUnits());
return balance.toString();
} else {
return "";
}
}
@Override
public String getBalanceCurrencyHTML() {
return "ETH";
}
public boolean awaitingConfirmation(TangemCard card) {
return false;
@Override
public String getOfflineBalanceHTML() {
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
Amount offlineAmount = convertToAmount(offlineInternalAmount);
return offlineAmount.toString();
}
public String getFeeCurrency() {
@Override
public boolean isBalanceAlterNotZero() {
return true;
}
@Override
public boolean isBalanceNotZero() {
if( ethData==null ) return false;
if (ethData.getBalanceInInternalUnits() == null) return false;
return ethData.getBalanceInInternalUnits().notZero();
}
@Override
public String getFeeCurrencyHTML() {
return "Gwei";
}
@ -66,10 +105,6 @@ public class EthEngine extends CoinEngine {
return d;
}
public int getTokenDecimals(TangemCard card) {
return 0;
}
public String getContractAddress(TangemCard card) {
return "";
}
@ -151,7 +186,7 @@ public class EthEngine extends CoinEngine {
return mCard.getBalance();
}
public String getBalanceWithAlter(TangemCard mCard) {
public String getBalanceHTML(TangemCard mCard) {
return getBalance(mCard);
}
@ -215,8 +250,9 @@ public class EthEngine extends CoinEngine {
return true;
}
public boolean hasBalanceInfo(TangemCard card) {
return card.hasBalanceInfo();
@Override
public boolean hasBalanceInfo() {
return ethData.hasBalanceInfo();
}
public Uri getShareWalletUri(TangemCard mCard) {
@ -239,6 +275,12 @@ public class EthEngine extends CoinEngine {
}
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[] { new DecimalDigitsInputFilter(18) };
}
public boolean checkAmountValue(TangemCard mCard, String amountValue, String feeValue, Long minFeeInInternalUnits, Boolean incfee) {
// Long fee = null;
// Long amount = null;
@ -263,7 +305,7 @@ public class EthEngine extends CoinEngine {
try {
BigDecimal tmpFee = new BigDecimal(feeValue);
BigDecimal tmpAmount = new BigDecimal(amountValue);
BigDecimal cardBalance = new BigDecimal(mCard.getDecimalBalance());
BigDecimal cardBalance = new BigDecimal(ethData.getDecimalBalance());
tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000"));
cardBalance = cardBalance.divide(new BigDecimal("1000000000"));
//if (tmpFee.compareTo(tmpAmount) > 0)
@ -311,7 +353,7 @@ public class EthEngine extends CoinEngine {
public byte[] sign(String feeValue, String amountValue, boolean IncFee, String toValue, TangemCard mCard, CardProtocol protocol) throws Exception {
BigInteger nonceValue = mCard.getConfirmedTXCount();
BigInteger nonceValue = ethData.getConfirmedTXCount();
byte[] pbKey = mCard.getWalletPublicKey();
boolean flag = (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = mCard.getIssuer();

View file

@ -5,15 +5,9 @@ import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.domain.cardReader.SettingsMask;
import com.tangem.util.FormatUtil;
import com.tangem.util.Util;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by dvol on 16.07.2017.
@ -28,11 +22,6 @@ public class TangemCard {
private Status status;
private String wallet;
private String UID;
private String error;
private String message;
private String balanceDecimal;
private String balanceDecimalAlter = "";
private Long balanceConfirmed, balanceUnconfirmed;
private String blockchainID;
private Manufacturer manufacturer = Manufacturer.Unknown;
private boolean manufacturerConfirmed = false;
@ -43,49 +32,6 @@ public class TangemCard {
private byte[] pbCardKey = null;
private byte[] pbWalletKeyRar = null;
private Date dtPersonalization = null;
private float rate = 0;
private float rateAlter = 0;
private BigInteger countConfirmedTX = null;
private BigInteger countUnconfirmedTX = BigInteger.valueOf(0);
public BigInteger getConfirmedTXCount() {
if (countConfirmedTX == null) {
countConfirmedTX = BigInteger.valueOf(0);
}
return countConfirmedTX;
}
public void setConfirmedTXCount(BigInteger count) {
countConfirmedTX = count;
}
public BigInteger getUnconfirmedTXCount() {
if (countUnconfirmedTX == null) {
countUnconfirmedTX = BigInteger.valueOf(0);
}
return countUnconfirmedTX;
}
public void setUnconfirmedTXCount(BigInteger count) {
countUnconfirmedTX = count;
}
public float getRate() {
return rate;
}
public float getRateAlter() {
return rateAlter;
}
public void setRate(float rate) {
this.rate = rate;
}
public void setRateAlter(float rate) {
this.rateAlter = rate;
}
public String getBlockchainID() {
return blockchainID;
@ -95,6 +41,10 @@ public class TangemCard {
return Blockchain.fromId(blockchainID);
}
public void setBlockchain(Blockchain blockchain) {
blockchainID=blockchain.getID();
}
public void setBlockchainID(String blockchainID) {
this.blockchainID = blockchainID;
}
@ -106,17 +56,12 @@ public class TangemCard {
String oldName = getBlockchain().getOfficialName();
//String newName = String.format("%s - %s ERC20 token", token, oldName);
String newName = token + " <br><small><small> " + oldName + " ERC20 token</small></small>";
setBlockchainName(newName);
blockchainName=newName;
}
private String blockchainName = "";
public void setBlockchainName(String name) {
blockchainName = name;
}
public String getBlockchainName() {
if (Strings.isNullOrEmpty(blockchainName))
return getBlockchain().getOfficialName();
@ -180,47 +125,6 @@ public class TangemCard {
return cardPublicKeyValid;
}
public Double amountFromInternalUnits(Long internalAmount) {
// TODO java.lang.NullPointerException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
return ((double) internalAmount) /
getBlockchain().getMultiplier();
}
public Double amountFromInternalUnits(Integer internalAmount) {
return ((double) internalAmount) / getBlockchain().getMultiplier();
}
// TODO разобраться с балансами, привести к единому интерфейсу
public Long internalUnitsFromString(String caption) {
try {
return FormatUtil.ConvertStringToLong(caption);
} catch (Exception e) {
return null;
}
}
public String getAmountDescription(Double amount) {
String output = FormatUtil.DoubleToString(amount);
return output + " " + getBlockchain().getCurrency();
}
public String getAmountEquivalentDescription(Double amount) {
if (getBlockchain() == Blockchain.Ethereum || getBlockchain() == Blockchain.EthereumTestNet || getBlockchain() == Blockchain.Token) {
return EthEngine.getAmountEquivalentDescriptionETH(amount, rate);
}
return BtcEngine.getAmountEquivalentDescriptionBTC(amount, rate);
}
public boolean getAmountEquivalentDescriptionAvailable() {
return rate > 0;
}
public String getAmountInGwei(String amount) {
BigInteger d = new BigInteger(amount, 10);
BigInteger m = d.divide(BigInteger.valueOf(1000000000L));
return m.toString(10);
}
public Manufacturer getManufacturer() {
return manufacturer;
@ -285,13 +189,7 @@ public class TangemCard {
this.PIN = PIN;
}
public void clearInfo() {
balanceConfirmed = null;
balanceUnconfirmed = null;
unspentTransactions = null;
balanceDecimal = null;
balanceDecimalAlter = null;
setIsBalanceEqual(false);
public void switchToInitialBlockchain() {
if (tokenSymbol.length() > 1)
blockchainID = Blockchain.Token.getID(); // Reset blockchain to Token from ETH for token cards with zero token balance on it
}
@ -306,7 +204,6 @@ public class TangemCard {
public String getPersonalizationDateTimeDescription() {
return Util.formatDate(dtPersonalization);
}
private int health = 0;
@ -418,34 +315,6 @@ public class TangemCard {
return pauseBeforePIN2;
}
private AtomicInteger failedBalanceRequestCounter;
public int incFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
failedBalanceRequestCounter = new AtomicInteger(0);
return failedBalanceRequestCounter.incrementAndGet();
}
public void resetFailedBalanceRequestCounter() {
failedBalanceRequestCounter = new AtomicInteger(0);
}
public int getFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
return 0;
return failedBalanceRequestCounter.get();
}
private Boolean balanceEqual;
public Boolean isBalanceEqual() {
return balanceEqual;
}
public void setIsBalanceEqual(boolean isEqual) {
balanceEqual = isEqual;
}
private Integer settingsMask = null;
public void setSettingsMask(int settingsMask) {
@ -591,16 +460,6 @@ public class TangemCard {
return batch;
}
private String validationNodeDescription = "";
public String getValidationNodeDescription() {
return validationNodeDescription;
}
public void setValidationNodeDescription(String validationNodeDescription) {
this.validationNodeDescription = validationNodeDescription;
}
public enum SigningMethod {
Sign_Hash(0, "sign hash"),
Sign_Raw(1, "sign raw tx"),
@ -638,40 +497,32 @@ public class TangemCard {
return signingMethod;
}
public static class UnspentTransaction {
public String txID;
public Integer Amount;
public Integer Height;
public String Raw = "";
public Bundle getAsBundle() {
Bundle B = new Bundle();
B.putString("txID", txID);
B.putInt("Amount", Amount);
B.putInt("Height", Height);
B.putString("Raw", Raw);
return B;
}
public void loadFromBundle(Bundle B) {
txID = B.getString("txID");
Amount = B.getInt("Amount");
Height = B.getInt("Height");
Raw = B.getString("Raw");
}
}
private List<UnspentTransaction> unspentTransactions = null;
public List<UnspentTransaction> getUnspentTransactions() {
if (unspentTransactions == null) unspentTransactions = new ArrayList<>();
return unspentTransactions;
}
public TangemCard(String UID) {
this.UID = UID;
}
public enum Status {
NotPersonalized(0), Empty(1), Loaded(2), Purged(3);
Status(int Code) {
mCode = Code;
}
private int mCode;
public int getCode() {
return mCode;
}
public static Status fromCode(int code) {
for (Status s : values()) {
if (s.getCode() == code) return s;
}
return null;
}
}
public void setStatus(Status status) {
this.status = status;
}
@ -721,58 +572,6 @@ public class TangemCard {
this.UID = UID;
}
public Long getBalance() {
BigDecimal balance = null;
if ((this.getBlockchain() == Blockchain.Bitcoin || this.getBlockchain() == Blockchain.BitcoinCash || this.getBlockchain() == Blockchain.BitcoinTestNet || this.getBlockchain() == Blockchain.BitcoinCashTestNet) && balanceConfirmed != null && balanceUnconfirmed != null) {
balance = BigDecimal.valueOf(balanceConfirmed + balanceUnconfirmed);
} else if (this.getBlockchain() == Blockchain.Ethereum || this.getBlockchain() == Blockchain.EthereumTestNet || this.getBlockchain() == Blockchain.Token) {
if (balanceDecimal != null) {
balance = new BigDecimal(balanceDecimal); // Returns ETH / token balance
} else if (balanceDecimalAlter != null) {
balance = new BigDecimal(balanceDecimalAlter); // or ETH balance if there're no tokens on Token card
}
}
if (balance != null) {
return balance.longValue(); // Will leave only lower 64 bits for ETH and Tokens
} else {
return null;
}
}
public Long getBalanceUnconfirmed() {
return balanceUnconfirmed;
}
public void setBalanceConfirmed(Long balance) {
this.balanceConfirmed = balance;
}
public void setBalanceUnconfirmed(Long balance) {
this.balanceUnconfirmed = balance;
}
public boolean hasBalanceInfo() {
return balanceConfirmed != null || balanceUnconfirmed != null || balanceDecimal != "" || balanceDecimalAlter != "";
}
public String getDecimalBalance() {
return balanceDecimal;
}
public String getDecimalBalanceAlter() {
return balanceDecimalAlter;
}
public void setDecimalBalance(String value) {
balanceDecimal = value;
}
public void setDecimalBalanceAlter(String value) {
balanceDecimalAlter = value;
}
private byte[] offlineBalance;
@ -799,7 +598,10 @@ public class TangemCard {
public void setDenomination(byte[] denomination) {
this.Denomination = denomination;
try {
this.DenominationText = CoinEngineFactory.create(getBlockchain()).convertByteArrayToAmount(this, denomination);
CoinEngine engine=CoinEngineFactory.create(getBlockchain());
CoinEngine.InternalAmount internalAmount=engine.convertToInternalAmount(denomination);
CoinEngine.Amount amount=engine.convertToAmount(internalAmount);
this.DenominationText = amount.toString();
} catch (Exception e) {
e.printStackTrace();
this.DenominationText = "N/A";
@ -834,43 +636,6 @@ public class TangemCard {
DenominationText = null;
}
public void setError(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setMessage(String value) {
this.message = value;
}
public String getMessage() {
return message;
}
public enum Status {
NotPersonalized(0), Empty(1), Loaded(2), Purged(3);
Status(int Code) {
mCode = Code;
}
private int mCode;
public int getCode() {
return mCode;
}
public static Status fromCode(int code) {
for (Status s : values()) {
if (s.getCode() == code) return s;
}
return null;
}
}
public Bundle getAsBundle() {
Bundle B = new Bundle();
saveToBundle(B);
@ -889,7 +654,6 @@ public class TangemCard {
B.putInt("TokensDecimal", tokensDecimal);
B.putString("TokenSymbol", tokenSymbol);
B.putString("ContractAddress", contractAddress);
B.putString("validationNodeDescription", validationNodeDescription);
if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime());
B.putInt("RemainingSignatures", remainingSignatures);
B.putInt("MaxSignatures", maxSignatures);
@ -902,26 +666,18 @@ public class TangemCard {
if (issuer != null) B.putString("Issuer", issuer.getID());
if (issuerPublicDataKey != null) B.putByteArray("IssuerPublicDataKey", issuerPublicDataKey);
if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion);
if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
if (batch != null) B.putString("Batch", batch);
B.putString("BalanceDecimal", balanceDecimal);
B.putString("BalanceDecimalAlter", balanceDecimalAlter);
B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed);
B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid());
B.putByteArray("CardPublicKey", getCardPublicKey());
if (failedBalanceRequestCounter != null)
B.putInt("FailedBalance", failedBalanceRequestCounter.get());
B.putInt("SignedHashes", getSignedHashes());
B.putString("Wallet", wallet);
B.putString("Error", error);
B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid());
if (pbWalletKey != null)
B.putByteArray("PublicKey", pbWalletKey);
if (pbWalletKeyRar != null)
B.putByteArray("PublicKeyRar", pbWalletKeyRar);
if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed);
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance());
@ -933,18 +689,6 @@ public class TangemCard {
B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData());
}
if (unspentTransactions != null) {
Bundle BB = new Bundle();
for (Integer i = 0; i < unspentTransactions.size(); i++) {
BB.putBundle(i.toString(), unspentTransactions.get(i).getAsBundle());
}
B.putBundle("UnspentTransactions", BB);
}
B.putFloat("rate", rate);
B.putFloat("rateAlter", rateAlter);
B.putString("confirmTx", getConfirmedTXCount().toString(16));
B.putString("unconfirmTx", getUnconfirmedTXCount().toString(16));
if (codeConfirmed != null)
B.putBoolean("codeConfirmed", codeConfirmed);
@ -954,13 +698,9 @@ public class TangemCard {
if (onlineVerified != null)
B.putBoolean("onlineVerified", onlineVerified);
B.putBoolean("balanceReceived", balanceReceived);
if (onlineValidated != null)
B.putBoolean("onlineValidated", onlineValidated);
if (codeConfirmed != null)
B.putBoolean("codeConfirmed", codeConfirmed);
@ -978,16 +718,6 @@ public class TangemCard {
}
public boolean isBalanceReceived() {
return balanceReceived;
}
private boolean balanceReceived = false;
public void setBalanceReceived(boolean value) {
balanceReceived = value;
}
public void loadFromBundle(Bundle B) {
UID = B.getString("UID");
CID = B.getByteArray("CID");
@ -995,19 +725,15 @@ public class TangemCard {
PIN2 = PIN2_Mode.valueOf(B.getString("PIN2"));
status = Status.valueOf(B.getString("Status"));
wallet = B.getString("Wallet");
error = B.getString("Error");
blockchainID = B.getString("Blockchain");
tokensDecimal = B.getInt("TokensDecimal", 18);
tokenSymbol = B.getString("TokenSymbol", "");
contractAddress = B.getString("ContractAddress", "");
if (B.containsKey("BlockchainName"))
blockchainName = B.getString("BlockchainName", "");
validationNodeDescription = B.getString("validationNodeDescription");
if (B.containsKey("dtPersonalization")) {
dtPersonalization = new Date(B.getLong("dtPersonalization"));
}
balanceDecimal = B.getString("BalanceDecimal");
balanceDecimalAlter = B.getString("BalanceDecimalAlter");
remainingSignatures = B.getInt("RemainingSignatures");
maxSignatures = B.getInt("MaxSignatures");
health = B.getInt("health");
@ -1025,8 +751,6 @@ public class TangemCard {
if (B.containsKey("SignedHashes")) setSignedHashes(B.getInt("SignedHashes"));
if (B.containsKey("FailedBalance"))
failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer"));
if (B.containsKey("IssuerPublicDataKey"))
issuerPublicDataKey = B.getByteArray("IssuerPublicDataKey");
@ -1034,17 +758,9 @@ public class TangemCard {
if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion");
if (B.containsKey("Batch")) batch = B.getString("Batch");
if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual"));
cardPublicKeyValid = B.getBoolean("CardPublicKeyValid");
if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey"));
if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived"));
if (B.containsKey("BalanceConfirmed")) balanceConfirmed = B.getLong("BalanceConfirmed");
else balanceConfirmed = null;
if (B.containsKey("BalanceUnconfirmed"))
balanceUnconfirmed = B.getLong("BalanceUnconfirmed");
else balanceUnconfirmed = null;
if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance"));
else clearOfflineBalance();
@ -1066,26 +782,6 @@ public class TangemCard {
pbWalletKeyRar = B.getByteArray("PublicKeyRar");
}
if (B.containsKey("UnspentTransactions")) {
unspentTransactions = new ArrayList<>();
Bundle BB = B.getBundle("UnspentTransactions");
Integer i = 0;
while (BB.containsKey(i.toString())) {
UnspentTransaction t = new UnspentTransaction();
t.loadFromBundle(BB.getBundle(i.toString()));
unspentTransactions.add(t);
i++;
}
}
if (B.containsKey("rate"))
rate = B.getFloat("rate");
if (B.containsKey("rateAlter"))
rateAlter = B.getFloat("rateAlter");
if (B.containsKey("confirmTx"))
countConfirmedTX = new BigInteger(B.getString("confirmTx"), 16);
if (B.containsKey("unconfirmTx"))
countUnconfirmedTX = new BigInteger(B.getString("unconfirmTx"), 16);
if (B.containsKey("codeConfirmed"))
codeConfirmed = B.getBoolean("codeConfirmed");
@ -1096,19 +792,6 @@ public class TangemCard {
onlineValidated = B.getBoolean("onlineValidated");
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "";
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public enum EncryptionMode {
None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2);

View file

@ -0,0 +1,114 @@
package com.tangem.domain.wallet;
import android.content.Context;
import android.os.Bundle;
public class TangemContext {
private static final String EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA";
private Context context;
private TangemCard card;
private CoinData coinData;
private String error;
private String message;
public TangemContext() {
}
public TangemContext(TangemCard card) {
this.card = card;
}
public Blockchain getBlockchain() {
if (card == null) return Blockchain.Unknown;
return card.getBlockchain();
}
public void setBlockchain(Blockchain blockchain) {
if (card == null) return;
card.setBlockchain(blockchain);
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
public TangemCard getCard() {
return card;
}
public void setCard(TangemCard card) {
this.card = card;
}
public CoinData getCoinData() {
return coinData;
}
public void setCoinData(CoinData coinData) {
this.coinData = coinData;
}
public void setError(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setMessage(String value) {
this.message = value;
}
public String getMessage() {
return message;
}
public static TangemContext loadFromBundle(Context context, Bundle bundle) {
TangemContext tangemContext = new TangemContext();
tangemContext.setContext(context);
if (bundle.containsKey(TangemCard.EXTRA_UID)) {
tangemContext.card = new TangemCard(bundle.getString(TangemCard.EXTRA_UID));
tangemContext.card.loadFromBundle(bundle.getBundle(TangemCard.EXTRA_CARD));
}
if (tangemContext.getBlockchain() != null && bundle.containsKey(EXTRA_BLOCKCHAIN_DATA)) {
tangemContext.coinData=CoinData.fromBundle(tangemContext.getBlockchain(), bundle.getBundle(EXTRA_BLOCKCHAIN_DATA));
}
tangemContext.error = bundle.getString("Error");
tangemContext.message = bundle.getString("Message");
return tangemContext;
}
public void saveToBundle(Bundle bundle) {
if (card != null) {
bundle.putString(TangemCard.EXTRA_UID, card.getUID());
bundle.putBundle(TangemCard.EXTRA_CARD, card.getAsBundle());
}
if (coinData != null) {
bundle.putBundle(EXTRA_BLOCKCHAIN_DATA, coinData.asBundle());
}
bundle.putString("Error", error);
bundle.putString("Message", message);
}
public String getString(int stringId) {
if( context!=null ) return getContext().getResources().getString(stringId);
return "context.resources.string["+stringId+"]";
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.wallet.R;
import org.bitcoinj.core.ECKey;
@ -28,24 +29,23 @@ import static com.tangem.util.FormatUtil.GetDecimalFormat;
public class TokenEngine extends CoinEngine {
public String getNode(TangemCard mCard) {
return "abc1.hsmiths.com";
@Override
public String getOfflineBalanceHTML() {
return ctx.getString(R.string.not_implemented);
}
public int getNodePort(TangemCard mCard) {
return 60001;
public TokenEngine(TangemContext context) {
super(context);
}
public void switchNode(TangemCard mCard) {
public TokenEngine() {
}
public boolean awaitingConfirmation(TangemCard card) {
return false;
}
public boolean inOutPutVisible() {
return false;
}
public String getBalanceCurrency(TangemCard card) {
String currency = card.getTokenSymbol();
@ -219,7 +219,7 @@ public class TokenEngine extends CoinEngine {
}
public String getBalanceWithAlter(TangemCard mCard) {
public String getBalanceHTML(TangemCard mCard) {
//return getBalance(mCard) + "\n(" + GetBalanceAlterValue(mCard) + " ETH)";
if (GetBalanceAlterValue(mCard) != "") {
return " " + getBalance(mCard) + " <br><small><small> + " + GetBalanceAlterValue(mCard) + " ETH for gas</small></small>";