Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-02 09:39:59 +00:00
commit 5cd82605ce
10 changed files with 328 additions and 266 deletions

View file

@ -0,0 +1,27 @@
package com.tangem.data.network;
import com.tangem.data.network.model.BlockchairAddressResponse;
import com.tangem.data.network.model.BlockchairSendBody;
import com.tangem.data.network.model.BlockchairStatsResponse;
import com.tangem.data.network.model.BlockchairTransactionResponse;
import io.reactivex.Completable;
import io.reactivex.Single;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
public interface BlockchairApi {
@GET(Server.ApiBlockchair.Method.ADDRESS)
Single<BlockchairAddressResponse> getAddress(@Path("blockchain") String blockchain, @Path("address") String address);
@GET(Server.ApiBlockchair.Method.TRANSACTION)
Single<BlockchairTransactionResponse> getTransaction(@Path("blockchain") String blockchain, @Path("transaction") String transaction);
@GET(Server.ApiBlockchair.Method.STATS)
Single<BlockchairStatsResponse> getStats(@Path("blockchain") String blockchain);
@POST(Server.ApiBlockchair.Method.PUSH)
Completable sendTransaction(@Path("blockchain") String blockchain, @Body BlockchairSendBody body);
}

View file

@ -139,4 +139,15 @@ public class Server {
static final String SEND = URL_DUCATUS + "tx/send";
}
}
public static class ApiBlockchair {
public static final String URL_BLOCKCHAIR = ServerURL.API_BLOCKCHAIR + "{blockchain}/";
public static class Method {
static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}";
static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}";
static final String STATS = URL_BLOCKCHAIR + "stats";
static final String PUSH = URL_BLOCKCHAIR + "push/transaction";
}
}
}

View file

@ -0,0 +1,73 @@
package com.tangem.data.network;
import com.tangem.App;
import com.tangem.data.Blockchain;
import com.tangem.data.network.model.BlockchairAddressResponse;
import com.tangem.data.network.model.BlockchairSendBody;
import com.tangem.data.network.model.BlockchairStatsResponse;
import com.tangem.data.network.model.BlockchairTransactionResponse;
import com.tangem.tangem_card.util.Log;
import io.reactivex.Completable;
import io.reactivex.CompletableObserver;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class ServerApiBlockchair {
private static String TAG = ServerApiBlockchair.class.getSimpleName();
private String blockchain;
public ServerApiBlockchair(Blockchain blockchain) {
if (blockchain == Blockchain.BitcoinCash) this.blockchain = "bitcoin-cash";
}
public void getAddress(String wallet, SingleObserver<BlockchairAddressResponse> addressObserver) {
Log.i(TAG, "new getAddress request");
BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class);
Single<BlockchairAddressResponse> addressSingle = api.getAddress(blockchain, wallet)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
addressSingle.subscribe(addressObserver);
}
public void getTransaction(String transaction, SingleObserver<BlockchairTransactionResponse> transactionObserver) {
Log.i(TAG, "new getAddress request");
BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class);
Single<BlockchairTransactionResponse> transactionSingle = api.getTransaction(blockchain, transaction)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
transactionSingle.subscribe(transactionObserver);
}
public void getStats(SingleObserver<BlockchairStatsResponse> statsObserver) {
Log.i(TAG, "new getStats request");
BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitBlockchair().create(BlockchairApi.class);
Single<BlockchairStatsResponse> statsSingle = api.getStats(blockchain)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
statsSingle.subscribe(statsObserver);
}
public void sendTransaction(String tx, CompletableObserver sendObserver) {
Log.i(TAG, "new getAddress request");
BlockchairApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(BlockchairApi.class);
Completable sendCompletable = api.sendTransaction(blockchain, new BlockchairSendBody(tx))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
sendCompletable.subscribe(sendObserver);
}
public String getUrl() {
return ServerURL.API_BLOCKCHAIR;
}
}

View file

@ -17,4 +17,5 @@ class ServerURL {
static final String API_STELLAR_TESTNET = "https://horizon-testnet.stellar.org/";
static final String API_BLOCKCHAIN_INFO = "https://blockchain.info/";
static final String API_DUCATUS = "https://ducapi.rocknblock.io/";
static final String API_BLOCKCHAIR = "https://api.blockchair.com/";
}

View file

@ -0,0 +1,69 @@
package com.tangem.data.network.model
import com.google.gson.annotations.SerializedName
data class BlockchairAddressResponse(
@SerializedName("data")
val data: Map<String, BlockchairAddressData>? = null
)
data class BlockchairAddressData(
@SerializedName("address")
val address: BlockchairAddressInfo? = null,
@SerializedName("utxo")
val unspentOutputs: List<BlockchairUnspentOutput>? = null,
@SerializedName("transactions")
val transactions: List<String>
)
data class BlockchairAddressInfo(
@SerializedName("balance")
val balance: Long? = null,
@SerializedName("output_count")
val outputCount: Int? = null,
@SerializedName("unspent_output_count")
val unspentOutputCount: Int? = null
)
data class BlockchairUnspentOutput(
@SerializedName("block_id")
val block: Int? = null,
@SerializedName("transaction_hash")
val transactionHash: String? = null,
@SerializedName("index")
val index: Int? = null,
@SerializedName("value")
val amount: Long? = null
)
data class BlockchairTransactionResponse(
@SerializedName("data")
val data: Map<String, BlockchairTransactionData>? = null
)
data class BlockchairTransactionData(
@SerializedName("transaction")
val transaction: BlockchairTransactionInfo? = null
)
data class BlockchairTransactionInfo(
@SerializedName("block_id")
val block: Int? = null
)
data class BlockchairStatsResponse(
@SerializedName("data")
val data: BlockchairStatsData? = null
)
data class BlockchairStatsData(
@SerializedName("suggested_transaction_fee_per_byte_sat")
val feePerByte: Int? = null
)

View file

@ -0,0 +1,9 @@
package com.tangem.data.network.model;
public class BlockchairSendBody {
private String data;
public BlockchairSendBody(String data) {
this.data = data;
}
}

View file

@ -41,7 +41,9 @@ interface NetworkComponent {
@get:Named(Server.ApiDucatus.URL_DUCATUS)
val retrofitDucatus: Retrofit
@get:Named(Server.ApiBlockchair.URL_BLOCKCHAIR)
val retrofitBlockchair: Retrofit
@get:Named("socket")
val socket: Socket
}

View file

@ -147,6 +147,19 @@ internal class NetworkModule {
return builder.build()
}
@Singleton
@Provides
@Named(Server.ApiBlockchair.URL_BLOCKCHAIR)
fun provideRetrofitBlockchair(): Retrofit {
val builder = Retrofit.Builder()
.baseUrl(Server.ApiBlockchair.URL_BLOCKCHAIR)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
if (BuildConfig.DEBUG)
builder.client(createOkHttpClient())
return builder.build()
}
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(createHttpLoggingInterceptor()).build()
}

View file

@ -205,6 +205,10 @@ public abstract class CoinData {
return sentTransactionsCount;
}
public void setSentTransactionsCount(int sentTransactionsCount) {
this.sentTransactionsCount = sentTransactionsCount;
}
public void incSentTransactionsCount() {
sentTransactionsCount++;
}

View file

