Updated on 2026-08-14
This commit is contained in:
parent
8013392219
commit
92d3780a82
7 changed files with 150 additions and 55 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue