Updated on 2026-08-14
This commit is contained in:
parent
318931a3e4
commit
e24d538aa5
9 changed files with 194 additions and 155 deletions
25
app/src/main/java/com/tangem/data/network/DucatusApi.java
Normal file
25
app/src/main/java/com/tangem/data/network/DucatusApi.java
Normal file
|
|
@ -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<BitcoreBalance> ducatusBalance(@Path("address") String address);
|
||||
|
||||
@GET(Server.ApiDucatus.Method.UTXO)
|
||||
Single<List<BitcoreUtxo>> ducatusUnspents(@Path("address") String address);
|
||||
|
||||
@POST(Server.ApiDucatus.Method.SEND)
|
||||
Single<BitcoreSendResponse> ducatusSend(@Body BitcoreSendBody body);
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BitcoreBalanceAndUnspents> balanceAndUnspentsObserver) {
|
||||
Log.i(TAG, "new getAddressAndUnspents request");
|
||||
DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class);
|
||||
|
||||
Single<BitcoreBalance> balanceObservable = api.ducatusBalance(wallet);
|
||||
|
||||
Single<List<BitcoreUtxo>> 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<BitcoreSendResponse> sendObserver) {
|
||||
Log.i(TAG, "new getAddress request");
|
||||
DucatusApi api = App.Companion.getNetworkComponent().getRetrofitDucatus().create(DucatusApi.class);
|
||||
|
||||
Single<BitcoreSendResponse> sendObservable = api.ducatusSend(new BitcoreSendBody(tx))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
|
||||
sendObservable.subscribe(sendObserver);
|
||||
}
|
||||
}
|
||||
|
|
@ -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/";
|
||||
}
|
||||
|
|
@ -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<BitcoreUtxo>
|
||||
)
|
||||
|
||||
data class BitcoreSendResponse(
|
||||
var txid: String? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.data.network.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BitcoreSendBody {
|
||||
private List<String> rawTx;
|
||||
|
||||
public BitcoreSendBody(String tx) {
|
||||
List<String> txList = new ArrayList<>();
|
||||
txList.add(tx);
|
||||
rawTx = txList;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<BtcData.UnspentTransaction> 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<BitcoreBalanceAndUnspents> balanceAndUnspentsObserver = new DisposableSingleObserver<BitcoreBalanceAndUnspents>() {
|
||||
@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<InsightUtxo> 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<InsightResponse> 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<BitcoreSendResponse> sendResponseObserver = new DisposableSingleObserver<BitcoreSendResponse>() {
|
||||
@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<InsightUtxo> 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue