Updated on 2026-08-14
This commit is contained in:
parent
e029da2b32
commit
a31c66d60f
10 changed files with 328 additions and 266 deletions
27
app/src/main/java/com/tangem/data/network/BlockchairApi.java
Normal file
27
app/src/main/java/com/tangem/data/network/BlockchairApi.java
Normal 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);
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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/";
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.data.network.model;
|
||||
|
||||
public class BlockchairSendBody {
|
||||
private String data;
|
||||
|
||||
public BlockchairSendBody(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,10 @@ public abstract class CoinData {
|
|||
return sentTransactionsCount;
|
||||
}
|
||||
|
||||
public void setSentTransactionsCount(int sentTransactionsCount) {
|
||||
this.sentTransactionsCount = sentTransactionsCount;
|
||||
}
|
||||
|
||||
public void incSentTransactionsCount() {
|
||||
sentTransactionsCount++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue