From aa824b3dc6b5e5851e9b3682374c2ebf85f2dc13 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Sep 2019 13:19:29 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/data/Blockchain.java | 1 + .../tangem/data/network/ServerApiStellar.java | 19 +- .../fragment/wallet/LoadedWalletFragment.kt | 2 +- .../com/tangem/wallet/CoinEngineFactory.kt | 4 + .../java/com/tangem/wallet/TangemContext.java | 6 + .../com/tangem/wallet/xlm/XlmAssetData.java | 177 +++++ .../com/tangem/wallet/xlm/XlmAssetEngine.java | 610 ++++++++++++++++++ .../java/com/tangem/wallet/xlm/XlmEngine.java | 6 +- 8 files changed, 812 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java create mode 100644 app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 2f50bdd5de..782234d4ed 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -25,6 +25,7 @@ public enum Blockchain { 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"), + StellarAsset("Asset", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"), Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { diff --git a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java index 503519f9be..b63a42274e 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java @@ -31,6 +31,15 @@ import io.reactivex.schedulers.Schedulers; * {@link Listener}.onSuccess(...) callback */ public class ServerApiStellar { + + public ServerApiStellar(Blockchain blockchain) { + if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset) { + currentURL = ServerURL.API_STELLAR; + } else { + currentURL = ServerURL.API_STELLAR_TESTNET; + } + } + private static String TAG = ServerApiStellar.class.getSimpleName(); /** @@ -97,14 +106,6 @@ public class ServerApiStellar { requestsCount++; LOG.i(TAG, String.format("New request[%d]: %s", requestsCount, stellarRequest.getClass().getSimpleName())); - if (currentURL == null) { - if (ctx.getBlockchain() == Blockchain.Stellar) { - currentURL = ServerURL.API_STELLAR; - } else if (ctx.getBlockchain() == Blockchain.StellarTestNet) { - currentURL = ServerURL.API_STELLAR_TESTNET; - } - } - Observable stellarObserver = Observable.just(stellarRequest) .doOnEach(stellarRequest1 -> doStellarRequest(ctx, stellarRequest)) @@ -172,7 +173,7 @@ public class ServerApiStellar { stellarRequest.setError(null); try { Server server; - if (ctx.getBlockchain() == Blockchain.Stellar) { + if (ctx.getBlockchain() == Blockchain.Stellar || ctx.getBlockchain() == Blockchain.StellarAsset) { Network.usePublicNetwork(); server = new Server(currentURL); } else if (ctx.getBlockchain() == Blockchain.StellarTestNet) { diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 537e0d4dbb..b2ef26de9b 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -698,7 +698,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte // Bitcoin, Litecoin, BitcoinCash, Stellar if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || - ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet) { + ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { ctx.coinData.setIsBalanceEqual(true) } diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt index b289fac550..10aaf161f1 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -16,6 +16,7 @@ import com.tangem.wallet.matic.MaticTokenEngine import com.tangem.wallet.nftToken.NftTokenEngine import com.tangem.wallet.rsk.RskEngine import com.tangem.wallet.rsk.RskTokenEngine +import com.tangem.wallet.xlm.XlmAssetEngine import com.tangem.wallet.xlm.XlmEngine import com.tangem.wallet.xrp.XrpEngine @@ -46,6 +47,7 @@ object CoinEngineFactory { Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine() Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine() Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine() + Blockchain.StellarAsset -> XlmAssetEngine() Blockchain.Eos -> EosEngine() else -> null } @@ -80,6 +82,8 @@ object CoinEngineFactory { MaticTokenEngine(context) else if (Blockchain.Stellar == context.blockchain || Blockchain.StellarTestNet == context.blockchain) XlmEngine(context) + else if (Blockchain.StellarAsset == context.blockchain) + XlmAssetEngine(context) else if (Blockchain.Eos == context.blockchain) EosEngine(context) else diff --git a/app/src/main/java/com/tangem/wallet/TangemContext.java b/app/src/main/java/com/tangem/wallet/TangemContext.java index aba5dc3e3f..07cb229329 100644 --- a/app/src/main/java/com/tangem/wallet/TangemContext.java +++ b/app/src/main/java/com/tangem/wallet/TangemContext.java @@ -39,6 +39,9 @@ public class TangemContext { if ((blockchain == Blockchain.Rootstock) && card.isToken()) { return Blockchain.RootstockToken; } + if (blockchain == Blockchain.Stellar && card.isToken()) { + return Blockchain.StellarAsset; + } return blockchain; } @@ -58,6 +61,9 @@ public class TangemContext { if (blockchain == Blockchain.NftToken) { return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " NFT token"; } + if (blockchain == Blockchain.StellarAsset) { + return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset"; + } return blockchain.getOfficialName(); } diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java new file mode 100644 index 0000000000..84c40a7b53 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java @@ -0,0 +1,177 @@ +package com.tangem.wallet.xlm; + +import android.os.Bundle; +import android.util.Log; + +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; + +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.responses.AccountResponse; +import org.stellar.sdk.responses.LedgerResponse; + +import java.math.BigDecimal; + +/* + * Created by dvol on 7.01.2019. + */ + +public class XlmAssetData extends CoinData { + + + public static class AccountResponseEx extends AccountResponse { + AccountResponseEx(String accountId, Long sequenceNumber) { + super(KeyPair.fromAccountId(accountId), sequenceNumber); + } + } + + private CoinEngine.Amount xlmBalance, assetBalance = null; + + private Long sequenceNumber = 0L; + private CoinEngine.Amount baseReserve = new CoinEngine.Amount("0.5", "XLM"); + private CoinEngine.Amount baseFee = new CoinEngine.Amount("0.00001", "XLM"); + private boolean error404 = false; + + @Override + public void clearInfo() { + super.clearInfo(); + xlmBalance = null; + assetBalance = null; + error404 = false; + } + + CoinEngine.Amount getXlmBalance() { + if (xlmBalance != null) { + return new CoinEngine.Amount(xlmBalance.subtract(getReserve()), "XLM"); + } else { + return null; + } + } + + CoinEngine.Amount getAssetBalance() { + return assetBalance; + } + + CoinEngine.Amount getReserve() { + return new CoinEngine.Amount(baseReserve.multiply(BigDecimal.valueOf(3)), "XLM"); + } + + CoinEngine.Amount getBaseFee() { + return baseFee; + } + + AccountResponse getAccountResponse() { + return new AccountResponseEx(getWallet(), sequenceNumber); + } + + void setAccountResponse(AccountResponse accountResponse) { + for (AccountResponse.Balance responseBalance : accountResponse.getBalances()) { + if (responseBalance.getAssetType().equals("native")) { + xlmBalance = new CoinEngine.Amount(responseBalance.getBalance(), "XLM"); + } else { + assetBalance = new CoinEngine.Amount(responseBalance.getBalance(), responseBalance.getAssetCode()); + } + } + sequenceNumber = accountResponse.getSequenceNumber(); + setBalanceReceived(true); + } + + void setLedgerResponse(LedgerResponse ledgerResponse) { + XlmEngine xlmEngine = new XlmEngine(); + baseReserve = xlmEngine.convertToAmount(new CoinEngine.InternalAmount(ledgerResponse.getBaseReserveInStroops(), "stroops")); + baseFee = xlmEngine.convertToAmount(new CoinEngine.InternalAmount(ledgerResponse.getBaseFeeInStroops(), "stroops")); + } + + public void incSequenceNumber() { + sequenceNumber++; + } + + public boolean isError404() { + return error404; + } + + public void setError404(boolean error404) { + this.error404 = error404; + } + + public boolean isAssetBalanceZero() { + if (assetBalance != null && assetBalance.notZero()) + return false; + else + return true; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) { + xlmBalance = new CoinEngine.Amount(B.getString("BalanceDecimal"), B.getString("BalanceCurrency")); + } else { + xlmBalance = null; + } + + if (B.containsKey("AssetBalanceCurrency") && B.containsKey("AssetBalanceDecimal")) { + assetBalance = new CoinEngine.Amount(B.getString("AssetBalanceDecimal"), B.getString("AssetBalanceCurrency")); + } else { + assetBalance = null; + } + + if (B.containsKey("sequenceNumber")) { + sequenceNumber = B.getLong("sequenceNumber"); + } else { + sequenceNumber = 0L; + } + + if (B.containsKey("BaseReserveCurrency") && B.containsKey("BaseReserveDecimal")) { + baseReserve = new CoinEngine.Amount(B.getString("BaseReserveDecimal"), B.getString("BaseReserveCurrency")); + } else { + baseReserve = new CoinEngine.Amount("0.5", "XLM"); + } + + if (B.containsKey("BaseFeeCurrency") && B.containsKey("BaseFeeDecimal")) { + baseFee = new CoinEngine.Amount(B.getString("BaseFeeDecimal"), B.getString("BaseFeeCurrency")); + } else { + baseFee = new CoinEngine.Amount("0.00001", "XLM"); + } + + if (B.containsKey("Error404")) error404 = B.getBoolean("Error404"); + else error404 = false; + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (xlmBalance != null) { + B.putString("BalanceCurrency", xlmBalance.getCurrency()); + B.putString("BalanceDecimal", xlmBalance.toValueString()); + } + + if (assetBalance != null) { + B.putString("AssetBalanceCurrency", assetBalance.getCurrency()); + B.putString("AssetBalanceDecimal", assetBalance.toValueString()); + } + + if (sequenceNumber != null) { + B.putLong("sequenceNumber", sequenceNumber); + } + + if (baseReserve != null) { + B.putString("BaseReserveCurrency", baseReserve.getCurrency()); + B.putString("BaseReserveDecimal", baseReserve.toValueString()); + } + + if (baseFee != null) { + B.putString("BaseFeeCurrency", baseFee.getCurrency()); + B.putString("BaseFeeDecimal", baseFee.toValueString()); + } + + if (error404) B.putBoolean("Error404", true); + + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + } +} + diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java new file mode 100644 index 0000000000..fbaae9dbd2 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java @@ -0,0 +1,610 @@ +package com.tangem.wallet.xlm; + +import android.net.Uri; +import android.os.StrictMode; +import android.text.InputFilter; +import android.util.Log; + +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiStellar; +import com.tangem.data.network.StellarRequest; +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.DecimalDigitsInputFilter; +import com.tangem.wallet.BalanceValidator; +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.R; +import com.tangem.wallet.TangemContext; + +import org.stellar.sdk.Asset; +import org.stellar.sdk.AssetTypeNative; +import org.stellar.sdk.ChangeTrustOperation; +import org.stellar.sdk.CreateAccountOperation; +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.Operation; +import org.stellar.sdk.PaymentOperation; +import org.stellar.sdk.Transaction; +import org.stellar.sdk.TransactionEx; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Created by dvol on 7.01.2019. + *

