Updated on 2026-08-14
This commit is contained in:
commit
7516c39c82
14 changed files with 514 additions and 7 deletions
|
|
@ -32,7 +32,8 @@ public enum Blockchain {
|
|||
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"),
|
||||
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus"),
|
||||
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos"),
|
||||
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo");
|
||||
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo"),
|
||||
TokenEmv("TTW", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum");
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ApiInfuraRopsten {
|
||||
public static final String URL_INFURA_ROPSTEN = ServerURL.API_INFURA_ROPSTEN;
|
||||
|
||||
public static class Method {
|
||||
public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ApiSoChain {
|
||||
public static final String URL = ServerURL.API_SOCHAIN_V2;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public class ServerApiInfura {
|
|||
public ServerApiInfura(Blockchain blockchain) {
|
||||
if (blockchain == Blockchain.EthereumTestNet) {
|
||||
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraTestnet().create(InfuraApi.class);
|
||||
} else if (blockchain == Blockchain.TokenEmv) {
|
||||
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraRopsten().create(InfuraApi.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.data.network
|
||||
|
||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
|
||||
import com.tangem.data.network.model.TokenEmvTransferAnswer
|
||||
import com.tangem.data.network.model.TokenEmvTransferBody
|
||||
import com.tangem.tangem_card.util.Log
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ServerApiTokenEmv {
|
||||
private val TAG = ServerApiTokenEmv::class.java.simpleName
|
||||
|
||||
private val tangemServer = "https://emvsupport.appspot.com/"
|
||||
|
||||
private val tokenEmvApi = Retrofit.Builder()
|
||||
.baseUrl(tangemServer)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addConverterFactory(ScalarsConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //logging for testing
|
||||
.client(OkHttpClient.Builder().addInterceptor(
|
||||
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
).build())
|
||||
.build()
|
||||
.create(TokenEmvApi::class.java)
|
||||
|
||||
fun transfer(tokenEmvTransferBody: TokenEmvTransferBody, transferObserver: SingleObserver<TokenEmvTransferAnswer>) {
|
||||
Log.i(TAG, "new transfer request")
|
||||
|
||||
tokenEmvApi.transfer(tokenEmvTransferBody)
|
||||
.timeout(30, TimeUnit.SECONDS)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(transferObserver)
|
||||
}
|
||||
|
||||
fun getTransferFee(tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody, transferObserver: SingleObserver<TokenEmvGetTransferFeeAnswer>) {
|
||||
Log.i(TAG, "new get transfer fee request")
|
||||
|
||||
tokenEmvApi.getTransferFee(tokenEmvGetTransferFeeBody)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(transferObserver)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ class ServerURL {
|
|||
static final String API_COINMARKETCAP = "https://pro-api.coinmarketcap.com/";
|
||||
static final String API_INFURA = "https://mainnet.infura.io/";
|
||||
static final String API_INFURA_TESTNET = "https://rinkeby.infura.io/";
|
||||
static final String API_INFURA_ROPSTEN = "https://ropsten.infura.io/";
|
||||
static final String API_SOCHAIN_V2 = "https://chain.so/";
|
||||
static final String API_ESTIMATEFEE = "https://estimatefee.com/";
|
||||
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";
|
||||
|
|
|
|||
17
app/src/main/java/com/tangem/data/network/TokenEmvApi.kt
Normal file
17
app/src/main/java/com/tangem/data/network/TokenEmvApi.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.network
|
||||
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
|
||||
import com.tangem.data.network.model.TokenEmvTransferAnswer
|
||||
import com.tangem.data.network.model.TokenEmvTransferBody
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface TokenEmvApi {
|
||||
@POST("./card/transfer")
|
||||
fun transfer(@Body tokenEmvTransferBody: TokenEmvTransferBody): Single<TokenEmvTransferAnswer>
|
||||
@POST("./card/transfer/fee")
|
||||
fun getTransferFee(@Body tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody): Single<TokenEmvGetTransferFeeAnswer>
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.data.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class TokenEmvTransferBody(
|
||||
val CID: String,
|
||||
val publicKey: String,
|
||||
val amount: String,
|
||||
val currency: String,
|
||||
val recipient: String,
|
||||
@SerializedName("fee_limit")
|
||||
val feeLimit: String,
|
||||
val sequence: Int,
|
||||
val signature: String
|
||||
)
|
||||
|
||||
data class TokenEmvTransferAnswer(
|
||||
val error: String?,
|
||||
val errorCode: Int?,
|
||||
val success: Boolean?,
|
||||
val tx_id: String?,
|
||||
val blockchain_tx_id: String?
|
||||
)
|
||||
|
||||
data class TokenEmvGetTransferFeeBody(
|
||||
val CID: String,
|
||||
val publicKey: String
|
||||
)
|
||||
|
||||
data class TokenEmvGetTransferFeeAnswer(
|
||||
val error: String?,
|
||||
val errorCode: Int?,
|
||||
val success: Boolean?,
|
||||
val fee: String?,
|
||||
val currency: String?
|
||||
)
|
||||
|
|
@ -17,6 +17,9 @@ interface NetworkComponent {
|
|||
@get:Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
|
||||
val retrofitInfuraTestnet: Retrofit
|
||||
|
||||
@get:Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
val retrofitInfuraRopsten: Retrofit
|
||||
|
||||
@get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
val retrofitMaticTesnet: Retrofit
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,18 @@ internal class NetworkModule {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
fun provideRetrofitInfuraRopsten(): Retrofit {
|
||||
val builder = Retrofit.Builder()
|
||||
.baseUrl(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
if (BuildConfig.DEBUG)
|
||||
builder.client(createOkHttpClient())
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiRootstock.URL_ROOTSTOCK)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ 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.tokenEmv.TokenEmvEngine
|
||||
import com.tangem.wallet.xlm.XlmAssetEngine
|
||||
import com.tangem.wallet.xlm.XlmEngine
|
||||
import com.tangem.wallet.xlmTag.XlmTagEngine
|
||||
|
|
@ -60,6 +61,7 @@ object CoinEngineFactory {
|
|||
Blockchain.Tezos -> TezosEngine()
|
||||
Blockchain.BitcoinDual -> BtcMultisigEngine()
|
||||
Blockchain.FlowDemo -> FlowDemoEngine()
|
||||
Blockchain.TokenEmv -> TokenEmvEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +111,8 @@ object CoinEngineFactory {
|
|||
BtcMultisigEngine(context)
|
||||
else if (Blockchain.FlowDemo == context.blockchain)
|
||||
FlowDemoEngine(context)
|
||||
else if (Blockchain.TokenEmv == context.blockchain)
|
||||
TokenEmvEngine(context)
|
||||
else
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class EthTransaction {
|
|||
return kec.digest(plainMsg);
|
||||
}
|
||||
|
||||
public int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
|
||||
public static int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
|
||||
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
|
||||
int recId = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
|
|
|||
|
|
@ -8,31 +8,46 @@ import com.tangem.wallet.eth.EthData;
|
|||
|
||||
public class TokenData extends EthData {
|
||||
private CoinEngine.InternalAmount balanceAlter = null;
|
||||
|
||||
// private Integer sequence = null;
|
||||
|
||||
@Override
|
||||
public void clearInfo() {
|
||||
super.clearInfo();
|
||||
balanceAlter = null;
|
||||
// sequence = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceAlterInInternalUnits() {
|
||||
return balanceAlter;
|
||||
|
||||
}
|
||||
|
||||
public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) {
|
||||
balanceAlter = value;
|
||||
}
|
||||
|
||||
// public Integer getSequence() {
|
||||
// return sequence;
|
||||
// }
|
||||
//
|
||||
// public void setSequence(Integer sequence) {
|
||||
// this.sequence = sequence;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
super.loadFromBundle(B);
|
||||
|
||||
if( B.containsKey("BalanceDecimalAlter" )) {
|
||||
if (B.containsKey("BalanceDecimalAlter")) {
|
||||
balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei");
|
||||
}else{
|
||||
balanceAlter=null;
|
||||
} else {
|
||||
balanceAlter = null;
|
||||
}
|
||||
// if (B.containsKey("Sequence")) {
|
||||
// sequence = B.getInt("Sequence");
|
||||
// } else {
|
||||
// sequence = null;
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -40,9 +55,12 @@ public class TokenData extends EthData {
|
|||
super.saveToBundle(B);
|
||||
|
||||
try {
|
||||
if( balanceAlter!=null ) {
|
||||
if (balanceAlter != null) {
|
||||
B.putString("BalanceDecimalAlter", balanceAlter.toString());
|
||||
}
|
||||
// if (sequence != null) {
|
||||
// B.putInt("Sequence", sequence);
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
|
|
|||
349
app/src/main/java/com/tangem/wallet/tokenEmv/TokenEmvEngine.kt
Normal file
349
app/src/main/java/com/tangem/wallet/tokenEmv/TokenEmvEngine.kt
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
package com.tangem.wallet.tokenEmv
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.InputFilter
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.data.network.ServerApiInfura
|
||||
import com.tangem.data.network.ServerApiTokenEmv
|
||||
import com.tangem.data.network.model.*
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.reader.CardProtocol.TangemException
|
||||
import com.tangem.tangem_card.reader.TLV
|
||||
import com.tangem.tangem_card.reader.TLVList
|
||||
import com.tangem.tangem_card.tasks.SignTask
|
||||
import com.tangem.tangem_card.util.Util
|
||||
import com.tangem.util.DecimalDigitsInputFilter
|
||||
import com.tangem.wallet.EthTransaction
|
||||
import com.tangem.wallet.Keccak256
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import io.reactivex.observers.DisposableSingleObserver
|
||||
import org.apache.commons.lang3.SerializationUtils
|
||||
import org.kethereum.extensions.toBytesPadded
|
||||
import java.math.BigInteger
|
||||
|
||||
class TokenEmvEngine : TokenEngine {
|
||||
constructor() : super()
|
||||
constructor(context: TangemContext) : super(context)
|
||||
|
||||
private val TAG = TokenEmvEngine::class.java.simpleName
|
||||
|
||||
private fun hasLinkedContract(): Boolean {
|
||||
return ctx.card.issuerData != null && ctx.card.issuerData.size == 44
|
||||
}
|
||||
|
||||
override fun getBlockchain(): Blockchain {
|
||||
return Blockchain.TokenEmv
|
||||
}
|
||||
|
||||
override fun getChainIdNum(): Int {
|
||||
return EthTransaction.ChainEnum.Ropsten.value
|
||||
}
|
||||
|
||||
override fun getBalance(): Amount? {
|
||||
return if (!hasBalanceInfo()) {
|
||||
null
|
||||
} else {
|
||||
convertToAmount(coinData.balanceInInternalUnits)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceHTML(): String? {
|
||||
return if (hasLinkedContract()) {
|
||||
if (balance != null) {
|
||||
balance!!.toDescriptionString(tokenDecimals)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
"NO LINKED CONTRACT"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceCurrency(): String? {
|
||||
return ctx.card.getTokenSymbol()
|
||||
}
|
||||
|
||||
override fun getAmountInputFilters(): Array<InputFilter>? {
|
||||
return arrayOf(DecimalDigitsInputFilter(tokenDecimals))
|
||||
}
|
||||
|
||||
override fun getFeeCurrency(): String? {
|
||||
return balanceCurrency
|
||||
}
|
||||
|
||||
override fun isBalanceNotZero(): Boolean {
|
||||
if (coinData == null) return false
|
||||
return if (coinData.balanceInInternalUnits == null) {
|
||||
false
|
||||
} else {
|
||||
coinData.balanceInInternalUnits.notZero()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceEquivalent(): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun evaluateFeeEquivalent(fee: String?): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun defineWallet() {
|
||||
try {
|
||||
if (hasLinkedContract()) {
|
||||
ctx.coinData.wallet = TLVList.fromBytes(ctx.card.issuerData).getTLV(TLV.Tag.TAG_Token_Contract_Address).asString
|
||||
} else {
|
||||
ctx.coinData.wallet = calculateAddress(ctx.card.walletPublicKey)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ctx.coinData.wallet = "ERROR"
|
||||
throw TangemException("Can't define wallet address")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasBalanceInfo(): Boolean {
|
||||
return coinData.balanceInInternalUnits != null
|
||||
}
|
||||
|
||||
override fun getWalletExplorerUri(): Uri {
|
||||
return Uri.parse("https://ropsten.etherscan.io/token/" + getContractAddress(ctx.card) + "?a=" + ctx.coinData.wallet)
|
||||
}
|
||||
|
||||
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 checkNewTransactionAmountAndFee(amount: Amount, fee: Amount?, isFeeIncluded: Boolean): Boolean {
|
||||
try {
|
||||
if (isFeeIncluded && (amount > balance || amount < fee)) return false
|
||||
if (!isFeeIncluded && amount.add(fee) > balance) return false
|
||||
} catch (e: NumberFormatException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun constructTransaction(amountValue: Amount, feeValue: Amount?, IncFee: Boolean, targetAddress: String?): SignTask.TransactionToSign? {
|
||||
val functionBytes = "transfer".toByteArray()
|
||||
|
||||
val contractHex = coinData.wallet.substring(2)
|
||||
val contractBytes = Util.hexToBytes(contractHex)
|
||||
val recipientHex = targetAddress!!.substring(2)
|
||||
val recipientBytes = Util.hexToBytes(recipientHex)
|
||||
|
||||
val amountBytes = convertToInternalAmount(amountValue).toBigInteger().toBytesPadded(32)
|
||||
val feeLimitBytes = convertToInternalAmount(feeValue).toBigInteger().toBytesPadded(32)
|
||||
|
||||
val sequence = ctx.card.SignedHashes
|
||||
val sequenceBytes = sequence.toBigInteger().toBytesPadded(4)
|
||||
|
||||
val hashToSign = Keccak256().digest(contractBytes + functionBytes + amountBytes + recipientBytes + feeLimitBytes + sequenceBytes)
|
||||
|
||||
return object : SignTask.TransactionToSign {
|
||||
override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash
|
||||
}
|
||||
|
||||
override fun getHashesToSign(): Array<ByteArray> {
|
||||
return arrayOf(hashToSign)
|
||||
}
|
||||
|
||||
@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 r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32))
|
||||
// var s: BigInteger? = BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64))
|
||||
// s = CryptoUtil.toCanonicalised(s)
|
||||
//
|
||||
// val publicKey = ctx.getCard().getWalletPublicKey()
|
||||
//
|
||||
// val verified = ECKey.verify(hashToSign, ECKey.ECDSASignature(r, s), publicKey)
|
||||
// if (!verified) {
|
||||
// Log.e(this.javaClass.simpleName + "-CHECK", "sign Failed.")
|
||||
// }
|
||||
//
|
||||
// val v = BruteRecoveryID2(ECDSASignatureETH(r, s), hashToSign, publicKey)
|
||||
// if (v != 27 && v != 28) {
|
||||
// Log.e(TAG, "invalid v")
|
||||
// throw java.lang.Exception("Error in " + this.javaClass.simpleName + " - invalid v")
|
||||
// }
|
||||
// Log.e(TAG, this.javaClass.simpleName + " V: " + v.toString())
|
||||
//
|
||||
// var rBytes = r.toByteArray()
|
||||
// if (rBytes.size == 33) {
|
||||
// rBytes = rBytes.copyOfRange(1,33)
|
||||
// }
|
||||
// val sBytes = s.toByteArray()
|
||||
//
|
||||
// val tokenEmvTransferBody = TokenEmvTransferBody(
|
||||
// CID = Util.bytesToHex(ctx.card.cid),
|
||||
// publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
|
||||
// contract = contractHex,
|
||||
// amount = Util.byteArrayToHexString(amountBytes),
|
||||
// recipient = recipientHex,
|
||||
// feeLimit = Util.byteArrayToHexString(feeLimitBytes),
|
||||
// sequence = sequence,
|
||||
// r = Util.byteArrayToHexString(rBytes),
|
||||
// s = Util.byteArrayToHexString(sBytes),
|
||||
// v = v
|
||||
// )
|
||||
|
||||
val tokenEmvTransferBody = TokenEmvTransferBody(
|
||||
CID = Util.bytesToHex(ctx.card.cid),
|
||||
publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
|
||||
amount = amountValue.toValueString(),
|
||||
currency = amountValue.currency,
|
||||
recipient = targetAddress,
|
||||
feeLimit = feeValue!!.toValueString(),
|
||||
sequence = sequence,
|
||||
signature = Util.bytesToHex(signFromCard)
|
||||
)
|
||||
|
||||
val jsonBody = Gson().toJson(tokenEmvTransferBody)
|
||||
val serializedBody = SerializationUtils.serialize(jsonBody)
|
||||
|
||||
notifyOnNeedSendTransaction(serializedBody)
|
||||
return serializedBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
|
||||
val serverApiInfura = ServerApiInfura(ctx.blockchain)
|
||||
|
||||
val responseListener: ServerApiInfura.ResponseListener = object : ServerApiInfura.ResponseListener {
|
||||
override fun onSuccess(method: String, infuraResponse: InfuraResponse) {
|
||||
try {
|
||||
var balanceCap = infuraResponse.result
|
||||
balanceCap = balanceCap!!.substring(2)
|
||||
val l = BigInteger(balanceCap, 16)
|
||||
coinData.balanceInInternalUnits = InternalAmount(l, ctx.card.tokenSymbol)
|
||||
coinData.isBalanceReceived = true
|
||||
// Log.i("$TAG eth_call", balanceCap)
|
||||
} catch (e: java.lang.Exception) {
|
||||
onFail(method, e.message ?: "invalid response")
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
|
||||
override fun onFail(method: String, message: String) {
|
||||
Log.e(TAG, "onFail: $method $message")
|
||||
ctx.error = message
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
serverApiInfura.setResponseListener(responseListener)
|
||||
|
||||
if (validateAddress(getContractAddress(ctx.card))) {
|
||||
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.wallet, getContractAddress(ctx.card), "")
|
||||
} else {
|
||||
ctx.error = "Smart contract address not defined"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestFee(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, targetAddress: String?, amount: Amount) {
|
||||
|
||||
val tokenEmvTransferBody = TokenEmvGetTransferFeeBody(
|
||||
CID = Util.bytesToHex(ctx.card.cid),
|
||||
publicKey = Util.bytesToHex(ctx.card.walletPublicKey)
|
||||
)
|
||||
|
||||
val observer = object : DisposableSingleObserver<TokenEmvGetTransferFeeAnswer>() {
|
||||
override fun onError(e: Throwable) {
|
||||
ctx.error = "Can't get transfer fee, ${e.message}"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
override fun onSuccess(t: TokenEmvGetTransferFeeAnswer) {
|
||||
if (t.success != null && t.success) {
|
||||
ctx.error = null
|
||||
coinData.minFee = Amount(t.fee, t.currency)
|
||||
coinData.normalFee = Amount(t.fee, t.currency)
|
||||
coinData.maxFee = Amount(t.fee, t.currency)
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
if (t.error != null) {
|
||||
ctx.error = t.error
|
||||
} else {
|
||||
ctx.error = "Can't get transfer fee, code ${t.errorCode}"
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ServerApiTokenEmv().getTransferFee(tokenEmvTransferBody, observer)
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
|
||||
override fun requestSendTransaction(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, txForSend: ByteArray?) {
|
||||
val jsonBody = SerializationUtils.deserialize<String>(txForSend)
|
||||
|
||||
Log.e(TAG, jsonBody)
|
||||
|
||||
val tokenEmvTransferBody = Gson().fromJson(jsonBody, TokenEmvTransferBody::class.java)
|
||||
|
||||
val observer = object : DisposableSingleObserver<TokenEmvTransferAnswer>() {
|
||||
override fun onError(e: Throwable) {
|
||||
ctx.error = "Can't send transfer, ${e.message}"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
override fun onSuccess(t: TokenEmvTransferAnswer) {
|
||||
if (t.success != null && t.success) {
|
||||
ctx.error = null
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
if (t.error != null) {
|
||||
ctx.error = t.error
|
||||
} else {
|
||||
ctx.error = "Can't send transfer, code ${t.errorCode}"
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ServerApiTokenEmv().transfer(tokenEmvTransferBody, observer)
|
||||
}
|
||||
|
||||
override fun allowSelectFeeLevel(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun pendingTransactionTimeoutInSeconds(): Int {
|
||||
return 60
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue