From e24d538aa55750053965ce9a22b4e6fb4ce7d331 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Jan 2020 13:13:04 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../com/tangem/data/network/DucatusApi.java | 25 +++ .../java/com/tangem/data/network/Server.java | 10 + .../tangem/data/network/ServerApiBitcore.java | 47 ++++ .../com/tangem/data/network/ServerURL.java | 1 + .../data/network/model/BitcoreResponse.kt | 34 +++ .../data/network/model/BitcoreSendBody.java | 14 ++ .../java/com/tangem/di/NetworkComponent.kt | 3 + .../main/java/com/tangem/di/NetworkModule.kt | 14 ++ .../tangem/wallet/ducatus/DucatusEngine.java | 201 ++++-------------- 9 files changed, 194 insertions(+), 155 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/DucatusApi.java create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiBitcore.java create mode 100644 app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt create mode 100644 app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java diff --git a/app/src/main/java/com/tangem/data/network/DucatusApi.java b/app/src/main/java/com/tangem/data/network/DucatusApi.java new file mode 100644 index 0000000000..53957f70fc --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/DucatusApi.java @@ -0,0 +1,25 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.BitcoreBalance; +import com.tangem.data.network.model.BitcoreSendBody; +import com.tangem.data.network.model.BitcoreSendResponse; +import com.tangem.data.network.model.BitcoreUtxo; + +import java.util.List; + +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface DucatusApi { + @GET(Server.ApiDucatus.Method.BALANCE) + Single ducatusBalance(@Path("address") String address); + + @GET(Server.ApiDucatus.Method.UTXO) + Single> ducatusUnspents(@Path("address") String address); + + @POST(Server.ApiDucatus.Method.SEND) + Single ducatusSend(@Body BitcoreSendBody body); +} diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 1b0570150a..14117ed83b 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -129,4 +129,14 @@ public class Server { static final String PUSH = URL_BLOCKCHAININFO + "pushtx"; } } + + public static class ApiDucatus { + public static final String URL_DUCATUS = ServerURL.API_DUCATUS + "api/DUC/mainnet/"; + + public static class Method { + static final String BALANCE = URL_DUCATUS + "address/{address}/balance"; + static final String UTXO = URL_DUCATUS + "address/{address}/?unspent=true"; + static final String SEND = URL_DUCATUS + "tx/send"; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java b/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java new file mode 100644 index 0000000000..de9de087eb --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiBitcore.java @@ -0,0 +1,47 @@ +package com.tangem.data.network; + +import com.tangem.App; +import com.tangem.data.network.model.BitcoreBalance; +import com.tangem.data.network.model.BitcoreBalanceAndUnspents; +import com.tangem.data.network.model.BitcoreSendBody; +import com.tangem.data.network.model.BitcoreSendResponse; +import com.tangem.data.network.model.BitcoreUtxo; +import com.tangem.tangem_card.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class ServerApiBitcore { + private static String TAG = ServerApiBitcore.class.getSimpleName(); + + public void getBalanceAndUnspents(String wallet, SingleObserver balanceAndUnspentsObserver) { + Log.i(TAG, "new getAddressAndUnspents request"); + DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class); + + Single balanceObservable = api.ducatusBalance(wallet); + + Single> unspentsObservable = api.ducatusUnspents(wallet) + .onErrorReturnItem(new ArrayList<>()); + + Single.zip(balanceObservable, unspentsObservable, BitcoreBalanceAndUnspents::new) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(balanceAndUnspentsObserver); + } + + public void sendTransaction(String tx, SingleObserver sendObserver) { + Log.i(TAG, "new getAddress request"); + DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class); + + Single sendObservable = api.ducatusSend(new BitcoreSendBody(tx)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + sendObservable.subscribe(sendObserver); + } +} diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index f1eda5d63e..cab4e81153 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -16,4 +16,5 @@ class ServerURL { static final String API_STELLAR_RESERVE = "https://horizon.sui.li/"; 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/"; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt b/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt new file mode 100644 index 0000000000..119c1fb3ef --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/BitcoreResponse.kt @@ -0,0 +1,34 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class BitcoreBalance( + @SerializedName("confirmed") + var confirmed: Long? = null, + + @SerializedName("unconfirmed") + var unconfirmed: Long? = null +) + +data class BitcoreUtxo( + @SerializedName("mintTxid") + var mintTxid: String? = null, + + @SerializedName("mintIndex") + var mintIndex: Int? = null, + + @SerializedName("value") + var value: Long? = null, + + @SerializedName("script") + var script: String? = null +) + +data class BitcoreBalanceAndUnspents( + var balance: BitcoreBalance, + var unspents: List +) + +data class BitcoreSendResponse( + var txid: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java b/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java new file mode 100644 index 0000000000..019b4c4e80 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/BitcoreSendBody.java @@ -0,0 +1,14 @@ +package com.tangem.data.network.model; + +import java.util.ArrayList; +import java.util.List; + +public class BitcoreSendBody { + private List rawTx; + + public BitcoreSendBody(String tx) { + List txList = new ArrayList<>(); + txList.add(tx); + rawTx = txList; + } +} diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.kt b/app/src/main/java/com/tangem/di/NetworkComponent.kt index e877710081..fa9291e3cc 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.kt +++ b/app/src/main/java/com/tangem/di/NetworkComponent.kt @@ -38,6 +38,9 @@ interface NetworkComponent { @get:Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) val retrofitBlockchainInfo: Retrofit + @get:Named(Server.ApiDucatus.URL_DUCATUS) + val retrofitDucatus: Retrofit + @get:Named("socket") val socket: Socket diff --git a/app/src/main/java/com/tangem/di/NetworkModule.kt b/app/src/main/java/com/tangem/di/NetworkModule.kt index 93e10159e6..64279e871b 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ b/app/src/main/java/com/tangem/di/NetworkModule.kt @@ -133,6 +133,20 @@ internal class NetworkModule { return builder.build() } + @Singleton + @Provides + @Named(Server.ApiDucatus.URL_DUCATUS) + fun provideRetrofitDucatus(): Retrofit { + val builder = Retrofit.Builder() + .baseUrl(Server.ApiDucatus.URL_DUCATUS) + .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(ScalarsConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + if (BuildConfig.DEBUG) + builder.client(createOkHttpClient()) + return builder.build() + } + private fun createOkHttpClient(): OkHttpClient { return OkHttpClient.Builder().addInterceptor(createHttpLoggingInterceptor()).build() } diff --git a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java index f79127ae7f..c8bb0f47aa 100644 --- a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java @@ -5,7 +5,11 @@ import android.text.InputFilter; import android.util.Log; import com.tangem.App; +import com.tangem.data.network.ServerApiBitcore; import com.tangem.data.network.ServerApiInsight; +import com.tangem.data.network.model.BitcoreBalanceAndUnspents; +import com.tangem.data.network.model.BitcoreSendResponse; +import com.tangem.data.network.model.BitcoreUtxo; import com.tangem.data.network.model.InsightResponse; import com.tangem.data.network.model.InsightUtxo; import com.tangem.tangem_card.data.TangemCard; @@ -37,6 +41,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import io.reactivex.SingleObserver; +import io.reactivex.observers.DisposableSingleObserver; + public class DucatusEngine extends BtcEngine { private static final String TAG = DucatusEngine.class.getSimpleName(); public BtcData coinData = null; @@ -397,13 +404,6 @@ public class DucatusEngine extends BtcEngine { 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); - 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)); } @@ -498,70 +498,43 @@ public class DucatusEngine extends BtcEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiInsight serverApiInsight = new ServerApiInsight(); - - ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { + SingleObserver balanceAndUnspentsObserver = new DisposableSingleObserver() { @Override - public void onSuccess(String method, InsightResponse insightResponse) { - + public void onSuccess(BitcoreBalanceAndUnspents balanceAndUnspents) { try { - String walletAddress = insightResponse.getAddrStr(); - if (!walletAddress.equals(coinData.getWallet())) { - // todo - check - throw new Exception("Invalid wallet address in answer!"); - } + coinData.setBalanceConfirmed(balanceAndUnspents.getBalance().getConfirmed()); + coinData.setBalanceUnconfirmed(balanceAndUnspents.getBalance().getUnconfirmed()); coinData.setBalanceReceived(true); - coinData.setBalanceConfirmed(insightResponse.getBalanceSat()); - coinData.setBalanceUnconfirmed(insightResponse.getUnconfirmedBalanceSat()); - coinData.setValidationNodeDescription(ServerApiInsight.lastNode); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL INSIGHT_ADDRESS Exception"); - } - if (serverApiInsight.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - - public void onSuccess(String method, List utxoList) { - // case ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS: TODO: check method - try { - coinData.getUnspentTransactions().clear(); - for (InsightUtxo utxo : utxoList) { + for (BitcoreUtxo utxo : balanceAndUnspents.getUnspents()) { BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); - trUnspent.txID = utxo.getTxid(); - trUnspent.amount = utxo.getSatoshis(); - trUnspent.outputN = utxo.getVout(); - trUnspent.script = utxo.getScriptPubKey(); + trUnspent.txID = utxo.getMintTxid(); + trUnspent.amount = utxo.getValue(); + trUnspent.outputN = utxo.getMintIndex(); + trUnspent.script = utxo.getScript(); coinData.getUnspentTransactions().add(trUnspent); } + blockchainRequestsCallbacks.onComplete(true); + } catch (Exception e) { + Log.e(TAG, "FAIL BITCORE_BALANCE_AND_UNSPENTS Exception"); e.printStackTrace(); - } - - if (serverApiInsight.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - - @Override - public void onFail(String method, String message) { - if (!serverApiInsight.isRequestsSequenceCompleted()) { //TODO: rework request sequence - ctx.setError(message); + ctx.setError(e.getMessage()); blockchainRequestsCallbacks.onComplete(false); } } + + @Override + public void onError(Throwable e) { + Log.e(TAG, "FAIL BITCORE_BALANCE_AND_UNSPENTS Exception"); + e.printStackTrace(); + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } }; - serverApiInsight.setResponseListener(responseListener); - - serverApiInsight.requestData(ServerApiInsight.INSIGHT_ADDRESS, coinData.getWallet(), ""); - serverApiInsight.requestData(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS, coinData.getWallet(), ""); + ServerApiBitcore serverApiBitcore = new ServerApiBitcore(); + serverApiBitcore.getBalanceAndUnspents(coinData.getWallet(), balanceAndUnspentsObserver); } // private final static BigDecimal relayFee = new BigDecimal(0.00001); @@ -570,65 +543,6 @@ public class DucatusEngine extends BtcEngine { 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; -// -// final ServerApiInsight serverApiInsight = new ServerApiInsight(); -// -// final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { -// @Override -// public void onSuccess(String method, InsightResponse insightResponse) { -// if ( method.equals(ServerApiInsight.INSIGHT_FEE)) { -// try { -// BigDecimal minFee = new BigDecimal(insightResponse.getFee2()); //fee per KB -// BigDecimal normalFee = new BigDecimal(insightResponse.getFee3()); -// BigDecimal maxFee = new BigDecimal(insightResponse.getFee6()); -// -// if (minFee.equals(BigDecimal.ZERO) || normalFee.equals(BigDecimal.ZERO) || maxFee.equals(BigDecimal.ZERO)) { -// serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "",""); -// } -// -// minFee = minFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size -// normalFee = normalFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); -// maxFee = maxFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); -// -//// //compare fee to usual relay fee TODO: check if needed after we get access to Ducatus network -//// if (fee.compareTo(relayFee) < 0) { -//// fee = relayFee; -//// } -// minFee = minFee.setScale(8, RoundingMode.DOWN); -// normalFee = normalFee.setScale(8, RoundingMode.DOWN); -// maxFee = maxFee.setScale(8, RoundingMode.DOWN); -// -// coinData.minFee = new Amount(minFee, ctx.getBlockchain().getCurrency()); -// coinData.normalFee = new Amount(normalFee, ctx.getBlockchain().getCurrency()); -// coinData.maxFee = new Amount(maxFee, ctx.getBlockchain().getCurrency()); -// -// blockchainRequestsCallbacks.onComplete(true); -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// } -// -// @Override -// public void onSuccess (String method, List utxoList) { -// Log.e(TAG, "Wrong response body, InsightResponse expected"); -// } -// -// @Override -// public void onFail(String method, String message) { -// if (!serverApiInsight.isRequestsSequenceCompleted()) { -// ctx.setError(message); -// blockchainRequestsCallbacks.onComplete(false); -// } -// } -// }; -// serverApiInsight.setResponseListener(responseListener); -// -// serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "", ""); TODO: fee api returns -1 now coinData.minFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000089)), ctx.getBlockchain().getCurrency()); //fee for byte from Ducatus wallet for android coinData.normalFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000144)), ctx.getBlockchain().getCurrency()); @@ -639,51 +553,28 @@ public class DucatusEngine extends BtcEngine { @Override public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { - final ServerApiInsight serverApiInsight = new ServerApiInsight(); final String txStr = BTCUtils.toHex(txForSend); - final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { + SingleObserver sendResponseObserver = new DisposableSingleObserver() { @Override - public void onSuccess(String method, InsightResponse insightResponse) { - if (method.equals(ServerApiInsight.INSIGHT_SEND)) { - String resultString = insightResponse.toString(); - try { - if (resultString.isEmpty()) { - ctx.setError("No response from node"); - blockchainRequestsCallbacks.onComplete(false); - } else { // TODO: Make check for a valid send response - 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); - - Log.e(TAG, resultString); - } - } - } - } - - @Override - public void onSuccess(String method, List utxoList) { - Log.e(TAG, "Wrong response body, InsightResponse expected"); - } - - @Override - public void onFail(String method, String message) { - if (!serverApiInsight.isRequestsSequenceCompleted()) { - ctx.setError(message); + public void onSuccess(BitcoreSendResponse sendResponse) { + if (sendResponse.getTxid() != null) { + blockchainRequestsCallbacks.onComplete(true); + } else { + ctx.setError("Unknown send error"); blockchainRequestsCallbacks.onComplete(false); } } - }; - serverApiInsight.setResponseListener(responseListener); - serverApiInsight.requestData(ServerApiInsight.INSIGHT_SEND, "", txStr); + @Override + public void onError(Throwable e) { + Log.e(TAG, "onError: Bitcore sendTransaction" + e.getMessage()); + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + + ServerApiBitcore serverApiBitcore = new ServerApiBitcore(); + serverApiBitcore.sendTransaction(txStr, sendResponseObserver); } } \ No newline at end of file From 150cc03261f918c06e5f26c97cb222ed3910c1c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Jan 2020 13:51:40 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java index c8bb0f47aa..4a363c1dfb 100644 --- a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java @@ -176,7 +176,7 @@ public class DucatusEngine extends BtcEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse("https://insight.ducatus.io/insight/address/" + ctx.getCoinData().getWallet()); + return Uri.parse("https://insight.ducatus.io/#/DUC/mainnet/address/" + ctx.getCoinData().getWallet()); } @Override