@ -5,8 +5,12 @@ import android.text.InputFilter;
import android.util.Log;
import com.tangem.App;
import com.tangem.data.network.ElectrumRequest;
import com.tangem.data.network.ServerApiElectrum;
import com.tangem.data.network.ServerApiBlockchair;
import com.tangem.data.network.model.BlockchairAddressData;
import com.tangem.data.network.model.BlockchairAddressResponse;
import com.tangem.data.network.model.BlockchairStatsResponse;
import com.tangem.data.network.model.BlockchairTransactionResponse;
import com.tangem.data.network.model.BlockchairUnspentOutput;
import com.tangem.tangem_card.data.TangemCard;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.tasks.SignTask;
@ -26,10 +30,6 @@ import com.tangem.wallet.UnspentOutputInfo;
import com.tangem.wallet.btc.BtcData;
import com.tangem.wallet.btc.Unspents;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -41,6 +41,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.reactivex.CompletableObserver;
import io.reactivex.SingleObserver;
import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.observers.DisposableSingleObserver;
public class BtcCashEngine extends CoinEngine {
private static final String TAG = BtcCashEngine.class.getSimpleName();
@ -59,7 +64,6 @@ public class BtcCashEngine extends CoinEngine {
}
public BtcCashEngine() {
}
private static int getDecimals() {
@ -73,7 +77,7 @@ public class BtcCashEngine extends CoinEngine {
@Override
public boolean awaitingConfirmation() {
if (coinData == null) return false;
return coinData.getBalanceUnconfirmed() != 0 || App.pendingTransactionsStorage.hasTransactions(ctx.getCard());
return coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed() || App.pendingTransactionsStorage.hasTransactions(ctx.getCard());
}
@Override
@ -127,50 +131,6 @@ public class BtcCashEngine extends CoinEngine {
@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("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !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;
// }
//
// if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin) {
// return false;
// }
//
// if (ctx.getBlockchain() == Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) {
// return false;
// }
//
// return true;
return CashAddr.isValidCashAddress(address);
}
@ -241,15 +201,7 @@ public class BtcCashEngine extends CoinEngine {
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) {
if (coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed()) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress);
balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation);
@ -265,37 +217,6 @@ public class BtcCashEngine extends CoinEngine {
balanceValidator.setSecondLine(R.string.empty_string);
}
}
// 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()) {
// balanceValidator.setScore(80);
// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
// }
// 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;
}
@ -441,20 +362,13 @@ public class BtcCashEngine extends CoinEngine {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
ctx.getCoinData().setWallet(wallet);
}
catch (Exception e)
{
} 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));
// }
@Override
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
@ -464,12 +378,10 @@ public class BtcCashEngine extends CoinEngine {
String destLegacyAddress = convertToLegacyAddress(targetAddress);
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
// Build script for our address
List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(srcLegacyAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BCHUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
final ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
for (BtcData.UnspentTransaction utxo : coinData.getUnspentTransactions()) {
unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null));
}
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
@ -513,7 +425,8 @@ public class BtcCashEngine extends CoinEngine {
@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!");
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];
}
@ -562,115 +475,95 @@ public class BtcCashEngine extends CoinEngine {
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain());
ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() {
SingleObserver<BlockchairAddressResponse> addressObserver = new DisposableSingleObserver<BlockchairAddressResponse>() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) {
try {
Long confBalance = electrumRequest.getResult().getLong("confirmed");
Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed");
coinData.setBalanceReceived(true);
coinData.setBalanceConfirmed(confBalance);
coinData.setBalanceUnconfirmed(unconfirmedBalance);
coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription());
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
}
}
public void onSuccess(BlockchairAddressResponse addressResponse) {
try {
BlockchairAddressData addressData = addressResponse.getData().get(coinData.getWallet());
if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) {
try {
String walletAddress = electrumRequest.getParams().getString(0);
JSONArray jsUnspentArray = electrumRequest.getResultArray();
try {
coinData.getUnspentTransactions().clear();
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = jsUnspent.getString("tx_hash");
trUnspent.amount = jsUnspent.getLong("value");
trUnspent.outputN = jsUnspent.getInt("height");
coinData.getUnspentTransactions().add(trUnspent);
}
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
coinData.setBalanceConfirmed(addressData.getAddress().getBalance());
coinData.setBalanceUnconfirmed(0L);
coinData.setBalanceReceived(true);
coinData.setValidationNodeDescription(serverApiBlockchair.getUrl());
coinData.setSentTransactionsCount(addressData.getAddress().getOutputCount() - addressData.getAddress().getUnspentOutputCount());
for (BlockchairUnspentOutput utxo : addressData.getUnspentOutputs()) {
if (utxo.getBlock() != -1) {
BtcData.UnspentTransaction unspentTx = new BtcData.UnspentTransaction();
unspentTx.txID = utxo.getTransactionHash();
unspentTx.amount = utxo.getAmount();
unspentTx.outputN = utxo.getIndex();
coinData.getUnspentTransactions().add(unspentTx);
} else {
coinData.setHasUnconfirmed(true);
}
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
Integer height = jsUnspent.getInt("height");
String hash = jsUnspent.getString("tx_hash");
if (height != -1) {
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
} else {
ctx.setError("Terminated by user");
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) {
try {
String txHash = electrumRequest.txHash;
String raw = electrumRequest.getResultString();
for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) {
if (tx.txID.equals(txHash))
tx.script = raw;
}
} catch (JSONException e) {
e.printStackTrace();
if (addressData.getAddress().getBalance() != 0) {
blockchainRequestsCallbacks.onComplete(true);
} else {
requestIsTransactionConfirmed(addressData.getTransactions().get(0), blockchainRequestsCallbacks);
}
}
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
}else{
blockchainRequestsCallbacks.onProgress();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL getAddress Exception");
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError());
ctx.setError(electrumRequest.getError());
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
}else{
blockchainRequestsCallbacks.onProgress();
}
public void onError(Throwable e) {
e.printStackTrace();
Log.e(TAG, "FAIL getAddress Exception");
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setResponseListener(electrumBodyListener);
serverApiBlockchair.getAddress(coinData.getWallet(), addressObserver);
}
serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet())));
serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet())));
private void requestIsTransactionConfirmed(String transaction, BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain());
SingleObserver<BlockchairTransactionResponse> transactionObserver = new DisposableSingleObserver<BlockchairTransactionResponse>() {
@Override
public void onSuccess(BlockchairTransactionResponse transactionResponse) {
if (transactionResponse.getData().get(transaction).getTransaction().getBlock() == -1) {
coinData.setHasUnconfirmed(true);
}
blockchainRequestsCallbacks.onComplete(true);
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
Log.e(TAG, "FAIL getTransaction Exception");
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiBlockchair.getTransaction(transaction, transactionObserver);
}
private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) {
try {
SignTask.TransactionToSign ps= constructTransaction(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress );
SignTask.TransactionToSign ps = constructTransaction(new Amount(outAmount, getBalanceCurrency()), new Amount("0.00", getFeeCurrency()), true, outputAddress);
OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction;
onNeedSendTransaction =(tx)->{}; // empty function to bypass exception
onNeedSendTransaction = (tx) -> {
}; // empty function to bypass exception
byte[][] hashesToSign=ps.getHashesToSign();
byte[][] hashesToSign = ps.getHashesToSign();
byte[] signFromCard = new byte[64 * hashesToSign.length];
Arrays.fill(signFromCard, (byte) 0x01);
byte[] txForSend=ps.onSignCompleted(signFromCard);
byte[] txForSend = ps.onSignCompleted(signFromCard);
onNeedSendTransaction = onNeedSendTransactionBackup;
Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length));
return txForSend.length +1;
Log.e(TAG, "txForSend.length=" + String.valueOf(txForSend.length));
return txForSend.length + 1;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't calculate transaction size -> use default!");
@ -684,104 +577,64 @@ public class BtcCashEngine extends CoinEngine {
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
coinData.minFee=null;
coinData.maxFee=null;
coinData.normalFee=null;
coinData.minFee = null;
coinData.maxFee = null;
coinData.normalFee = null;
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain());
final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() {
SingleObserver<BlockchairStatsResponse> statsObserver = new DisposableSingleObserver<BlockchairStatsResponse>() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
BigDecimal fee;
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) {
try {
fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB
if (fee.equals(BigDecimal.ZERO)) {
serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
}
// if (calcSize != 0) {
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
// } else {
// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
// }
//compare fee to usual relay fee
if (fee.compareTo(relayFee) < 0) {
fee = relayFee;
}
fee = fee.setScale(8, RoundingMode.DOWN);
CoinEngine.Amount feeAmount = new CoinEngine.Amount(fee, ctx.getBlockchain().getCurrency());
coinData.minFee = feeAmount;
coinData.normalFee = feeAmount;
coinData.maxFee = feeAmount;
// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) {
blockchainRequestsCallbacks.onComplete(true);
// } else {
// blockchainRequestsCallbacks.onProgress();
// }
} catch (JSONException e) {
e.printStackTrace();
public void onSuccess(BlockchairStatsResponse blockchairStatsResponse) {
try {
int feeSatoshi = blockchairStatsResponse.getData().getFeePerByte() * calcSize;
BigDecimal fee = BigDecimal.valueOf(feeSatoshi).movePointLeft(getDecimals());
if (fee.compareTo(relayFee) < 0) {
fee = relayFee;
}
Amount feeAmount = new Amount(fee.setScale(getDecimals(), RoundingMode.DOWN), getFeeCurrency());
coinData.minFee = feeAmount;
coinData.normalFee = feeAmount;
coinData.maxFee = feeAmount;
blockchainRequestsCallbacks.onComplete(true);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL getStats Exception");
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
public void onError(Throwable e) {
e.printStackTrace();
Log.e(TAG, "FAIL getStats Exception");
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setResponseListener(electrumListener);
serverApiElectrum.requestData(ctx, ElectrumRequest.getFee());
serverApiBlockchair.getStats(statsObserver);
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final String txStr = BTCUtils.toHex(txForSend);
final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain());
ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() {
CompletableObserver sendObserver = new DisposableCompletableObserver() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) {
try {
String resultString = electrumRequest.getResultString();
if (resultString == null || resultString.isEmpty()) {
ctx.setError("Rejected by node: " + electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}else {
ctx.setError(null);
blockchainRequestsCallbacks.onComplete(true);
}
} catch (Exception e) {
if (e.getMessage() != null) {
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
} else {
ctx.setError(e.getClass().getName());
blockchainRequestsCallbacks.onComplete(false);
}
}
}
public void onComplete() {
blockchainRequestsCallbacks.onComplete(true);
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
public void onError(Throwable e) {
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setResponseListener(electrumBodyListener);
serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr));
serverApiBlockchair.sendTransaction(Util.byteArrayToHexString(txForSend), sendObserver);
}
@Override