+ * PS. To create and fill testnet account just open https://friendbot.stellar.org/?addr=XXX in browser + **/ + +public class XlmAssetEngine extends CoinEngine { + + private static final String TAG = XlmAssetEngine.class.getSimpleName(); + + public XlmAssetData coinData = null; + + public XlmAssetEngine(TangemContext context) throws Exception { + super(context); + if (context.getCoinData() == null) { + coinData = new XlmAssetData(); + context.setCoinData(coinData); + } else if (context.getCoinData() instanceof XlmAssetData) { + coinData = (XlmAssetData) context.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for XlmAssetEngine"); + } + } + + public XlmAssetEngine() { + super(); + } + + private static int getDecimals() { + return 7; + } + + + private void checkBlockchainDataExists() throws Exception { + if (coinData == null) throw new Exception("No blockchain data"); + } + + @Override + public boolean awaitingConfirmation() { + if (coinData == null) return false; + //TODO + return false;//coinData.getBalanceUnconfirmed() != 0; + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) return null; + if (!coinData.isAssetBalanceZero()) + return coinData.getAssetBalance(); + else + return coinData.getXlmBalance(); + } + + @Override + public String getBalanceHTML() { + Amount balance = coinData.getXlmBalance(); + Amount assetBalance = coinData.getAssetBalance(); + + if (balance != null) { + if (!coinData.isAssetBalanceZero()) { + return " " + assetBalance.toDescriptionString(getDecimals()) + "
"+ balance.toDescriptionString(getDecimals()) + " for fee + " + coinData.getReserve().toDescriptionString(getDecimals()) + " reserve"; + } + return " " + balance.toDescriptionString(getDecimals()) + "
+ " + coinData.getReserve().toDescriptionString(getDecimals()) + " reserve"; + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + if (!coinData.isAssetBalanceZero()) { + return coinData.getAssetBalance().getCurrency(); + } else { + return "XLM"; + } + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getXlmBalance() == null) return false; + return coinData.getXlmBalance().notZero(); + } + + @Override + public boolean hasBalanceInfo() { + if (coinData == null) return false; + return coinData.getXlmBalance() != null; + } + + + @Override + public boolean isExtractPossible() { + 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 + public String getFeeCurrency() { + return "XLM"; + } + + @Override + public boolean validateAddress(String address) { + try { + KeyPair kp = KeyPair.fromAccountId(address); + // TODO is it possible to check address testNet or not +// if (ctx.getBlockchain() == Blockchain.StellarTestNet) { +// return false; +// } + } catch (Exception e) { + return false; + } + return true; + } + + + @Override + public boolean isNeedCheckNode() { + return false; + } + + @Override + public Uri getWalletExplorerUri() { + return Uri.parse((ctx.getBlockchain() == Blockchain.Stellar ? "http://stellarchain.io/address/" : "http://testnet.stellarchain.io/address/") + ctx.getCoinData().getWallet()); + } + + @Override + public Uri getShareWalletUri() { + //TODO - how to construct payment query intent for stellar? +// if (ctx.getCard().getDenomination() != null) { +// return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString()); +// } else { + return Uri.parse(ctx.getCoinData().getWallet()); +// } + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + + Amount balance; + if (!coinData.isAssetBalanceZero()) { + balance = coinData.getAssetBalance(); + } else { + balance = coinData.getXlmBalance(); + } + + if (amount.compareTo(balance) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { + try { + checkBlockchainDataExists(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if (feeValue == null || amountValue == null) + return false; + + if (feeValue.isZero() || amountValue.isZero()) + return false; + + if (!coinData.isAssetBalanceZero()) { + if (amountValue.compareTo(coinData.getAssetBalance()) > 0 || feeValue.compareTo(coinData.getXlmBalance()) > 0) + return false; + } else { + if (isIncludeFee && (amountValue.compareTo(coinData.getXlmBalance()) > 0 || amountValue.compareTo(feeValue) < 0)) + return false; + if (!isIncludeFee && amountValue.add(feeValue).compareTo(coinData.getXlmBalance()) > 0) + return false; + } + + return true; + } + + @Override + public boolean validateBalance(BalanceValidator balanceValidator) { + try { + if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { + if (coinData.isError404()) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_instruction); + } else { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); + return false; + } + } + + // Workaround before new back-end +// if (card.getRemainingSignatures() == card.getMaxSignatures()) { +// firstLine = "Verified balance"; +// secondLine = "Balance confirmed in blockchain. "; +// secondLine += "Verified note identity. "; +// return; +// } + +// if (coinData.getBalanceUnconfirmed() != 0) { +// balanceValidator.setScore(0); +// balanceValidator.setFirstLine("Transaction in progress"); +// balanceValidator.setSecondLine("Wait for confirmation in blockchain"); +// return false; +// } + + if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); + if (coinData.getXlmBalance().isZero()) { + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); + } + } + + // rule 4 TODO: need to check SignedHashed against number of outputs in blockchain +// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0) +// { +// score = 80; +// firstLine = "Unguaranteed balance"; +// secondLine = "Potential unsent transaction. Redeem immediately if accept. "; +// return; +// } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getXlmBalance().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); + } + +// if(card.getFailedBalanceRequestCounter()!=0) { +// score -= 5 * card.getFailedBalanceRequestCounter(); +// secondLine += "Not all nodes have returned balance. Swipe down or tap again. "; +// if(score <= 0) +// return; +// } + + // +// if(card.isBalanceReceived() && !card.isBalanceEqual()) { +// score = 0; +// firstLine = "Disputed balance"; +// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later."; +// return; +// } + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String evaluateFeeEquivalent(String fee) { + if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; + try { + Amount feeAmount = new Amount(fee, getFeeCurrency()); + return feeAmount.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + return ""; + } + } + + @Override + public String getBalanceEquivalent() { + if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public String calculateAddress(byte[] pkUncompressed) { + KeyPair kp = KeyPair.fromPublicKey(pkUncompressed); + return kp.getAccountId(); + } + + private static BigDecimal multiplier = new BigDecimal("10000000"); + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(multiplier); + return new Amount(d, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + BigDecimal d = amount.multiply(multiplier); + return new InternalAmount(d, "stroops"); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + if (bytes == null) return null; + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return new InternalAmount(Util.byteArrayToLong(reversed), "stroops"); + } + + @Override + public byte[] convertToByteArray(InternalAmount internalAmount) { + byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return reversed; + } + + @Override + public CoinData createCoinData() { + return new XlmAssetData(); + } + + @Override + public String getUnspentInputsDescription() { + return ""; + } + + + @Override + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + checkBlockchainDataExists(); + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + + StrictMode.setThreadPolicy(policy); + + if (coinData.isAssetBalanceZero() && IncFee) { + amountValue = new Amount(amountValue.subtract(feeValue), amountValue.getCurrency()); + } + + Operation operation; + if (coinData.getAssetBalance() == null) { + operation = new ChangeTrustOperation.Builder(Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), "900000000000.0000000").build(); + } else { + if (!coinData.isAssetBalanceZero()) { + operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), amountValue.toValueString()).build(); + } else { + if (isAccountCreated(targetAddress)) + operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build(); + else + operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build(); + } + } + TransactionEx transaction = TransactionEx.buildEx(60, coinData.getAccountResponse(), operation); + + + if (transaction.getFee() != convertToInternalAmount(feeValue).intValueExact()) { + throw new Exception("Invalid fee!"); + } + + return new SignTask.TransactionToSign() { + + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; + } + + @Override + public byte[][] getHashesToSign() throws Exception { + byte[][] dataForSign = new byte[1][]; + dataForSign[0] = transaction.hash(); + return dataForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + return transaction.signatureBase(); + } + + @Override + public String getHashAlgToSign() { + return "sha-256"; + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Issuer validation not supported!"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + // Sign the transaction to prove you are actually the person sending it. + transaction.setSign(signFromCard); + + byte[] txForSend = transaction.toEnvelopeXdrBase64().getBytes(); + notifyOnNeedSendTransaction(txForSend); + return txForSend; + } + }; + } + + + // network call inside, don't use on main thread + private boolean isAccountCreated(String address) { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); + + StellarRequest.Balance request = new StellarRequest.Balance(address); + + try { + serverApi.doStellarRequest(ctx, request); + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + return true; // suppose account is created if anything goes wrong TODO:check + } + + if (request.errorResponse != null && request.errorResponse.getCode() == 404) + return false; + else + return true; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); + + ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { + @Override + public void onSuccess(StellarRequest.Base request) { + Log.i(TAG, "onSuccess: " + request.getClass().getSimpleName()); + + if (request instanceof StellarRequest.Balance) { + StellarRequest.Balance balanceRequest = (StellarRequest.Balance) request; + + coinData.setAccountResponse(balanceRequest.accountResponse); + coinData.setValidationNodeDescription(serverApi.getCurrentURL()); + + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } else if (request instanceof StellarRequest.Ledgers) { + StellarRequest.Ledgers ledgersRequest = (StellarRequest.Ledgers) request; + + coinData.setLedgerResponse(ledgersRequest.ledgerResponse); + + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } else { + ctx.setError("Invalid request logic"); + blockchainRequestsCallbacks.onComplete(false); + } + } + + + @Override + public void onFail(StellarRequest.Base request) { + Log.i(TAG, "onFail: " + request.getClass().getSimpleName() + " " + request.getError()); + + if (request.errorResponse.getCode() == 404) { + coinData.setError404(true); + } else { + ctx.setError(request.getError()); + } + + if (serverApi.isRequestsSequenceCompleted()) { + if (ctx.hasError()) { + blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onComplete(true); + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApi.setListener(listener); + + serverApi.requestData(ctx, new StellarRequest.Balance(coinData.getWallet())); + serverApi.requestData(ctx, new StellarRequest.Ledgers()); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { + // TODO: get fee stats? + coinData.minFee = coinData.normalFee = coinData.maxFee = coinData.getBaseFee(); + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); + + ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { + @Override + public void onSuccess(StellarRequest.Base request) { + try { + if (!StellarRequest.SubmitTransaction.class.isInstance(request)) + throw new Exception("Invalid request logic"); + StellarRequest.SubmitTransaction submitTransactionRequest = (StellarRequest.SubmitTransaction) request; + if (submitTransactionRequest.response.isSuccess()) { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } else { + if (submitTransactionRequest.response.getExtras() != null && submitTransactionRequest.response.getExtras().getResultCodes() != null) { + String trResult = submitTransactionRequest.response.getExtras().getResultCodes().getTransactionResultCode(); + if (submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes() != null && submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes().size() > 0) { + trResult += "/" + submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes().get(0); + } + ctx.setError(trResult); + } else { + ctx.setError("transaction failed"); + } + blockchainRequestsCallbacks.onComplete(false); + } + } 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(StellarRequest.Base request) { + ctx.setError(request.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApi.setListener(listener); + + Transaction transaction = TransactionEx.fromEnvelopeXdr(new String(txForSend)); + coinData.incSequenceNumber(); + serverApi.requestData(ctx, new StellarRequest.SubmitTransaction(transaction)); + + } + + public boolean needMultipleLinesForBalance() { + return true; + } + + public boolean allowSelectFeeLevel() { + return false; + } + + public boolean allowSelectFeeInclusion() { + return coinData.isAssetBalanceZero(); + } + + public int pendingTransactionTimeoutInSeconds() { + return 10; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java index c72948bcdd..f22bdc5c2f 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java @@ -417,7 +417,7 @@ public class XlmEngine extends CoinEngine { // network call inside, don't use on main thread private boolean isAccountCreated(String address) { - final ServerApiStellar serverApi = new ServerApiStellar(); + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); StellarRequest.Balance request = new StellarRequest.Balance(address); @@ -436,7 +436,7 @@ public class XlmEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiStellar serverApi = new ServerApiStellar(); + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { @Override @@ -508,7 +508,7 @@ public class XlmEngine extends CoinEngine { @Override public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException { - final ServerApiStellar serverApi = new ServerApiStellar(); + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { @Override