Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-12 14:02:43 +00:00
commit 3e9d6902d2
7 changed files with 150 additions and 55 deletions

View file

@ -14,7 +14,7 @@ import retrofit2.http.Query;
public interface BlockchainInfoApi {
@GET(Server.ApiBlockchainInfo.Method.ADDRESS)
Single<BlockchainInfoAddress> blockchainInfoAddress(@Path("address") String address);
Single<BlockchainInfoAddress> blockchainInfoAddress(@Path("address") String address, @Query("offset") Integer offset);
@GET(Server.ApiBlockchainInfo.Method.UTXO)
Single<BlockchainInfoUnspents> blockchainInfoUnspents(@Query("active") String address);

View file

@ -113,7 +113,7 @@ public class Server {
public static class Method {
static final String MAIN = URL_BLOCKCYPHER + V1_MAIN;
static final String ADDRESS = MAIN + "/addrs/{address}?unspentOnly=true&includeScript=true";
static final String ADDRESS = MAIN + "/addrs/{address}?includeScript=true&limit=2000";
static final String TXS = MAIN + "/txs/{txHash}?includeHex=true";
static final String PUSH = MAIN + "/txs/push";
}
@ -123,7 +123,7 @@ public class Server {
public static final String URL_BLOCKCHAININFO = ServerURL.API_BLOCKCHAIN_INFO;
public static class Method {
static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}?limit=5";
static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}";
static final String UTXO = URL_BLOCKCHAININFO + "unspent";
// static final String TX = URL_BLOCKCHAININFO + "rawtx/{txHash}";
static final String PUSH = URL_BLOCKCHAININFO + "pushtx";

View file

@ -5,9 +5,11 @@ import android.util.Log;
import com.tangem.App;
import com.tangem.data.network.model.BlockchainInfoAddress;
import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents;
import com.tangem.data.network.model.BlockchainInfoTransaction;
import com.tangem.data.network.model.BlockchainInfoUnspents;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
@ -17,12 +19,13 @@ import okhttp3.ResponseBody;
public class ServerApiBlockchainInfo {
private static String TAG = ServerApiBlockchainInfo.class.getSimpleName();
private int page = 1;
public void getAddressAndUnspents(String wallet, SingleObserver<BlockchainInfoAddressAndUnspents> addressAndUnspentsObserver) {
Log.i(TAG, "new getAddressAndUnspents request");
BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class);
Single<BlockchainInfoAddress> addressObservable = api.blockchainInfoAddress(wallet);
Single<BlockchainInfoAddress> addressObservable = api.blockchainInfoAddress(wallet, null);
Single<BlockchainInfoUnspents> unspentsObservable = api.blockchainInfoUnspents(wallet)
.onErrorReturnItem(new BlockchainInfoUnspents(new ArrayList<>()));
@ -43,4 +46,17 @@ public class ServerApiBlockchainInfo {
sendObservable.subscribe(sendObserver);
}
public Single<List<BlockchainInfoTransaction>> getMoreAddressTxs(String wallet) {
Log.i(TAG, "new getAddress request");
BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class);
Single<List<BlockchainInfoTransaction>> addressObservable = api.blockchainInfoAddress(wallet, page * 50)
.map(BlockchainInfoAddress::getTxs)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
page++;
return addressObservable;
}
}

View file

@ -15,7 +15,10 @@ data class BlockchainInfoTransaction(
var hash: String? = null,
@SerializedName("block_height")
var block_height: Long? = null
var block_height: Long? = null,
@SerializedName("inputs")
var inputs: List<BlockchainInfoInput>
)
data class BlockchainInfoUnspents(
@ -37,6 +40,16 @@ data class BlockchainInfoUtxo(
var script: String? = null
)
data class BlockchainInfoInput(
@SerializedName("prev_out")
var prev_out: BlockchainInfoOutput
)
data class BlockchainInfoOutput(
@SerializedName("addr")
var addr: String? = null
)
data class BlockchainInfoAddressAndUnspents(
var address: BlockchainInfoAddress,
var unspents: BlockchainInfoUnspents

View file

@ -13,13 +13,22 @@ data class BlockcypherResponse(
var unconfirmed_balance: Long? = null,
@SerializedName("txrefs")
var txrefs: List<BlockcypherTxref>? = null
var txrefs: List<BlockcypherTxref>? = null,
@SerializedName("unconfirmed_txrefs")
var unconfirmed_txrefs: List<BlockcypherTxref>? = null,
@SerializedName("hasMore")
var hasMore: Boolean? = null
)
data class BlockcypherTxref(
@SerializedName("tx_hash")
var tx_hash: String? = null,
@SerializedName("tx_input_n")
var tx_input_n: Int? = null,
@SerializedName("tx_output_n")
var tx_output_n: Int? = null,
@ -30,7 +39,10 @@ data class BlockcypherTxref(
var confirmations: Long? = null,
@SerializedName("script")
var script: String? = null
var script: String? = null,
@SerializedName("spent")
var spent: Boolean? = null
)
data class BlockcypherTx(

View file

@ -75,9 +75,7 @@ public abstract class CoinData {
B.putBoolean("balanceReceived", balanceReceived);
B.putString("validationNodeDescription", validationNodeDescription);
if (sentTransactionsCount != null) {
B.putInt("sentTransactionsCount", sentTransactionsCount);
}
B.putInt("sentTransactionsCount", sentTransactionsCount);
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
@ -91,8 +89,8 @@ public abstract class CoinData {
}
public static CoinData fromBundle(Blockchain blockchain, Bundle bundle) {
CoinEngine engine= CoinEngineFactory.INSTANCE.create(blockchain);
if( engine==null ) return null;
CoinEngine engine = CoinEngineFactory.INSTANCE.create(blockchain);
if (engine == null) return null;
CoinData result = engine.createCoinData();
result.loadFromBundle(bundle);
return result;
@ -151,12 +149,12 @@ public abstract class CoinData {
setIsBalanceEqual(false);
setBalanceReceived(false);
setValidationNodeDescription("");
minFee=null;
maxFee=null;
normalFee=null;
rate=0f;
rateAlter=0f;
sentTransactionsCount = null;
minFee = null;
maxFee = null;
normalFee = null;
rate = 0f;
rateAlter = 0f;
sentTransactionsCount = 0;
}
// private AtomicInteger failedBalanceRequestCounter;
@ -201,13 +199,13 @@ public abstract class CoinData {
public CoinEngine.Amount normalFee = null;
public CoinEngine.Amount maxFee = null;
private Integer sentTransactionsCount = null;
private int sentTransactionsCount = 0;
public Integer getSentTransactionsCount() {
public int getSentTransactionsCount() {
return sentTransactionsCount;
}
public void setSentTransactionsCount(Integer sentTransactionsCount) {
this.sentTransactionsCount = sentTransactionsCount;
public void incSentTransactionsCount() {
sentTransactionsCount++;
}
}

View file

@ -13,6 +13,7 @@ import com.tangem.data.network.ServerApiBlockcypher;
import com.tangem.data.network.ServerApiCommon;
import com.tangem.data.network.model.BlockchainInfoAddress;
import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents;
import com.tangem.data.network.model.BlockchainInfoInput;
import com.tangem.data.network.model.BlockchainInfoTransaction;
import com.tangem.data.network.model.BlockchainInfoUnspents;
import com.tangem.data.network.model.BlockchainInfoUtxo;
@ -53,6 +54,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.observers.DisposableSingleObserver;
import okhttp3.ResponseBody;
@ -576,30 +578,6 @@ public class BtcEngine extends CoinEngine {
BlockchainInfoAddress blockchainInfoAddress = blockchainInfoAddressAndUnspents.getAddress();
BlockchainInfoUnspents blockchainInfoUnspents = blockchainInfoAddressAndUnspents.getUnspents();
if (blockchainInfoAddress.getFinal_balance() != null) {
coinData.setBalanceReceived(true);
coinData.setBalanceConfirmed(blockchainInfoAddress.getFinal_balance());
coinData.setBalanceUnconfirmed(0L);
coinData.setValidationNodeDescription(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO);
for (BlockchainInfoTransaction tx : blockchainInfoAddress.getTxs()) {
if (tx.getBlock_height() == null) {
coinData.setHasUnconfirmed(true);
}
}
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
String pendingTxId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
for (BlockchainInfoTransaction responseTx : blockchainInfoAddress.getTxs()) {
if (responseTx.getHash().equals(pendingTxId)) {
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), pendingTx.getTx());
}
}
}
}
}
for (BlockchainInfoUtxo utxo : blockchainInfoUnspents.getUnspent_outputs()) {
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = utxo.getTx_hash_big_endian();
@ -609,7 +587,18 @@ public class BtcEngine extends CoinEngine {
coinData.getUnspentTransactions().add(trUnspent);
}
blockchainRequestsCallbacks.onComplete(true);
if (blockchainInfoAddress.getFinal_balance() != null) {
coinData.setBalanceReceived(true);
coinData.setBalanceConfirmed(blockchainInfoAddress.getFinal_balance());
coinData.setBalanceUnconfirmed(0L);
coinData.setValidationNodeDescription(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO);
}
if (blockchainInfoAddress.getTxs() != null) {
checkTransactionsBlockchainInfo(Single.just(blockchainInfoAddress.getTxs()), serverApiBlockchainInfo, blockchainRequestsCallbacks);
} else {
blockchainRequestsCallbacks.onComplete(true);
}
}
@Override
@ -648,14 +637,29 @@ public class BtcEngine extends CoinEngine {
coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER);
coinData.getUnspentTransactions().clear();
//TODO: change request logic, 2000 tx max
if (blockcypherResponse.getTxrefs() != null) {
for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) {
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = txref.getTx_hash();
trUnspent.amount = txref.getValue();
trUnspent.outputN = txref.getTx_output_n();
trUnspent.script = txref.getScript();
coinData.getUnspentTransactions().add(trUnspent);
if (txref.getTx_input_n() == -1) { //recieved only
if (!txref.getSpent()) {
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = txref.getTx_hash();
trUnspent.amount = txref.getValue();
trUnspent.outputN = txref.getTx_output_n();
trUnspent.script = txref.getScript();
coinData.getUnspentTransactions().add(trUnspent);
}
} else { //sent only
coinData.incSentTransactionsCount();
}
}
}
if (blockcypherResponse.getUnconfirmed_txrefs() != null) {
for (BlockcypherTxref unconfirmedTxref : blockcypherResponse.getUnconfirmed_txrefs()) {
if (unconfirmedTxref.getTx_input_n() != -1) {
coinData.incSentTransactionsCount();
}
}
}
} catch (Exception e) {
@ -663,7 +667,7 @@ public class BtcEngine extends CoinEngine {
Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception");
}
checkPending(blockchainRequestsCallbacks);
checkPendingBlockcypher(blockchainRequestsCallbacks);
}
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
@ -686,7 +690,59 @@ public class BtcEngine extends CoinEngine {
}
}
private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
private void checkTransactionsBlockchainInfo(Single<List<BlockchainInfoTransaction>> txsSingle, ServerApiBlockchainInfo serverApiBlockchainInfo, BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
SingleObserver<List<BlockchainInfoTransaction>> txsObserver = new DisposableSingleObserver<List<BlockchainInfoTransaction>>() {
@Override
public void onSuccess(List<BlockchainInfoTransaction> txs) {
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
String pendingTxId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
for (BlockchainInfoTransaction responseTx : txs) {
if (pendingTxId.equals(responseTx.getHash())) {
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), pendingTx.getTx());
}
}
}
}
for (BlockchainInfoTransaction tx : txs) {
if (tx.getBlock_height() == null) {
coinData.setHasUnconfirmed(true);
}
for (BlockchainInfoInput input : tx.getInputs()) {
String inputAddress = input.getPrev_out().getAddr();
if (coinData.getWallet().equals(inputAddress)) {
coinData.incSentTransactionsCount();
}
}
}
if (txs.size() == 50) {
final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo();
checkTransactionsBlockchainInfo(serverApiBlockchainInfo.getMoreAddressTxs(coinData.getWallet()), serverApiBlockchainInfo, blockchainRequestsCallbacks);
} else {
blockchainRequestsCallbacks.onComplete(true);
}
}
@Override
public void onError(Throwable e) {
Log.i(TAG, "onError: getMoreAddressTxs" + e.getMessage());
coinData.setUseBlockcypher(true);
try {
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
} catch (Exception ex) {
ctx.setError(ex.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
}
};
txsSingle.subscribe(txsObserver);
}
private void checkPendingBlockcypher(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();