Updated on 2026-08-14
This commit is contained in:
commit
af06bdca22
71 changed files with 1010 additions and 296 deletions
|
|
@ -2,16 +2,18 @@ package com.tangem
|
|||
|
||||
import android.app.Application
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.tangem.data.local.PendingTransactionsStorage
|
||||
import com.tangem.card_android.android.data.Firmwares
|
||||
import com.tangem.card_android.android.data.PINStorage
|
||||
import com.tangem.card_common.data.Issuer
|
||||
import com.tangem.data.dp.PrefsManager
|
||||
import com.tangem.data.local.PendingTransactionsStorage
|
||||
import com.tangem.di.*
|
||||
import com.tangem.server_android.data.LocalStorage
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
|
@ -73,6 +75,7 @@ class App : Application() {
|
|||
}
|
||||
}
|
||||
)
|
||||
if (BuildConfig.CRASHLYTICS) Fabric.with(this, Crashlytics())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
import com.tangem.data.network.model.BlockchainInfoAddress;
|
||||
import com.tangem.data.network.model.BlockchainInfoUnspents;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface BlockchainInfoApi {
|
||||
@GET(Server.ApiBlockchainInfo.Method.ADDRESS)
|
||||
Single<BlockchainInfoAddress> blockchainInfoAddress(@Path("address") String address);
|
||||
|
||||
@GET(Server.ApiBlockchainInfo.Method.UTXO)
|
||||
Single<BlockchainInfoUnspents> blockchainInfoUnspents(@Query("active") String address);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(Server.ApiBlockchainInfo.Method.PUSH)
|
||||
Single<ResponseBody> blockchainInfoPush(@Field("tx") String tx);
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.data.network;
|
|||
import com.tangem.data.network.model.RateInfoResponse;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.Query;
|
||||
|
|
|
|||
|
|
@ -118,4 +118,15 @@ public class Server {
|
|||
static final String PUSH = MAIN + "/txs/push";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiBlockchainInfo {
|
||||
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 UTXO = URL_BLOCKCHAININFO + "unspent";
|
||||
// static final String TX = URL_BLOCKCHAININFO + "rawtx/{txHash}";
|
||||
static final String PUSH = URL_BLOCKCHAININFO + "pushtx";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
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.BlockchainInfoUnspents;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class ServerApiBlockchainInfo {
|
||||
private static String TAG = ServerApiBlockchainInfo.class.getSimpleName();
|
||||
|
||||
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<BlockchainInfoUnspents> unspentsObservable = api.blockchainInfoUnspents(wallet)
|
||||
.onErrorReturnItem(new BlockchainInfoUnspents(new ArrayList<>()));
|
||||
|
||||
Single.zip(addressObservable, unspentsObservable, BlockchainInfoAddressAndUnspents::new)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(addressAndUnspentsObserver);
|
||||
}
|
||||
|
||||
public void sendTransaction(String tx, SingleObserver<ResponseBody> sendObserver) {
|
||||
Log.i(TAG, "new getAddress request");
|
||||
BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class);
|
||||
|
||||
Single<ResponseBody> sendObservable = api.blockchainInfoPush(tx)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
|
||||
sendObservable.subscribe(sendObserver);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.data.network;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.model.BlockcypherBody;
|
||||
|
|
@ -11,13 +13,12 @@ import com.tangem.data.network.model.BlockcypherTx;
|
|||
|
||||
import java.util.Random;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ServerApiBlockcypher {
|
||||
private static String TAG = ServerApiRipple.class.getSimpleName();
|
||||
private static String TAG = ServerApiBlockcypher.class.getSimpleName();
|
||||
|
||||
public static final String BLOCKCYPHER_ADDRESS = "blockcypher_address";
|
||||
public static final String BLOCKCYPHER_FEE = "blockcypher_fee";
|
||||
|
|
|
|||
|
|
@ -7,18 +7,16 @@ import com.tangem.wallet.eos.EosPushTransactionRequest;
|
|||
|
||||
import io.jafka.jeos.EosApi;
|
||||
import io.jafka.jeos.EosApiFactory;
|
||||
import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest;
|
||||
import io.jafka.jeos.core.response.chain.account.Account;
|
||||
import io.jafka.jeos.core.response.chain.transaction.PushedTransaction;
|
||||
import io.jafka.jeos.impl.EosApiServiceGenerator;
|
||||
import io.jafka.jeos.impl.EosChainApiService;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class ServerApiEos {
|
||||
private static String TAG = ServerApiBinance.class.getSimpleName();
|
||||
private static String TAG = ServerApiEos.class.getSimpleName();
|
||||
|
||||
public static void getBalance(String wallet, Observer<Account> accountObserver) {
|
||||
Log.i(TAG, "new getBalance request");
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package com.tangem.data.network;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tangem.data.network.model.RippleBody;
|
||||
import com.tangem.data.network.model.RippleResponse;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
|
@ -25,7 +26,14 @@ public class ServerApiRipple {
|
|||
|
||||
private int requestsCount = 0;
|
||||
|
||||
public static String lastNode;
|
||||
private final String rippleURL1 = "https://s1.ripple.com:51234"; //TODO: make random selection, add more?, move
|
||||
private final String rippleURL2 = "https://s2.ripple.com:51234";
|
||||
|
||||
private String currentURL = rippleURL1;
|
||||
|
||||
public String getCurrentURL() {
|
||||
return currentURL;
|
||||
}
|
||||
|
||||
public boolean isRequestsSequenceCompleted() {
|
||||
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
|
||||
|
|
@ -46,11 +54,9 @@ public class ServerApiRipple {
|
|||
|
||||
public void requestData(String method, String wallet, String tx) {
|
||||
requestsCount++;
|
||||
String rippleURL = "https://s1.ripple.com:51234"; //TODO: make random selection
|
||||
lastNode = rippleURL; //TODO: show node instead of URL
|
||||
|
||||
Retrofit retrofitRipple = new Retrofit.Builder()
|
||||
.baseUrl(rippleURL)
|
||||
.baseUrl(currentURL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
|
|
@ -93,6 +99,38 @@ public class ServerApiRipple {
|
|||
rippleBody = new RippleBody();
|
||||
}
|
||||
|
||||
Call<RippleResponse> call = rippleApi.ripple(rippleBody);
|
||||
call.enqueue(new Callback<RippleResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<RippleResponse> call, @NonNull Response<RippleResponse> response) {
|
||||
if (response.code() == 200) {
|
||||
requestsCount--;
|
||||
responseListener.onSuccess(method, response.body());
|
||||
Log.i(TAG, "requestData " + method + " onResponse " + response.code());
|
||||
} else {
|
||||
retryRequest(method, rippleBody);
|
||||
Log.e(TAG, "requestData " + method + " onResponse " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<RippleResponse> call, @NonNull Throwable t) {
|
||||
retryRequest(method, rippleBody);
|
||||
Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void retryRequest(String method, RippleBody rippleBody) {
|
||||
currentURL = rippleURL2;
|
||||
|
||||
Retrofit retrofitRipple = new Retrofit.Builder()
|
||||
.baseUrl(currentURL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
RippleApi rippleApi = retrofitRipple.create(RippleApi.class);
|
||||
|
||||
Call<RippleResponse> call = rippleApi.ripple(rippleBody);
|
||||
call.enqueue(new Callback<RippleResponse>() {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ class ServerURL {
|
|||
static final String API_MATIC_TESTNET = "https://testnet2.matic.network";
|
||||
static final String API_STELLAR = "https://horizon.stellar.org/";
|
||||
static final String API_STELLAR_TESTNET = "https://horizon-testnet.stellar.org";
|
||||
static final String API_BLOCKCHAIN_INFO = "https://blockchain.info/";
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.data.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class BlockchainInfoAddress(
|
||||
@SerializedName("final_balance")
|
||||
var final_balance: Long? = null,
|
||||
|
||||
@SerializedName("txs")
|
||||
var txs: List<BlockchainInfoTransaction>? = null
|
||||
)
|
||||
|
||||
data class BlockchainInfoTransaction(
|
||||
@SerializedName("hash")
|
||||
var hash: String? = null,
|
||||
|
||||
@SerializedName("block_height")
|
||||
var block_height: Long? = null
|
||||
)
|
||||
|
||||
data class BlockchainInfoUnspents(
|
||||
@SerializedName("unspent_outputs")
|
||||
var unspent_outputs: List<BlockchainInfoUtxo>
|
||||
)
|
||||
|
||||
data class BlockchainInfoUtxo(
|
||||
@SerializedName("tx_hash_big_endian")
|
||||
var tx_hash_big_endian: String? = null,
|
||||
|
||||
@SerializedName("tx_output_n")
|
||||
var tx_output_n: Int? = null,
|
||||
|
||||
@SerializedName("value")
|
||||
var value: Long? = null,
|
||||
|
||||
@SerializedName("script")
|
||||
var script: String? = null
|
||||
)
|
||||
|
||||
data class BlockchainInfoAddressAndUnspents(
|
||||
var address: BlockchainInfoAddress,
|
||||
var unspents: BlockchainInfoUnspents
|
||||
)
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
package com.tangem.di
|
||||
|
||||
import com.tangem.data.network.Server
|
||||
|
||||
import java.net.Socket
|
||||
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
import dagger.Component
|
||||
import retrofit2.Retrofit
|
||||
import java.net.Socket
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = [NetworkModule::class])
|
||||
|
|
@ -38,6 +35,9 @@ interface NetworkComponent {
|
|||
@get:Named(Server.ApiSoChain.URL)
|
||||
val retrofitSoChain: Retrofit
|
||||
|
||||
@get:Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO)
|
||||
val retrofitBlockchainInfo: Retrofit
|
||||
|
||||
@get:Named("socket")
|
||||
val socket: Socket
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,19 @@ package com.tangem.di
|
|||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import com.tangem.data.network.Server
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
internal class NetworkModule {
|
||||
|
|
@ -121,6 +119,20 @@ internal class NetworkModule {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO)
|
||||
fun provideRetrofitBlockchainInfo(): Retrofit {
|
||||
val builder = Retrofit.Builder()
|
||||
.baseUrl(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO)
|
||||
.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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class LogoActivity : AppCompatActivity() {
|
|||
|
||||
App.navigatorComponent.inject(this)
|
||||
|
||||
ivLogo.setOnClickListener { hide() }
|
||||
clLogoContainer.setOnClickListener { hide() }
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
|||
|
|
@ -41,14 +41,17 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
// private var onNfcReaderCallback: NfcAdapter.ReaderCallback? = null
|
||||
|
||||
// override fun onNewIntent(intent: Intent?) {
|
||||
// super.onNewIntent(intent)
|
||||
// if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
|
||||
// val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
|
||||
// if (tag != null && onNfcReaderCallback != null)
|
||||
// onNfcReaderCallback?.onTagDiscovered(tag)
|
||||
// }
|
||||
// }
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
|
||||
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
|
||||
if (tag != null) {
|
||||
val activeFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
|
||||
?.childFragmentManager?.primaryNavigationFragment
|
||||
(activeFragment as? NfcAdapter.ReaderCallback)?.onTagDiscovered(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -19,17 +19,19 @@ public class BtcData extends CoinData {
|
|||
|
||||
private boolean useBlockcypher = false;
|
||||
|
||||
//for blockchain.info
|
||||
private boolean hasUnconfirmed = false;
|
||||
|
||||
public String getUnspentInputsDescription() {
|
||||
try {
|
||||
int gatheredUnspents = 0;
|
||||
if( unspentTransactions==null ) return "";
|
||||
if (unspentTransactions == null) return "";
|
||||
for (int i = 0; i < unspentTransactions.size(); i++) {
|
||||
if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1) gatheredUnspents++;
|
||||
if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1)
|
||||
gatheredUnspents++;
|
||||
}
|
||||
return unspentTransactions.size() + " unspents (" + gatheredUnspents + " received)";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
|
|
@ -86,6 +88,7 @@ public class BtcData extends CoinData {
|
|||
}
|
||||
}
|
||||
if (B.containsKey("UseBlockcypher")) useBlockcypher = B.getBoolean("UseBlockcypher");
|
||||
if (B.containsKey("HasUnconfirmed")) useBlockcypher = B.getBoolean("HasUnconfirmed");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -102,6 +105,7 @@ public class BtcData extends CoinData {
|
|||
if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed);
|
||||
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
|
||||
if (useBlockcypher) B.putBoolean("UseBlockcypher", true);
|
||||
if (hasUnconfirmed) B.putBoolean("HasUnconfirmed", true);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
|
@ -116,7 +120,11 @@ public class BtcData extends CoinData {
|
|||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).add(BigDecimal.valueOf(balanceUnconfirmed)),"Satoshi");
|
||||
if (balanceConfirmed != null && balanceUnconfirmed != null) {
|
||||
return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).add(BigDecimal.valueOf(balanceUnconfirmed)), "Satoshi");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Long getBalanceUnconfirmed() {
|
||||
|
|
@ -142,4 +150,12 @@ public class BtcData extends CoinData {
|
|||
public void setUseBlockcypher(boolean useBlockcypher) {
|
||||
this.useBlockcypher = useBlockcypher;
|
||||
}
|
||||
|
||||
public boolean isHasUnconfirmed() {
|
||||
return hasUnconfirmed;
|
||||
}
|
||||
|
||||
public void setHasUnconfirmed(boolean hasUnconfirmed) {
|
||||
this.hasUnconfirmed = hasUnconfirmed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,18 @@ import com.tangem.card_common.util.Util;
|
|||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.local.PendingTransactionsStorage;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBlockchainInfo;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiCommon;
|
||||
import com.tangem.data.network.ServerApiSoChain;
|
||||
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 com.tangem.data.network.model.BlockchainInfoUtxo;
|
||||
import com.tangem.data.network.model.BlockcypherFee;
|
||||
import com.tangem.data.network.model.BlockcypherResponse;
|
||||
import com.tangem.data.network.model.BlockcypherTx;
|
||||
import com.tangem.data.network.model.BlockcypherTxref;
|
||||
import com.tangem.data.network.model.SoChain;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
import com.tangem.util.DerEncodingUtil;
|
||||
|
|
@ -33,9 +37,8 @@ import com.tangem.wallet.TangemContext;
|
|||
import com.tangem.wallet.Transaction;
|
||||
import com.tangem.wallet.UnspentOutputInfo;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
|
|
@ -46,6 +49,10 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class BtcEngine extends CoinEngine {
|
||||
|
||||
private static final String TAG = BtcEngine.class.getSimpleName();
|
||||
|
|
@ -263,7 +270,7 @@ public class BtcEngine extends CoinEngine {
|
|||
// return;
|
||||
// }
|
||||
|
||||
if (coinData.getBalanceUnconfirmed() != 0) {
|
||||
if (coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed()) {
|
||||
balanceValidator.setScore(0);
|
||||
balanceValidator.setFirstLine("Transaction in progress");
|
||||
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
|
||||
|
|
@ -537,120 +544,67 @@ public class BtcEngine extends CoinEngine {
|
|||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
|
||||
ctx.setError(null);
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
//SoChain request can be found at LtcEngine
|
||||
if (!coinData.isUseBlockcypher() && ctx.getBlockchain() != Blockchain.BitcoinTestNet) {
|
||||
final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo();
|
||||
|
||||
ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() {
|
||||
SingleObserver<BlockchainInfoAddressAndUnspents> addressAndUnspentsObserver = new DisposableSingleObserver<BlockchainInfoAddressAndUnspents>() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.AddressBalance response) {
|
||||
try {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
public void onSuccess(BlockchainInfoAddressAndUnspents blockchainInfoAddressAndUnspents) {
|
||||
BlockchainInfoAddress blockchainInfoAddress = blockchainInfoAddressAndUnspents.getAddress();
|
||||
BlockchainInfoUnspents blockchainInfoUnspents = blockchainInfoAddressAndUnspents.getUnspents();
|
||||
|
||||
if (blockchainInfoAddress.getFinal_balance() != null) {
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(Server.ApiSoChain.URL);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
|
||||
ctx.setError("FAIL METHOD_GetBalance JSONException");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
|
||||
ctx.setError("FAIL METHOD_GetBalance Exception");
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
for (BlockchainInfoUtxo utxo : blockchainInfoUnspents.getUnspent_outputs()) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = utxo.getTx_hash_big_endian();
|
||||
trUnspent.amount = utxo.getValue();
|
||||
trUnspent.outputN = utxo.getTx_output_n();
|
||||
trUnspent.script = utxo.getScript();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
}
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.TxUnspent response) {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
try {
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
coinData.getUnspentTransactions().clear();
|
||||
if (response.getData().getTxs() != null)
|
||||
for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = tx.getTxid();
|
||||
trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact();
|
||||
trUnspent.outputN = tx.getOutput_no();
|
||||
trUnspent.script = tx.getScript_hex();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
|
||||
// if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
|
||||
// } else {
|
||||
// ctx.setError("Terminated by user");
|
||||
// }
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
|
||||
}
|
||||
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: " + message);
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onError: getAddress" + e.getMessage());
|
||||
coinData.setUseBlockcypher(true);
|
||||
// ctx.setError(R.string.cannot_obtain_data_from_blockchain);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception ex) {
|
||||
ctx.setError(ex.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiBlockchainInfo.getAddressAndUnspents(coinData.getWallet(), addressAndUnspentsObserver);
|
||||
|
||||
serverApiSoChain.setAddressInfoListener(addressInfoListener);
|
||||
|
||||
serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet());
|
||||
serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet());
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
|
|
@ -716,93 +670,48 @@ public class BtcEngine extends CoinEngine {
|
|||
|
||||
private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
|
||||
ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.GetTx response) {
|
||||
Log.i(TAG, "onSuccess: GetTx");
|
||||
try {
|
||||
if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: GetTx" + response);
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: GetTx " + message);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiSoChain.setTransactionInfoListener(listener);
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(BlockcypherTx response) {
|
||||
Log.i(TAG, "onSuccess: BlockcypherTx");
|
||||
try {
|
||||
serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId);
|
||||
if (response.getHex() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "onFail: BlockcypherTx" + response);
|
||||
}
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(BlockcypherTx response) {
|
||||
Log.i(TAG, "onSuccess: BlockcypherTx");
|
||||
try {
|
||||
if (response.getHex() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: BlockcypherTx" + response);
|
||||
}
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: BlockcypherTx " + message);
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setTxResponseListener(listener);
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: BlockcypherTx " + message);
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setTxResponseListener(listener);
|
||||
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
|
|
@ -1026,42 +935,40 @@ public class BtcEngine extends CoinEngine {
|
|||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final String txStr = BTCUtils.toHex(txForSend);
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() {
|
||||
//SoChain request can be found at LtcEngine
|
||||
if (!coinData.isUseBlockcypher() && ctx.getBlockchain() != Blockchain.BitcoinTestNet) {
|
||||
final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo();
|
||||
|
||||
SingleObserver<ResponseBody> responseObserver = new DisposableSingleObserver<ResponseBody>() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.SendTx response) {
|
||||
public void onSuccess(ResponseBody response) {
|
||||
try {
|
||||
if (response.getStatus() == null || response.getData() == null) {
|
||||
ctx.setError("Rejected by node: invalid answer received");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) {
|
||||
ctx.setError("Rejected by node: " + response.getData());
|
||||
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);
|
||||
Log.i(TAG, response.string());
|
||||
if (!response.string().equals("Transaction Submitted")) {
|
||||
ctx.setError(response.string());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ctx.setError(e.getMessage());
|
||||
}
|
||||
|
||||
if (ctx.hasError()) {
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
ctx.setError(message);
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiSoChain.setSendTxListener(listener);
|
||||
serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr);
|
||||
|
||||
serverApiBlockchainInfo.sendTransaction(txStr, responseObserver);
|
||||
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
|
|
|||
|
|
@ -2,11 +2,22 @@ package com.tangem.wallet.ltc;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.card_common.data.TangemCard;
|
||||
import com.tangem.card_common.reader.CardProtocol;
|
||||
import com.tangem.card_common.tasks.SignTask;
|
||||
import com.tangem.card_common.util.Util;
|
||||
import com.tangem.data.local.PendingTransactionsStorage;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiSoChain;
|
||||
import com.tangem.data.network.model.BlockcypherFee;
|
||||
import com.tangem.data.network.model.BlockcypherResponse;
|
||||
import com.tangem.data.network.model.BlockcypherTx;
|
||||
import com.tangem.data.network.model.BlockcypherTxref;
|
||||
import com.tangem.data.network.model.SoChain;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
import com.tangem.util.DerEncodingUtil;
|
||||
|
|
@ -21,6 +32,8 @@ import com.tangem.wallet.UnspentOutputInfo;
|
|||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.wallet.btc.BtcEngine;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
|
@ -478,6 +491,281 @@ public class LtcEngine extends BtcEngine {
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
|
||||
ctx.setError(null);
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.AddressBalance response) {
|
||||
try {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact();
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(Server.ApiSoChain.URL);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
|
||||
ctx.setError("FAIL METHOD_GetBalance JSONException");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
|
||||
ctx.setError("FAIL METHOD_GetBalance Exception");
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.TxUnspent response) {
|
||||
String walletAddress = response.getData().getAddress();
|
||||
try {
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
coinData.getUnspentTransactions().clear();
|
||||
if (response.getData().getTxs() != null)
|
||||
for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = tx.getTxid();
|
||||
trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact();
|
||||
trUnspent.outputN = tx.getOutput_no();
|
||||
trUnspent.script = tx.getScript_hex();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
|
||||
// if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
|
||||
// } else {
|
||||
// ctx.setError("Terminated by user");
|
||||
// }
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
|
||||
}
|
||||
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: " + message);
|
||||
coinData.setUseBlockcypher(true);
|
||||
// ctx.setError(R.string.cannot_obtain_data_from_blockchain);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
try {
|
||||
requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks);
|
||||
} catch (Exception e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
serverApiSoChain.setAddressInfoListener(addressInfoListener);
|
||||
|
||||
serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet());
|
||||
serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet());
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
|
||||
Log.i(TAG, "onSuccess: " + method);
|
||||
try {
|
||||
String walletAddress = blockcypherResponse.getAddress();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
Long confBalance = blockcypherResponse.getBalance();
|
||||
Long unconfirmedBalance = blockcypherResponse.getUnconfirmed_balance();
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(confBalance);
|
||||
coinData.setBalanceUnconfirmed(unconfirmedBalance);
|
||||
coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER);
|
||||
|
||||
coinData.getUnspentTransactions().clear();
|
||||
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);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception");
|
||||
}
|
||||
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
checkPending(blockchainRequestsCallbacks);
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
|
||||
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.GetTx response) {
|
||||
Log.i(TAG, "onSuccess: GetTx");
|
||||
try {
|
||||
if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: GetTx" + response);
|
||||
}
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: GetTx " + message);
|
||||
if (serverApiSoChain.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
serverApiSoChain.setTransactionInfoListener(listener);
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(BlockcypherTx response) {
|
||||
Log.i(TAG, "onSuccess: BlockcypherTx");
|
||||
try {
|
||||
if (response.getHex() != null) {
|
||||
App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onFail: BlockcypherTx" + response);
|
||||
}
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
Log.i(TAG, "onFail: BlockcypherTx " + message);
|
||||
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setTxResponseListener(listener);
|
||||
|
||||
for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) {
|
||||
String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))));
|
||||
try {
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
}
|
||||
}
|
||||
|
||||
private final static BigDecimal relayFee = new BigDecimal(0.00001).setScale(8, RoundingMode.DOWN);
|
||||
|
||||
@Override
|
||||
|
|
@ -486,6 +774,92 @@ public class LtcEngine extends BtcEngine {
|
|||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final String txStr = BTCUtils.toHex(txForSend);
|
||||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiSoChain serverApiSoChain = new ServerApiSoChain();
|
||||
|
||||
ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() {
|
||||
@Override
|
||||
public void onSuccess(SoChain.Response.SendTx response) {
|
||||
try {
|
||||
if (response.getStatus() == null || response.getData() == null) {
|
||||
ctx.setError("Rejected by node: invalid answer received");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) {
|
||||
ctx.setError("Rejected by node: " + response.getData());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String message) {
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiSoChain.setSendTxListener(listener);
|
||||
serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr);
|
||||
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
|
||||
String resultString = blockcypherResponse.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestSendTransaction");
|
||||
ctx.setError("Wrong response type for requestSendTransaction");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowSelectFeeLevel() {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ public class XrpEngine extends CoinEngine {
|
|||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceUnconfirmed(Long.parseLong(rippleResponse.getResult().getAccount_data().getBalance()));
|
||||
coinData.setSequence(rippleResponse.getResult().getAccount_data().getSequence());
|
||||
coinData.setValidationNodeDescription(ServerApiRipple.lastNode);
|
||||
coinData.setValidationNodeDescription(serverApiRipple.getCurrentURL());
|
||||
|
||||
// //check pending
|
||||
// if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue