Updated on 2026-08-14

This commit is contained in:
Tangem 2020-01-16 15:24:11 +00:00
commit ce64b6827d
10 changed files with 784 additions and 5 deletions

View file

@ -23,12 +23,13 @@ public enum Blockchain {
BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.tangem2, "Binance Testnet"),
Matic("MATIC", "MTX", 1.0, R.drawable.tangem2, "Matic"),
MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet"),
Stellar("XLM", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"),
StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"),
Stellar("XLM", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"),
StellarTestNet("XLM/test", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"),
StellarAsset("Asset", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"),
StellarTag("XLM-Tag", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"),
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"),
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus");
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus"),
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.tangem2, "Tezos");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;

View file

@ -0,0 +1,139 @@
package com.tangem.data.network;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.data.network.model.TezosAccountResponse;
import com.tangem.data.network.model.TezosForgeBody;
import com.tangem.data.network.model.TezosHeaderResponse;
import com.tangem.data.network.model.TezosPreapplyBody;
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;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public class ServerApiTezos {
private static String TAG = ServerApiTezos.class.getSimpleName();
private final String letzbakeURI = "https://teznode.letzbake.com";
private final String tezrpcURI = "https://mainnet.tezrpc.me";
static final String TEZOS_ADDRESS = "chains/main/blocks/head/context/contracts/{address}";
static final String TEZOS_HEADER = "chains/main/blocks/head/header";
static final String TEZOS_MANAGER_KEY = "chains/main/blocks/head/context/contracts/{address}/manager_key";
static final String TEZOS_FORGE_OPERATIONS = "chains/main/blocks/head/helpers/forge/operations";
static final String TEZOS_PREAPPLY_OPERATIONS = "chains/main/blocks/head/helpers/preapply/operations";
static final String TEZOS_RUN_OPERATION = "chains/main/blocks/head/helpers/scripts/run_operation";
static final String TEZOS_INJECT_OPERATIONS = "injection/operation";
private Retrofit retrofitTezos = new Retrofit.Builder()
.baseUrl(letzbakeURI)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//logging for testing
.client(new OkHttpClient.Builder().addInterceptor(
new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
).build())
.build();
private TezosApi tezosApi = retrofitTezos.create(TezosApi.class);
private int requestsCount = 0;
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
public void getAddress(String wallet, SingleObserver<TezosAccountResponse> accountObserver) {
requestsCount++;
Log.i(TAG, "new getAddress request");
Single<TezosAccountResponse> accountSingle = tezosApi.getAccount(wallet)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((object, throwable) -> requestsCount--);
accountSingle.subscribe(accountObserver);
}
public void getMangerKey(String wallet, SingleObserver<String> accountObserver) {
requestsCount++;
Log.i(TAG, "new getManagerKey request");
Single<String> managerKeySingle = tezosApi.getManagerKey(wallet)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((object, throwable) -> requestsCount--);
managerKeySingle.subscribe(accountObserver);
}
public TezosHeaderResponse getHeader() throws Exception { // TODO? not async
requestsCount++;
Log.i(TAG, "new getHeader request");
Response<TezosHeaderResponse> headerResponse = tezosApi.getHeader().execute();
requestsCount--;
if (headerResponse.code() == 200) {
return headerResponse.body();
} else {
throw new Exception("Wrong header response, code: " + headerResponse.code());
}
}
public String forgeOperations(TezosForgeBody tezosForgeBody) throws Exception { // TODO? not async
requestsCount++;
Log.i(TAG, "new forgeOperations request");
Response<String> forgeResponse = tezosApi.forgeOperations(tezosForgeBody).execute();
requestsCount--;
if (forgeResponse.code() == 200) {
return forgeResponse.body();
} else {
throw new Exception("Wrong forge response, code: " + forgeResponse.code());
}
}
public void peapplyOperations(TezosPreapplyBody tezosPreapplyBody) throws Exception {
Log.i(TAG, "new peapplyOperations request");
List<TezosPreapplyBody> tezosPreapplyBodyList = new ArrayList<>();
tezosPreapplyBodyList.add(tezosPreapplyBody);
Response<Void> preapplyResponse = tezosApi.preapplyOperations(tezosPreapplyBodyList).execute();
if (preapplyResponse.code() != 200) {
String error = "Preapply error: unknown error";
if (preapplyResponse.errorBody() != null) {
error = "Preapply error: " + preapplyResponse.errorBody().string();
}
Log.e(TAG, error);
throw new Exception(error);
}
}
public void injectOperations(String txForSend, SingleObserver<Object> injectObserver) {
requestsCount++;
Log.i(TAG, "new injectOperations request");
Single<Object> injectSingle = tezosApi.injectOperations(txForSend)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((object, throwable) -> requestsCount--);
injectSingle.subscribe(injectObserver);
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.data.network;
import com.tangem.data.network.model.TezosAccountResponse;
import com.tangem.data.network.model.TezosForgeBody;
import com.tangem.data.network.model.TezosHeaderResponse;
import com.tangem.data.network.model.TezosPreapplyBody;
import java.util.List;
import io.reactivex.Single;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
public interface TezosApi {
@GET(ServerApiTezos.TEZOS_ADDRESS)
Single<TezosAccountResponse> getAccount(@Path("address") String address);
@GET(ServerApiTezos.TEZOS_HEADER)
Call<TezosHeaderResponse> getHeader();
@GET(ServerApiTezos.TEZOS_MANAGER_KEY)
Single<String> getManagerKey(@Path("address") String address);
@POST(ServerApiTezos.TEZOS_FORGE_OPERATIONS)
Call<String> forgeOperations(@Body TezosForgeBody tezosForgeBody);
@POST(ServerApiTezos.TEZOS_PREAPPLY_OPERATIONS)
Call<Void> preapplyOperations(@Body List<TezosPreapplyBody> tezosPreapplyBodyList);
@POST(ServerApiTezos.TEZOS_INJECT_OPERATIONS)
Single<Object> injectOperations(@Body String txForSend);
}

View file

@ -0,0 +1,25 @@
package com.tangem.data.network.model
data class TezosForgeBody(
val branch: String,
val contents: List<TezosOperationContent>
)
data class TezosOperationContent(
val kind: String,
val source: String,
val fee: String,
val counter: String,
val gas_limit: String,
val storage_limit: String,
val public_key: String? = null,
val destination: String? = null,
val amount: String? = null
)
data class TezosPreapplyBody(
val protocol: String,
val branch: String,
val contents: List<TezosOperationContent>,
val signature: String
)

View file

@ -0,0 +1,19 @@
package com.tangem.data.network.model
import com.google.gson.annotations.SerializedName
data class TezosAccountResponse(
@SerializedName("balance")
var balance: Long? = null,
@SerializedName("counter")
var counter: Long? = null
)
data class TezosHeaderResponse(
@SerializedName("protocol")
var protocol: String? = null,
@SerializedName("hash")
var hash: String? = null
)

View file

@ -16,6 +16,7 @@ import com.tangem.wallet.nftToken.NftTokenEngine
import com.tangem.wallet.rsk.RskEngine
import com.tangem.wallet.rsk.RskTokenEngine
import com.tangem.wallet.token.TokenEngine
import com.tangem.wallet.tezos.TezosEngine
import com.tangem.wallet.xlm.XlmAssetEngine
import com.tangem.wallet.xlm.XlmEngine
import com.tangem.wallet.xlmtag.XlmTagEngine
@ -52,6 +53,7 @@ object CoinEngineFactory {
Blockchain.StellarTag -> XlmTagEngine()
Blockchain.Eos -> EosEngine()
Blockchain.Ducatus -> DucatusEngine()
Blockchain.Tezos -> TezosEngine()
else -> null
}
}
@ -93,6 +95,8 @@ object CoinEngineFactory {
EosEngine(context)
else if (Blockchain.Ducatus == context.blockchain)
DucatusEngine(context)
else if (Blockchain.Tezos == context.blockchain)
TezosEngine(context)
else
return null
} catch (e: Exception) {

View file

@ -503,7 +503,7 @@ public class CardanoEngine extends CoinEngine {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
@Override

View file

@ -0,0 +1,49 @@
package com.tangem.wallet.tezos
import android.os.Bundle
import android.util.Log
import com.tangem.wallet.CoinData
import java.lang.Exception
class TezosData : CoinData() {
var balance: Long? = null
var counter: Long? = null
var publicKeyReavealed: Boolean? = null
var tezosPublicKey: String? = null
override fun clearInfo() {
super.clearInfo()
balance = null
counter = null
publicKeyReavealed = null
tezosPublicKey = null
}
override fun loadFromBundle(B: Bundle) {
super.loadFromBundle(B)
if (B.containsKey("Balance")) balance = B.getLong("Balance")
if (B.containsKey("Counter")) counter = B.getLong("Counter")
if (B.containsKey("PublicKeyReavealed")) publicKeyReavealed = B.getBoolean("PublicKeyReavealed")
if (B.containsKey("TezosPublicKey")) tezosPublicKey = B.getString("TezosPublicKey")
}
override fun saveToBundle(B: Bundle) {
super.saveToBundle(B)
try {
if (balance != null) B.putLong("Balance", balance!!)
if (counter != null) B.putLong("Counter", counter!!)
if (publicKeyReavealed != null) B.putBoolean("PublicKeyReavealed", publicKeyReavealed!!)
if (tezosPublicKey != null) B.putString("TezosPublicKey", tezosPublicKey!!)
} catch (e: Exception) {
Log.e("Can't save to bundle ", e.message)
}
}
}

View file

@ -0,0 +1,507 @@
package com.tangem.wallet.tezos
import android.net.Uri
import android.os.StrictMode
import android.text.InputFilter
import android.util.Log
import com.tangem.App
import com.tangem.data.network.ServerApiTezos
import com.tangem.data.network.model.TezosAccountResponse
import com.tangem.data.network.model.TezosForgeBody
import com.tangem.data.network.model.TezosOperationContent
import com.tangem.data.network.model.TezosPreapplyBody
import com.tangem.tangem_card.data.TangemCard
import com.tangem.tangem_card.tasks.SignTask
import com.tangem.tangem_card.util.Util
import com.tangem.util.CryptoUtil
import com.tangem.util.DecimalDigitsInputFilter
import com.tangem.wallet.*
import io.reactivex.observers.DisposableSingleObserver
import org.spongycastle.jcajce.provider.digest.Blake2b
import java.math.BigDecimal
class TezosEngine : CoinEngine {
constructor()
constructor(context: TangemContext) : super(context) {
if (context.coinData == null) {
coinData = TezosData()
context.coinData = coinData
} else if (context.coinData is TezosData) {
coinData = context.coinData as TezosData
} else {
throw Exception("Invalid type of Blockchain data for XlmEngine")
}
}
private val TAG = TezosEngine::class.java.simpleName
var coinData: TezosData? = null
private fun getDecimals() = 6
@Throws(Exception::class)
private fun checkBlockchainDataExists() {
if (coinData == null) throw Exception("No blockchain data")
}
override fun awaitingConfirmation(): Boolean {
return App.pendingTransactionsStorage.hasTransactions(ctx.card)
}
override fun getBalanceHTML(): String? {
val balance = balance
return if (balance != null) {
balance.toDescriptionString(getDecimals())
} else {
""
}
}
override fun getBalanceCurrency(): String? {
return "XTZ"
}
override fun isBalanceNotZero(): Boolean {
if (coinData == null) return false
return if (balance == null) false else balance!!.notZero()
}
override fun hasBalanceInfo(): Boolean {
return if (coinData == null) false else coinData!!.balance != null
}
override fun isExtractPossible(): Boolean {
if (!hasBalanceInfo()) {
ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data)
} else if (!isBalanceNotZero) {
ctx.setMessage(R.string.general_wallet_empty)
} else if (awaitingConfirmation()) {
ctx.setMessage(R.string.loaded_wallet_message_wait)
} else {
return true
}
return false
}
override fun getFeeCurrency(): String? {
return "XTZ"
}
override fun validateAddress(address: String?): Boolean {
val prefixedHashWithChecksum = Base58.decodeBase58(address)
if (prefixedHashWithChecksum == null || prefixedHashWithChecksum.size != 27) return false
val prefixedHash = prefixedHashWithChecksum.copyOf(23)
val checksum = prefixedHashWithChecksum.copyOfRange(23, 27)
val calcChecksum = CryptoUtil.doubleSha256(prefixedHash).copyOfRange(0, 4)
return calcChecksum.contentEquals(checksum)
}
override fun isNeedCheckNode(): Boolean {
return false
}
override fun getWalletExplorerUri(): Uri? {
return Uri.parse("https://tezblock.io/account/" + ctx.coinData.wallet)
}
override fun getShareWalletUri(): Uri? {
return Uri.parse(ctx.coinData.wallet)
}
override fun getAmountInputFilters(): Array<InputFilter>? {
return arrayOf(DecimalDigitsInputFilter(getDecimals()))
}
override fun checkNewTransactionAmount(amount: Amount): Boolean {
if (coinData == null) return false
return amount <= balance
}
override fun checkNewTransactionAmountAndFee(amountValue: Amount?,
feeValue: Amount?,
isIncludeFee: Boolean
): Boolean {
try {
checkBlockchainDataExists()
} catch (e: java.lang.Exception) {
e.printStackTrace()
return false
}
if (feeValue == null || amountValue == null) return false
if (feeValue.isZero || amountValue.isZero) return false
if (isIncludeFee && (amountValue > balance || amountValue < feeValue)) return false
if (!isIncludeFee && amountValue.add(feeValue) > balance) return false
return true
}
override fun validateBalance(balanceValidator: BalanceValidator): Boolean {
try {
if (ctx.card.offlineBalance == null &&
!ctx.coinData.isBalanceReceived ||
!ctx.coinData.isBalanceReceived &&
ctx.card.remainingSignatures != ctx.card.maxSignatures
) {
balanceValidator.setScore(0)
balanceValidator.firstLine = R.string.balance_validator_first_line_unknown_balance
balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance)
return false
}
if (coinData!!.isBalanceReceived) {
balanceValidator.setScore(100)
balanceValidator.firstLine = R.string.balance_validator_first_line_verified_balance
balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain)
if (balance!!.isZero) {
balanceValidator.firstLine = R.string.balance_validator_first_line_empty_wallet
balanceValidator.setSecondLine(R.string.empty_string)
}
}
if (ctx.card.offlineBalance != null &&
!coinData!!.isBalanceReceived &&
ctx.card.remainingSignatures ==
ctx.card.maxSignatures &&
balance!!.notZero()
) {
balanceValidator.setScore(80)
balanceValidator.firstLine = R.string.balance_validator_first_line_verified_offline
balanceValidator.setSecondLine(
R.string.balance_validator_second_line_internet_to_get_balance
)
}
return true
} catch (e: java.lang.Exception) {
e.printStackTrace()
return false
}
}
override fun getBalance(): Amount? {
var balanceAmount: Amount? = null
if (hasBalanceInfo()) {
val xtzBalance = BigDecimal
.valueOf(coinData!!.balance!!).movePointLeft(getDecimals())
balanceAmount = Amount(xtzBalance, balanceCurrency)
}
return balanceAmount
}
override fun evaluateFeeEquivalent(fee: String?): String? {
return if (!coinData!!.amountEquivalentDescriptionAvailable) "" else try {
val feeAmount = Amount(fee, feeCurrency)
feeAmount.toEquivalentString(coinData!!.rate.toDouble())
} catch (e: java.lang.Exception) {
""
}
}
override fun getBalanceEquivalent(): String? {
if (coinData == null || !coinData!!.amountEquivalentDescriptionAvailable) return ""
val balance = balance ?: return ""
return balance.toEquivalentString(coinData!!.rate.toDouble())
}
override fun calculateAddress(pkUncompressed: ByteArray): String? {
val publicKeyHash = Blake2b.Blake2b160().digest(pkUncompressed)
val tz1Prefix = Util.hexToBytes("06A19F")
val prefixedHash = tz1Prefix + publicKeyHash
val checksum = CryptoUtil.doubleSha256(prefixedHash).copyOfRange(0, 4)
val prefixedHashWithChecksum = prefixedHash + checksum
return Base58.encodeBase58(prefixedHashWithChecksum)
}
fun calculateTezosPublicKey(pkUncompressed: ByteArray): String {
val edpkPrefix = Util.hexToBytes("0D0F25D9")
val prefixedPubKey = edpkPrefix + pkUncompressed
val checksum = CryptoUtil.doubleSha256(prefixedPubKey).copyOfRange(0, 4)
val prefixedHashWithChecksum = prefixedPubKey + checksum
return Base58.encodeBase58(prefixedHashWithChecksum)
}
override fun convertToAmount(internalAmount: InternalAmount): Amount {
return Amount(internalAmount.movePointLeft(getDecimals()), balanceCurrency)
}
override fun convertToAmount(strAmount: String, currency: String): Amount {
return Amount(strAmount, currency)
}
override fun convertToInternalAmount(amount: Amount): InternalAmount {
return InternalAmount(amount.movePointRight(getDecimals()), "mutez")
}
override fun convertToInternalAmount(bytes: ByteArray?): InternalAmount? {
if (bytes == null) return null
val reversed = ByteArray(bytes.size)
for (i in bytes.indices) reversed[i] = bytes[bytes.size - i - 1]
return InternalAmount(Util.byteArrayToLong(reversed), "mutez")
}
override fun convertToByteArray(internalAmount: InternalAmount): ByteArray? {
val bytes = Util.longToByteArray(internalAmount.longValueExact())
val reversed = ByteArray(bytes.size)
for (i in bytes.indices) reversed[i] = bytes[bytes.size - i - 1]
return reversed
}
override fun createCoinData(): CoinData {
return TezosData()
}
override fun getUnspentInputsDescription() = ""
override fun constructTransaction(
amountValue: Amount,
feeValue: Amount,
IncFee: Boolean,
targetAddress: String
): SignTask.TransactionToSign {
checkBlockchainDataExists()
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val finalAmount = if (IncFee) {
amountValue.minus(feeValue)
} else {
amountValue
}
val serverApiTezos = ServerApiTezos()
val headerResponse = serverApiTezos.header
val contents = arrayListOf<TezosOperationContent>()
var counter = coinData!!.counter!!
if (!coinData!!.publicKeyReavealed!!) {
counter++
val revealOp = TezosOperationContent(
kind = "reveal",
source = coinData!!.wallet,
fee = "1300",
counter = counter.toString(),
gas_limit = "10000",
storage_limit = "0",
public_key = coinData!!.tezosPublicKey!!
)
contents.add(revealOp)
}
counter++
val transactionOp = TezosOperationContent(
kind = "transaction",
source = coinData!!.wallet,
fee = "1350",
counter = counter.toString(),
gas_limit = "10600",
storage_limit = "277",
destination = targetAddress,
amount = finalAmount.movePointRight(getDecimals()).toBigInteger().toString()
)
contents.add(transactionOp)
val tezosForgeBody = TezosForgeBody(headerResponse.hash!!, contents)
val forgeResponse = serverApiTezos.forgeOperations(tezosForgeBody)
val watermark = "03"
val forgedBytes = Util.hexToBytes(watermark + forgeResponse)
return object : SignTask.TransactionToSign {
override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean {
return signingMethod == TangemCard.SigningMethod.Sign_Hash
}
@Throws(java.lang.Exception::class)
override fun getHashesToSign(): Array<ByteArray?> {
val dataForSign = arrayOfNulls<ByteArray>(1)
dataForSign[0] = Blake2b.Blake2b256().digest(forgedBytes)
return dataForSign
}
@Throws(java.lang.Exception::class)
override fun getRawDataToSign(): ByteArray? {
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
}
@Throws(java.lang.Exception::class)
override fun getHashAlgToSign(): String? {
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
}
@Throws(java.lang.Exception::class)
override fun getIssuerTransactionSignature(dataToSignByIssuer: ByteArray?): ByteArray? {
throw java.lang.Exception("Transaction validation by issuer not supported in this version")
}
@Throws(java.lang.Exception::class)
override fun onSignCompleted(signFromCard: ByteArray): ByteArray? {
val edsigPrefix = Util.hexToBytes("09F5CD8612")
val prefixedSignature = edsigPrefix + signFromCard
val checksum = CryptoUtil.doubleSha256(prefixedSignature).copyOfRange(0, 4)
val prefixedSignatureWithChecksum = prefixedSignature + checksum
val preapplyBody = TezosPreapplyBody(
protocol = headerResponse.protocol!!,
branch = headerResponse.hash!!,
contents = contents,
signature = Base58.encodeBase58(prefixedSignatureWithChecksum)
)
try {
serverApiTezos.peapplyOperations(preapplyBody)
} catch (e: java.lang.Exception) {
ctx.error = e.message
return null
}
val txForSend = Util.hexToBytes(forgeResponse) + signFromCard
notifyOnNeedSendTransaction(txForSend)
return txForSend
}
}
}
override fun requestBalanceAndUnspentTransactions(
blockchainRequestsCallbacks: BlockchainRequestsCallbacks
) {
coinData!!.tezosPublicKey = calculateTezosPublicKey(ctx.card.walletPublicKey)
val serverApiTezos = ServerApiTezos()
val accountObserver = object : DisposableSingleObserver<TezosAccountResponse>() {
override fun onSuccess(response: TezosAccountResponse) {
coinData!!.balance = response.balance
coinData!!.isBalanceReceived = true
coinData!!.counter = response.counter
if (serverApiTezos.isRequestsSequenceCompleted) {
blockchainRequestsCallbacks.onComplete(ctx.hasError())
} else {
blockchainRequestsCallbacks.onProgress()
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "requestBalanceAndUnspentTransactions error " + e.message)
ctx.error = e.message
e.printStackTrace()
if (serverApiTezos.isRequestsSequenceCompleted) {
blockchainRequestsCallbacks.onComplete(false)
} else {
blockchainRequestsCallbacks.onProgress()
}
}
}
val managerKeyObserver = object : DisposableSingleObserver<String>() {
override fun onSuccess(response: String) {
coinData!!.publicKeyReavealed = true
if (serverApiTezos.isRequestsSequenceCompleted) {
blockchainRequestsCallbacks.onComplete(ctx.hasError())
} else {
blockchainRequestsCallbacks.onProgress()
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "requestFee error " + e.message)
e.printStackTrace()
coinData!!.publicKeyReavealed = false // error expected when key is not revealed
if (serverApiTezos.isRequestsSequenceCompleted) {
blockchainRequestsCallbacks.onComplete(ctx.hasError())
} else {
blockchainRequestsCallbacks.onProgress()
}
}
}
serverApiTezos.getAddress(coinData!!.wallet, accountObserver)
serverApiTezos.getMangerKey(coinData!!.wallet, managerKeyObserver)
}
override fun requestFee(
blockchainRequestsCallbacks: BlockchainRequestsCallbacks,
targetAddress: String?,
amount: Amount?
) {
var fee: BigDecimal = BigDecimal.valueOf(0.00135)
if (!coinData!!.publicKeyReavealed!!) {
fee += BigDecimal.valueOf(0.0013)
}
val serverApiTezos = ServerApiTezos()
val accountObserver = object : DisposableSingleObserver<TezosAccountResponse>() {
override fun onSuccess(response: TezosAccountResponse) {
if (response.balance == 0L) {
fee += BigDecimal.valueOf(0.257)
}
val feeAmount = Amount(fee, feeCurrency)
coinData!!.minFee = feeAmount
coinData!!.normalFee = feeAmount
coinData!!.maxFee = feeAmount
blockchainRequestsCallbacks.onComplete(true)
}
override fun onError(e: Throwable) {
Log.e(TAG, "requestFee error " + e.message)
ctx.error = e.message
e.printStackTrace()
blockchainRequestsCallbacks.onComplete(false)
}
}
serverApiTezos.getAddress(targetAddress, accountObserver)
}
override fun requestSendTransaction(
blockchainRequestsCallbacks: BlockchainRequestsCallbacks,
txForSend: ByteArray?
) {
if (txForSend == null) {
blockchainRequestsCallbacks.onComplete(false)
} else {
val injectObserver = object : DisposableSingleObserver<Any>() {
override fun onSuccess(response: Any) {
blockchainRequestsCallbacks.onComplete(true)
}
override fun onError(e: Throwable) {
blockchainRequestsCallbacks.onComplete(false)
}
}
ServerApiTezos().injectOperations(Util.bytesToHex(txForSend), injectObserver)
}
}
override fun allowSelectFeeLevel(): Boolean {
return false
}
override fun pendingTransactionTimeoutInSeconds(): Int {
return 60
}
}

View file

@ -6,7 +6,7 @@ buildscript {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath 'io.fabric.tools:gradle:1.31.0'