From 2a18f35a1ffac10d1356bc7c5052409c438c4cb5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Dec 2018 16:30:46 +0300 Subject: [PATCH 01/12] Updated on 2026-08-14 --- app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java index 203d2e7d25..5005784da1 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java @@ -56,9 +56,11 @@ public class CashAddr { String[] addressParts = bitcoinCashAddress.split(SEPARATOR); if (addressParts.length == 2) { decoded.setPrefix(addressParts[0]); + } else { + decoded.setPrefix(MAIN_NET_PREFIX); } - byte[] addressData = BitcoinCashBase32.decode(addressParts[1]); + byte[] addressData = BitcoinCashBase32.decode(addressParts[addressParts.length - 1]); addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8); addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true); byte versionByte = addressData[0]; From 7c2a0198ece9988ae7a04a459fcb22156327ec9a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Dec 2018 17:00:10 +0300 Subject: [PATCH 02/12] Updated on 2026-08-14 --- .../java/com/tangem/domain/wallet/bch/BtcCashEngine.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index ae60967b3c..c050ba8d8a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -167,9 +167,7 @@ public class BtcCashEngine extends CoinEngine { // // return true; - if(CashAddr.isValidCashAddress(address)) - return true; - return false; + return CashAddr.isValidCashAddress(address); } @Override @@ -179,7 +177,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getShareWalletUriExplorer() { - return Uri.parse((ctx.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + ctx.getCoinData().getWallet()); + return Uri.parse("https://bch.btc.com/" + ctx.getCoinData().getWallet()); } @Override From 9377178b127c1394ee78082bc77cf61b93273e0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Dec 2018 10:06:04 +0300 Subject: [PATCH 03/12] Updated on 2026-08-14 --- .../java/com/tangem/domain/wallet/eth/EthData.java | 14 ++++++++++---- .../com/tangem/domain/wallet/token/TokenData.java | 11 +++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthData.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthData.java index df44ecef13..7921eaf1c6 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthData.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthData.java @@ -62,8 +62,12 @@ public class EthData extends CoinData { public void loadFromBundle(Bundle B) { super.loadFromBundle(B); - String currency = B.getString("BalanceCurrency"); - balance = new CoinEngine.InternalAmount(B.getString("BalanceDecimal"), currency); + if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) { + String currency = B.getString("BalanceCurrency"); + balance = new CoinEngine.InternalAmount(B.getString("BalanceDecimal"), currency); + } else { + balance = null; + } if (B.containsKey("confirmTx")) countConfirmedTX = new BigInteger(B.getString("confirmTx"), 16); @@ -75,8 +79,10 @@ public class EthData extends CoinData { public void saveToBundle(Bundle B) { super.saveToBundle(B); try { - B.putString("BalanceCurrency", balance.getCurrency()); - B.putString("BalanceDecimal", balance.toString()); + if (balance != null) { + B.putString("BalanceCurrency", balance.getCurrency()); + B.putString("BalanceDecimal", balance.toString()); + } B.putString("confirmTx", getConfirmedTXCount().toString(16)); B.putString("unconfirmTx", getUnconfirmedTXCount().toString(16)); diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java index b0bfd6cd50..3c653824ee 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java @@ -20,6 +20,7 @@ public class TokenData extends EthData { return balanceAlter; } + public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) { balanceAlter = value; } @@ -28,7 +29,11 @@ public class TokenData extends EthData { public void loadFromBundle(Bundle B) { super.loadFromBundle(B); - balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"),"wei"); + if (B.containsKey("BalanceDecimalAlter")) { + balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei"); + } else { + balanceAlter = null; + } } @Override @@ -36,7 +41,9 @@ public class TokenData extends EthData { super.saveToBundle(B); try { - B.putString("BalanceDecimalAlter", balanceAlter.toString()); + if (balanceAlter != null) { + B.putString("BalanceDecimalAlter", balanceAlter.toString()); + } } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } From dd343fa9924a76776878e6a89bb1c414190bc784 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Dec 2018 12:01:12 +0300 Subject: [PATCH 04/12] Updated on 2026-08-14 --- .../tangem/data/network/ElectrumRequest.java | 3 +- .../activity/ConfirmPaymentActivity.kt | 107 +++++++++++------- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java b/app/src/main/java/com/tangem/data/network/ElectrumRequest.java index 4c3f05a3c2..aa8839cc28 100644 --- a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java +++ b/app/src/main/java/com/tangem/data/network/ElectrumRequest.java @@ -84,10 +84,9 @@ public class ElectrumRequest { } - public static ElectrumRequest getFee(String wallet) { + public static ElectrumRequest getFee() { ElectrumRequest request = new ElectrumRequest(); try { - request.walletAddress = wallet; //METHOD_GetFee request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }"); } catch (JSONException e) { e.printStackTrace(); diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt index 93dc3ab3af..fd4377877a 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt @@ -13,6 +13,7 @@ import android.util.Log import android.view.KeyEvent import android.view.View import android.widget.Toast +import org.json.JSONException import com.tangem.data.network.ElectrumRequest import com.tangem.data.network.ServerApiCommon import com.tangem.data.network.ServerApiElectrum @@ -27,6 +28,7 @@ import com.tangem.tangemcard.data.loadFromBundle import com.tangem.tangemcard.util.Util import com.tangem.util.* import com.tangem.wallet.R +import com.tangem.wallet.R.string.fee import kotlinx.android.synthetic.main.activity_confirm_payment.* import java.io.IOException import java.math.BigDecimal @@ -105,6 +107,13 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { requestInfura(ServerApiInfura.INFURA_ETH_GAS_PRICE) + } else if (ctx.blockchain == Blockchain.BitcoinCash) { + rgFee.isEnabled = false + + progressBar.visibility = View.VISIBLE + + requestElectrum(ctx, ElectrumRequest.getFee()) + } else { rgFee.isEnabled = true @@ -193,38 +202,48 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } // request electrum listener -// val electrumBodyListener: ServerApiHelperElectrum.ElectrumRequestDataListener = object : ServerApiHelperElectrum.ElectrumRequestDataListener { -// override fun onSuccess(electrumRequest: ElectrumRequest?) { -// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) { -// try { -// if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty)) -// val engine = CoinEngineFactory.create(ctx) -// val balance = engine.convertToAmount(CoinEngine.InternalAmount(electrumRequest.result.getLong("confirmed") + electrumRequest.result.getLong("unconfirmed"), "Satoshi")) -// val amount = CoinEngine.Amount(etAmount.text.toString(), ctx.blockchain.currency) -// if (balance < amount) { -// etFee.error = getString(R.string.not_enough_funds) -// } else { -// etFee.error = null -// balanceRequestSuccess = true -// if (feeRequestSuccess && balanceRequestSuccess) { -// btnSend.visibility = View.VISIBLE -// } -// dtVerified = Date() -// nodeCheck = true -// } -// } catch (e: JSONException) { -// e.printStackTrace() -//// requestElectrum(ctx.card!!, ElectrumRequest.checkBalance(ctx.card!!.wallet)) -// } -// } -// } -// -// override fun onFail(message: String?) { -// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes)) -// } -// -// } -// serverApiHelperElectrum.setElectrumRequestData(electrumBodyListener) + val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { + override fun onSuccess(electrumRequest: ElectrumRequest?) { + var fee: BigDecimal + if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetFee)) { + try { + //if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty)) + fee = BigDecimal(electrumRequest.resultString) //fee per KB + + if (fee == BigDecimal.ZERO) { + requestElectrum(ctx, ElectrumRequest.getFee()) + } + + if (calcSize.toLong() != 0L) { + fee = fee.multiply(BigDecimal(calcSize.toLong())).divide(BigDecimal(1024)) // (per KB -> per byte)*size + } else { + requestElectrum(ctx, ElectrumRequest.getFee()) + } + progressBar.visibility = View.INVISIBLE + + fee = fee.setScale(8, RoundingMode.DOWN) + var feeAmount: CoinEngine.Amount = CoinEngine.Amount(fee, "BCH") + minFee = feeAmount + normalFee = feeAmount + maxFee = feeAmount + doSetFee(rgFee.checkedRadioButtonId) + etFee.error = null + btnSend.visibility = View.VISIBLE + feeRequestSuccess = true + dtVerified = Date() + + } catch (e: JSONException) { + e.printStackTrace() + } + } + } + + override fun onFail(message: String?) { + finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes)) + } + + } + serverApiElectrum.setElectrumRequestData(electrumBodyListener) // request infura eth gasPrice listener val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener { @@ -251,7 +270,6 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { etFee.error = null btnSend.visibility = View.VISIBLE feeRequestSuccess = true -// balanceRequestSuccess = true dtVerified = Date() } } @@ -270,7 +288,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { // request estimate fee listener val estimateFeeListener: ServerApiCommon.EstimateFeeListener = object : ServerApiCommon.EstimateFeeListener { override fun onSuccess(blockCount: Int, estimateFeeResponse: String?) { - var fee: BigDecimal? + var fee: BigDecimal fee = BigDecimal(estimateFeeResponse) // BTC per 1 kb if (fee == BigDecimal.ZERO) { @@ -286,7 +304,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { progressBar.visibility = View.INVISIBLE - fee = fee!!.setScale(8, RoundingMode.DOWN) + fee = fee.setScale(8, RoundingMode.DOWN) when (blockCount) { ServerApiCommon.ESTIMATE_FEE_MINIMAL -> { @@ -307,9 +325,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { etFee.error = null feeRequestSuccess = true - if (feeRequestSuccess) -// if (feeRequestSuccess && balanceRequestSuccess) - btnSend.visibility = View.VISIBLE + btnSend.visibility = View.VISIBLE dtVerified = Date() } @@ -458,6 +474,16 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { + if( calcSize==0 ) + { + calcSize = 256 + try { + + calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount.text.toString()) + } catch (ex: Exception) { + Log.e("Build Fee error", ex.message) + } + } if (UtilHelper.isOnline(this)) { serverApiElectrum.electrumRequestData(ctx, electrumRequest) } else @@ -487,7 +513,10 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL) } - private fun doSetFee(checkedRadioButtonId: Int) { + private fun + + + doSetFee(checkedRadioButtonId: Int) { var txtFee = "" when (checkedRadioButtonId) { R.id.rbMinimalFee -> From 5bc2036183ccafacfabeea747de9f04a89a4b4a2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Dec 2018 16:23:07 +0300 Subject: [PATCH 05/12] Updated on 2026-08-14 --- .../dialog/WaitSecurityDelayDialog.java | 155 +++++++++--------- 1 file changed, 78 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialog.java b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialog.java index 8e61a1087b..b7184c9444 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialog.java +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialog.java @@ -3,12 +3,11 @@ package com.tangem.presentation.dialog; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; -import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; -import android.view.WindowManager; import android.widget.ProgressBar; import com.tangem.wallet.R; @@ -20,24 +19,16 @@ import java.util.TimerTask; * Created by dvol on 06.03.2018. */ public class WaitSecurityDelayDialog extends DialogFragment { - private static final String TAG = WaitSecurityDelayDialog.class.getSimpleName(); - - private ProgressBar progressBar; - private int msTimeout = 60000, msProgress = 0; - private Timer timer; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + ProgressBar progressBar; + int msTimeout = 60000, msProgress = 0; + Timer timer; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = getActivity().getLayoutInflater(); - // Inflate and set t he layout for the dialog + // Inflate and set the layout for the dialog // Pass null as the parent view because its going in the dialog layout View v = inflater.inflate(R.layout.dialog_wait_pin2, null); @@ -49,17 +40,20 @@ public class WaitSecurityDelayDialog extends DialogFragment { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { - progressBar.post(() -> { - int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); - if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) { - WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000); + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) { + WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000); + } } }); } }, 1000, 1000); return new AlertDialog.Builder(getActivity()) .setIcon(R.drawable.tangem_logo_small_new) - .setTitle(R.string.security_delay) + .setTitle("Security delay") .setView(v) .setCancelable(false) .create(); @@ -76,19 +70,22 @@ public class WaitSecurityDelayDialog extends DialogFragment { } public void setRemainingTimeout(final int msec) { - progressBar.post(() -> { - int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); - if (timer != null) { - // we get delay latency from card for first time - don't change progress by timer, only by card answer - progressBar.setMax(progress + msec); - timer.cancel(); - timer = null; - } else { - int newProgress = progressBar.getMax() - msec; - if (newProgress > progress) { - progressBar.setProgress(newProgress); - } else { + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (timer != null) { + // we get delay latency from card for first time - don't change progress by timer, only by card answer progressBar.setMax(progress + msec); + timer.cancel(); + timer = null; + } else { + int newProgress = progressBar.getMax() - msec; + if (newProgress > progress) { + progressBar.setProgress(newProgress); + } else { + progressBar.setMax(progress + msec); + } } } }); @@ -104,64 +101,68 @@ public class WaitSecurityDelayDialog extends DialogFragment { return instance; } - private final static int MinRemainingDelayToShowDialog = 1000; - private final static int DelayBeforeShowDialog = 5000; + private final static int MinRemainingDelayToShowDialog=1000; + private final static int DelayBeforeShowDialog=5000; public static void onReadBeforeRequest(final Activity activity, final int timeout) { - activity.runOnUiThread(() -> { - if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog + MinRemainingDelayToShowDialog) - return; - timerToShowDelayDialog = new Timer(); - timerToShowDelayDialog.schedule(new TimerTask() { - @Override - public void run() { - if (WaitSecurityDelayDialog.instance != null) return; - instance = new WaitSecurityDelayDialog(); - instance.setup(timeout, DelayBeforeShowDialog); - instance.setCancelable(false); - if (instance.getFragmentManager() != null) - instance.show(instance.getFragmentManager(), TAG); - } - }, DelayBeforeShowDialog); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog+MinRemainingDelayToShowDialog) return; + timerToShowDelayDialog = new Timer(); + timerToShowDelayDialog.schedule(new TimerTask() { + @Override + public void run() { + if (WaitSecurityDelayDialog.instance != null) return; + instance = new WaitSecurityDelayDialog(); + instance.setup(timeout, DelayBeforeShowDialog); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + }, DelayBeforeShowDialog); + } }); } public static void onReadAfterRequest(final Activity activity) { - activity.runOnUiThread(() -> { - if (timerToShowDelayDialog == null) return; - timerToShowDelayDialog.cancel(); - timerToShowDelayDialog = null; + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog == null) return; + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; + } }); } public static void OnReadWait(final Activity activity, final int msec) { - activity.runOnUiThread(() -> { - if (timerToShowDelayDialog != null) { - timerToShowDelayDialog.cancel(); - timerToShowDelayDialog = null; - } - - if (msec == 0) { - if (instance != null) { - // TODO java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.FragmentTransaction android.support.v4.app.FragmentManager.beginTransaction()' on a null object reference - instance.dismiss(); - instance = null; + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null) { + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; } - return; - } - if (instance == null) { - if (msec > MinRemainingDelayToShowDialog) { - instance = new WaitSecurityDelayDialog(); - // 1000ms - card delay notification interval - instance.setup(msec + 1000, 1000); - instance.setCancelable(false); - if (instance.getFragmentManager() != null) - instance.show(instance.getFragmentManager(), TAG); + if (msec == 0) { + if (instance != null) { + instance.dismiss(); + instance = null; + } + return; } - } else - instance.setRemainingTimeout(msec); + if (instance == null) { + if( msec>MinRemainingDelayToShowDialog ) { + instance = new WaitSecurityDelayDialog(); + // 1000ms - card delay notification interval + instance.setup(msec + 1000, 1000); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + } else { + instance.setRemainingTimeout(msec); + } + } }); } - -} \ No newline at end of file +} From 4e11cfcec0b76becd4f491ab34fccfdc3f8d0ca2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Dec 2018 16:47:09 +0300 Subject: [PATCH 06/12] Updated on 2026-08-14 --- .../java/com/tangem/domain/wallet/CoinEngine.java | 7 +------ .../tangem/domain/wallet/bch/BtcCashEngine.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java index f97c7c217e..99fb710d2a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java @@ -236,12 +236,7 @@ public abstract class CoinEngine { public void defineWallet() throws CardProtocol.TangemException { try { - String wallet; - if (ctx.getBlockchain() == Blockchain.BitcoinCash) { - wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); - } else { - wallet = calculateAddress(ctx.getCard().getWalletPublicKey()); - } + String wallet = calculateAddress(ctx.getCard().getWalletPublicKey()); ctx.getCoinData().setWallet(wallet); } catch (Exception e) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index c050ba8d8a..8fd87ee969 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -424,6 +424,20 @@ public class BtcCashEngine extends CoinEngine { return coinData.getUnspentInputsDescription(); } + @Override + public void defineWallet() throws CardProtocol.TangemException { + try { + String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); + ctx.getCoinData().setWallet(wallet); + } + catch (Exception e) + { + ctx.getCoinData().setWallet("ERROR"); + throw new CardProtocol.TangemException("Can't define wallet address"); + } + + } + // @Override // public String getAmountDescription(TangemCard mCard, String amount) throws Exception { // return mCard.getAmountDescription(Double.parseDouble(amount)); From caa3140c0e77272e282beabbc38ff0007f2c2b15 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Dec 2018 13:29:48 +0300 Subject: [PATCH 07/12] Updated on 2026-08-14 --- app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java index 5005784da1..8a256f69bc 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java @@ -51,6 +51,7 @@ public class CashAddr { if (!isValidCashAddress(bitcoinCashAddress)) { throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress); } + bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts(); String[] addressParts = bitcoinCashAddress.split(SEPARATOR); From b3b77a788636d7f185f8aa7b4aab60cda4199839 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Dec 2018 18:12:07 +0300 Subject: [PATCH 08/12] Updated on 2026-08-14 --- .../tangem/domain/wallet/bch/CashAddr.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java index 8a256f69bc..d73f88f8d7 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/CashAddr.java @@ -51,7 +51,6 @@ public class CashAddr { if (!isValidCashAddress(bitcoinCashAddress)) { throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress); } - bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts(); String[] addressParts = bitcoinCashAddress.split(SEPARATOR); @@ -87,19 +86,21 @@ public class CashAddr { public static boolean isValidCashAddress(String bitcoinCashAddress ) { try { - String prefix; - if (bitcoinCashAddress.contains(SEPARATOR)) { - String[] split = bitcoinCashAddress.split(SEPARATOR); - prefix = split[0]; - bitcoinCashAddress = split[1]; - } else { - prefix =MAIN_NET_PREFIX; - } - if (!isSingleCase(bitcoinCashAddress)) return false; bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); + String prefix; + + if (bitcoinCashAddress.contains(SEPARATOR)) { + String[] split = bitcoinCashAddress.split(SEPARATOR); + prefix = split[0]; + if (!prefix.equals(MAIN_NET_PREFIX)) {return false;} //for now we use main net only + bitcoinCashAddress = split[1]; + } else { + prefix = MAIN_NET_PREFIX; + } + if (!bitcoinCashAddress.startsWith("q")) {return false;} //for now we use P2PKH addresses only byte[] checksumData = concatenateByteArrays( concatenateByteArrays(getPrefixBytes(prefix ), new byte[] { 0x00 }), From 406cdbaeae1aee4a0acd699450be5c8e103413c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Dec 2018 18:46:13 +0300 Subject: [PATCH 09/12] Updated on 2026-08-14 --- .../presentation/activity/ConfirmPaymentActivity.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt index fd4377877a..bc43826b87 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt @@ -219,9 +219,16 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } else { requestElectrum(ctx, ElectrumRequest.getFee()) } - progressBar.visibility = View.INVISIBLE + progressBar.visibility = View.INVISIBLE + val relayFee : BigDecimal = BigDecimal(0.00001) + + //compare fee to usual relay fee + if (fee.compareTo(relayFee) == -1) { + fee = relayFee + } fee = fee.setScale(8, RoundingMode.DOWN) + var feeAmount: CoinEngine.Amount = CoinEngine.Amount(fee, "BCH") minFee = feeAmount normalFee = feeAmount From d270021a088d0a18d58519cb997b37fc9812c7ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Dec 2018 13:57:13 +0300 Subject: [PATCH 10/12] Updated on 2026-08-14 --- .../main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index 8fd87ee969..0dac247d88 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -320,10 +320,10 @@ public class BtcCashEngine extends CoinEngine { } @Override - public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + public String calculateAddress(byte[] pubKey) throws NoSuchProviderException, NoSuchAlgorithmException { // CashAddr format - byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash1[] = Util.calculateSHA256(pubKey); byte hash2[] = Util.calculateRIPEMD160(hash1); return CashAddr.toCashAddress(BitcoinCashAddressType.P2PKH, hash2); From ef355397fa2f23dcfdd3c49c9b685f40ae9b4a1e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Dec 2018 18:57:43 +0300 Subject: [PATCH 11/12] Updated on 2026-08-14 --- .../main/java/com/tangem/data/Blockchain.java | 3 +- .../data/network/ServerApiElectrum.java | 15 + .../tangem/domain/wallet/CoinEngineFactory.kt | 4 + .../domain/{ => wallet/ltc}/LitecoinNode.kt | 0 .../tangem/domain/wallet/ltc/LtcEngine.java | 570 ++++++++++++++++++ .../activity/ConfirmPaymentActivity.kt | 4 +- .../presentation/fragment/LoadedWallet.kt | 2 +- 7 files changed, 594 insertions(+), 4 deletions(-) rename app/src/main/java/com/tangem/domain/{ => wallet/ltc}/LitecoinNode.kt (100%) create mode 100644 app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 28d38c45df..5ad40fedc9 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -12,7 +12,8 @@ public enum Blockchain { Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"), Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"), - BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"); + BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), + Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java index 227a6d9812..d23306cd81 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java @@ -8,6 +8,7 @@ import com.tangem.data.Blockchain; import com.tangem.domain.wallet.bch.BitcoinCashNode; import com.tangem.domain.wallet.btc.BitcoinNode; import com.tangem.domain.wallet.btc.BitcoinNodeTestNet; +import com.tangem.domain.wallet.ltc.LitecoinNode; import java.io.BufferedReader; import java.io.IOException; @@ -144,6 +145,20 @@ public class ServerApiElectrum { this.host = host; this.port = port; + if (proto.equals("tcp")) { + return doElectrumRequestTcp(electrumRequest, host, port); + } else { + return doElectrumRequestSsl(electrumRequest, host, port); + } + } else if (ctx.getBlockchain() == Blockchain.Litecoin) { + LitecoinNode litecoinNode = LitecoinNode.values()[new Random().nextInt(LitecoinNode.values().length)]; + host = litecoinNode.getHost(); + port = litecoinNode.getPort(); + proto = litecoinNode.getProto(); + + this.host = host; + this.port = port; + if (proto.equals("tcp")) { return doElectrumRequestTcp(electrumRequest, host, port); } else { diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt index 2d8e4f64ab..951b7eb457 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt @@ -7,6 +7,7 @@ import com.tangem.domain.wallet.eth.EthEngine import com.tangem.domain.wallet.token.TokenEngine import com.tangem.domain.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain +import com.tangem.domain.wallet.ltc.LtcEngine /** * Factory for create specific engine @@ -25,6 +26,7 @@ object CoinEngineFactory { Blockchain.BitcoinCash -> BtcCashEngine() Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine() Blockchain.Token -> TokenEngine() + Blockchain.Litecoin -> LtcEngine() else -> null } } @@ -40,6 +42,8 @@ object CoinEngineFactory { EthEngine(context) else if (Blockchain.Token == context.blockchain) TokenEngine(context) + else if (Blockchain.Litecoin == context.blockchain) + LtcEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/domain/LitecoinNode.kt b/app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt similarity index 100% rename from app/src/main/java/com/tangem/domain/LitecoinNode.kt rename to app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt diff --git a/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java new file mode 100644 index 0000000000..9de4a1dfab --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java @@ -0,0 +1,570 @@ +package com.tangem.domain.wallet.ltc; + +import android.net.Uri; +import android.text.InputFilter; + +import com.tangem.data.Blockchain; +import com.tangem.domain.wallet.BTCUtils; +import com.tangem.domain.wallet.BalanceValidator; +import com.tangem.domain.wallet.Base58; +import com.tangem.domain.wallet.CoinData; +import com.tangem.domain.wallet.CoinEngine; +import com.tangem.domain.wallet.TangemContext; +import com.tangem.domain.wallet.Transaction; +import com.tangem.domain.wallet.UnspentOutputInfo; +import com.tangem.domain.wallet.btc.BtcData; +import com.tangem.tangemcard.data.TangemCard; +import com.tangem.tangemcard.reader.CardProtocol; +import com.tangem.tangemcard.tasks.SignTask; +import com.tangem.tangemcard.util.Util; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.util.DerEncodingUtil; +import com.tangem.wallet.R; + +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.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class LtcEngine extends CoinEngine { + + public BtcData coinData = null; + + public LtcEngine(TangemContext context) throws Exception { + super(context); + if (context.getCoinData() == null) { + coinData = new BtcData(); + context.setCoinData(coinData); + } else if (context.getCoinData() instanceof BtcData) { + coinData = (BtcData) context.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for LtcEngine"); + } + } + + public LtcEngine() { + super(); + } + + private static int getDecimals() { + return 8; + } + + + private void checkBlockchainDataExists() throws Exception { + if (coinData == null) throw new Exception("No blockchain data"); + } + + @Override + public boolean awaitingConfirmation() { + if (coinData == null) return false; + return coinData.getBalanceUnconfirmed() != 0; + } + + @Override + public String getBalanceHTML() { + Amount balance = getBalance(); + if (balance != null) { + return balance.toDescriptionString(getDecimals()); + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + return "LTC"; + } + + @Override + public String getOfflineBalanceHTML() { + InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); + Amount offlineAmount = convertToAmount(offlineInternalAmount); + return offlineAmount.toDescriptionString(getDecimals()); + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getBalanceInInternalUnits() == null) return false; + return coinData.getBalanceInInternalUnits().notZero(); + } + + @Override + public boolean hasBalanceInfo() { + if (coinData == null) return false; + return coinData.hasBalanceInfo(); + } + + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.cannot_obtain_data_from_blockchain); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.please_wait_while_previous); + } else if (coinData.getUnspentTransactions().size() == 0) { + ctx.setMessage(R.string.please_wait_for_confirmation); + } else { + return true; + } + return false; + } + + @Override + public String getFeeCurrency() { + return "LTC"; + } + + @Override + public boolean validateAddress(String address) { + if (address == null || address.isEmpty()) { + return false; + } + + if (address.length() < 25) { + return false; + } + + if (address.length() > 35) { + return false; + } + + if (!address.startsWith("L") && !address.startsWith("M")) { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if (decAddress == null || decAddress.length == 0) { + return false; + } + + byte[] rip = new byte[21]; + for (int i = 0; i < 21; ++i) { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for (int i = 0; i < 4; ++i) { + if (kcv[i] != decAddress[21 + i]) + return false; + } + + return true; + } + + + @Override + public boolean isNeedCheckNode() { + return true; + } + + @Override + public Uri getShareWalletUriExplorer() { + return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCoinData().getWallet()); + } + + @Override + public Uri getShareWalletUri() { + if (ctx.getCard().getDenomination() != null) { + return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + } else { + return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet()); + } + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { + InternalAmount fee; + InternalAmount amount; + + try { + checkBlockchainDataExists(); + amount = convertToInternalAmount(amountValue); + fee = convertToInternalAmount(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if (fee == null || amount == null) + return false; + + if (fee.isZero() || amount.isZero()) + return false; + + if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0)) + return false; + + if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0) + return false; + + return true; + } + + @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 (coinData.getBalanceUnconfirmed() != 0) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Transaction in progress"); + balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + return false; + } + + if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine("Verified balance"); + balanceValidator.setSecondLine("Balance confirmed in blockchain"); + if (coinData.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) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.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 Amount getBalance() { + if (!hasBalanceInfo()) return null; + return convertToAmount(coinData.getBalanceInInternalUnits()); + } + + @Override + public String evaluateFeeEquivalent(String fee) { + if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; + try { + Amount feeAmount = new Amount(fee, getFeeCurrency()); + return feeAmount.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + return ""; + } + } + + @Override + public String getBalanceEquivalent() { + if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + byte netSelectionByte = (byte) 0x30; + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(new BigDecimal("100000000")); + return new Amount(d, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) throws Exception { + BigDecimal d = amount.multiply(new BigDecimal("100000000")); + return new InternalAmount(d, "Satoshi"); + } + + @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), "Satoshi"); + } + + @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 CoinData createCoinData() { + return new BtcData(); + } + + @Override + public String getUnspentInputsDescription() { + return coinData.getUnspentInputsDescription(); + } + + @Override + public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + final ArrayList unspentOutputs; + checkBlockchainDataExists(); + + String myAddress = ctx.getCoinData().getWallet(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + + // Build script for our address + List rawTxList = coinData.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + long fees = convertToInternalAmount(feeValue).longValueExact(); + long amount = convertToInternalAmount(amountValue).longValueExact(); + long change = fullAmount - amount; + if (IncFee) { + amount = amount - fees; + } else { + change = change - fees; + } + + final long amountFinal=amount; + final long changeFinal=change; + + if (amount + fees > fullAmount) { + throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); + } + + final byte[][] txForSign = new byte[unspentOutputs.size()][]; + final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; + final byte[][] bodyHash= new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); + bodyHash[i] = Util.calculateSHA256(txForSign[i]); + bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); + } + + return new SignTask.PaymentToSign() { + + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw; + } + + @Override + public byte[][] getHashesToSign() throws Exception { + byte[][] dataForSign=new byte[unspentOutputs.size()][]; + if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < unspentOutputs.size(); ++i) { + dataForSign[i] = bodyDoubleHash[i]; + } + return dataForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + for (int i = 0; i < txForSign.length; i++) { + if (i != 0 && txForSign[0].length != txForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(txForSign[i]); + } + + return bs.toByteArray(); + } + + @Override + public String getHashAlgToSign() { + return "sha-256x2"; + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Issuer validation not supported!"); + } + + @Override + public void onSignCompleted(byte[] signFromCard) throws Exception { + for (int i = 0; i < unspentOutputs.size(); ++i) { + 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); + + unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); + } + + byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); + notifyOnNeedSendPayment(txForSend); + } + }; + } + +// @Override +// public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { +// +// checkBlockchainDataExists(); +// +// String myAddress = ctx.getCoinData().getWallet(); +// byte[] pbKey = ctx.getCard().getWalletPublicKey(); +// +// // Build script for our address +// List rawTxList = coinData.getUnspentTransactions(); +// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; +// +// // Collect unspent +// ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); +// +// long fullAmount = 0; +// for (int i = 0; i < unspentOutputs.size(); ++i) { +// fullAmount += unspentOutputs.get(i).value; +// } +// +// +// long fees = convertToInternalAmount(feeValue).longValueExact(); +// long amount = convertToInternalAmount(amountValue).longValueExact(); +// long change = fullAmount - amount; +// if (IncFee) { +// amount = amount - fees; +// } else { +// change = change - fees; +// } +// +// if (amount + fees > fullAmount) { +// throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); +// } +// +// byte[][] dataForSign = new byte[unspentOutputs.size()][]; +// +// for (int i = 0; i < unspentOutputs.size(); ++i) { +// byte[] newTX = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); +// +// byte[] hashData = Util.calculateSHA256(newTX); +// byte[] doubleHashData = Util.calculateSHA256(hashData); +// +// unspentOutputs.get(i).bodyDoubleHash = doubleHashData; +// unspentOutputs.get(i).bodyHash = hashData; +// +// 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; +// } +// } +// +// 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++) { +// if (i != 0 && dataForSign[0].length != dataForSign[i].length) +// throw new Exception("Hashes length must be identical!"); +// bs.write(dataForSign[i]); +// } +// signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), "sha-256x2",bs.toByteArray(),null,null,null).getTLV(TLV.Tag.TAG_Signature).Value; +// } else { +// //ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer() +// signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, null, null, null).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, i * 64, 32 + i * 64)); +// BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); +// s = CryptoUtil.toCanonicalised(s); +// +// unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); +// } +// +// return BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amount, change); +// } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt index bc43826b87..097a078c21 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt @@ -107,7 +107,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { requestInfura(ServerApiInfura.INFURA_ETH_GAS_PRICE) - } else if (ctx.blockchain == Blockchain.BitcoinCash) { + } else if (ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Litecoin) { rgFee.isEnabled = false progressBar.visibility = View.VISIBLE @@ -229,7 +229,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } fee = fee.setScale(8, RoundingMode.DOWN) - var feeAmount: CoinEngine.Amount = CoinEngine.Amount(fee, "BCH") + var feeAmount: CoinEngine.Amount = CoinEngine.Amount(fee, ctx.blockchain.currency) minFee = feeAmount normalFee = feeAmount maxFee = feeAmount diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt index 528d9299af..0e5567c3e8 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -808,7 +808,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific requestVerifyAndGetInfo() // Bitcoin - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) { + if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.Litecoin) { ctx.coinData.setIsBalanceEqual(true) requestElectrum(ElectrumRequest.checkBalance(ctx.coinData!!.wallet)) From 828e0cca5a4ef95f82848ff43f8e475f0ec222a4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Dec 2018 18:32:08 +0300 Subject: [PATCH 12/12] Updated on 2026-08-14 --- .../java/com/tangem/domain/wallet/Transaction.java | 8 ++++---- .../java/com/tangem/domain/wallet/ltc/LtcEngine.java | 6 +++--- .../presentation/activity/SendTransactionActivity.kt | 4 +--- .../com/tangem/presentation/fragment/LoadedWallet.kt | 11 ++++++++++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/Transaction.java b/app/src/main/java/com/tangem/domain/wallet/Transaction.java index 99fa23ccdd..792a116802 100644 --- a/app/src/main/java/com/tangem/domain/wallet/Transaction.java +++ b/app/src/main/java/com/tangem/domain/wallet/Transaction.java @@ -566,11 +566,11 @@ public final class Transaction { public static Script buildOutput(String address) throws BitcoinException { //noinspection TryWithIdenticalCatches byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111) { + if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48) { //0 for BTC/BCH 1 address | 48 for LTC L address return buildOutputP2H(address); } - if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4) { + if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4 || addressWithCheckSumAndNetworkCode[0] == 50) { //5 for BTC/BCH/LTC 3 address | 50 for LTC M address return buildOutputP2SH(address); } @@ -579,7 +579,7 @@ public final class Transaction { public static Script buildOutputP2SH(String address) throws BitcoinException { try { byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4) { + if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4 && addressWithCheckSumAndNetworkCode[0] != 50) { throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); } @@ -601,7 +601,7 @@ public final class Transaction { //noinspection TryWithIdenticalCatches try { byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address); - if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111) { + if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48) { throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address); } diff --git a/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java index 9de4a1dfab..9fe1ccd95d 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java @@ -171,15 +171,15 @@ public class LtcEngine extends CoinEngine { @Override public Uri getShareWalletUriExplorer() { - return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCoinData().getWallet()); + return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet()); } @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet()); + return Uri.parse("litecoin:" + ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt index f91991a62e..1cb007715d 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt @@ -49,9 +49,7 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) requestInfura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, "") - else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) - requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) - else if (ctx.blockchain == Blockchain.BitcoinCash) + else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Litecoin) requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) // request electrum listener diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt index 0e5567c3e8..8246e51dcc 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -808,7 +808,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific requestVerifyAndGetInfo() // Bitcoin - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.Litecoin) { + if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) { ctx.coinData.setIsBalanceEqual(true) requestElectrum(ElectrumRequest.checkBalance(ctx.coinData!!.wallet)) @@ -816,6 +816,15 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific requestRateInfo("bitcoin") } + // Litecoin + if (ctx.blockchain == Blockchain.Litecoin) { + ctx.coinData.setIsBalanceEqual(true) + + requestElectrum(ElectrumRequest.checkBalance(ctx.coinData!!.wallet)) + requestElectrum(ElectrumRequest.listUnspent(ctx.coinData!!.wallet)) + requestRateInfo("litecoin") + } + // BitcoinCash else if (ctx.blockchain == Blockchain.BitcoinCash) { ctx.coinData.setIsBalanceEqual(true)