From 1a54ecc24443366e4f93626e8980fb25e7a9eeb0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Jul 2019 15:18:42 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 3 + .../main/java/com/tangem/data/Blockchain.java | 3 +- .../com/tangem/data/network/ServerApiEos.java | 30 + .../java/com/tangem/util/DerEncodingUtil.java | 84 +-- .../com/tangem/wallet/CoinEngineFactory.kt | 5 + .../java/com/tangem/wallet/EOS/EosData.java | 57 ++ .../java/com/tangem/wallet/EOS/EosEngine.java | 525 ++++++++++++++++++ .../wallet/EOS/EosPackedTransaction.java | 15 + .../java/com/tangem/wallet/EOS/EosPacker.java | 58 ++ .../wallet/EOS/EosPushTransactionRequest.java | 8 + 10 files changed, 745 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiEos.java create mode 100644 app/src/main/java/com/tangem/wallet/EOS/EosData.java create mode 100644 app/src/main/java/com/tangem/wallet/EOS/EosEngine.java create mode 100644 app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java create mode 100644 app/src/main/java/com/tangem/wallet/EOS/EosPacker.java create mode 100644 app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java diff --git a/app/build.gradle b/app/build.gradle index cdcf753d87..b01d90050c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,4 +131,7 @@ dependencies { implementation 'com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.8' implementation 'joda-time:joda-time:2.10.1' testImplementation 'junit:junit:4.12' + + //dependencies for EOS + implementation 'io.jafka:jeos:0.9.15' } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 4ab21c3565..98b451c54e 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -24,7 +24,8 @@ public enum Blockchain { 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"); + StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"), + Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/ServerApiEos.java b/app/src/main/java/com/tangem/data/network/ServerApiEos.java new file mode 100644 index 0000000000..7b5082bebc --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiEos.java @@ -0,0 +1,30 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.wallet.CoinEngine; + +import io.jafka.jeos.EosApi; +import io.jafka.jeos.EosApiFactory; +import io.jafka.jeos.core.response.chain.account.Account; +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(); + + public static void getBalance(String wallet, Observer accountObserver) { + Log.i(TAG, "new getBalance request"); + EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); //TODO: add random server request + + Observable accountObservable = Observable.just(new Account()) + .map(account -> eosApi.getAccount(wallet)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + accountObservable.subscribe(accountObserver); + } + +} diff --git a/app/src/main/java/com/tangem/util/DerEncodingUtil.java b/app/src/main/java/com/tangem/util/DerEncodingUtil.java index 3ff4a66391..f3a88131df 100644 --- a/app/src/main/java/com/tangem/util/DerEncodingUtil.java +++ b/app/src/main/java/com/tangem/util/DerEncodingUtil.java @@ -87,46 +87,46 @@ public class DerEncodingUtil { return bos.toByteArray(); } - public static byte[] DerEncoding(byte[] sign) - { - byte[] r = sign; - byte[] s = new byte[32]; - for(int i =0; i < 32; ++i) - { - s[i] = sign[i+32]; - } - - byte[] newR = PackInteger(r); - byte[] newS = PackInteger(s); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write((byte)(newR.length+newS.length+2)); - baos.write((byte)newR.length); - baos.write(newR, 0, newR.length); - baos.write((byte)newS.length); - baos.write(newS, 0, newS.length); - - return baos.toByteArray(); - } - - public static byte[] DerEncodingBI(BigInteger[] sign) - { - byte[] r = sign[0].toByteArray(); - byte[] s = sign[1].toByteArray(); - - byte[] newR = PackInteger(r); - byte[] newS = PackInteger(s); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - baos.write((byte)(newR.length+newS.length+2)); - - baos.write((byte)newR.length); - baos.write(newR, 0, newR.length); - - baos.write((byte)newS.length); - baos.write(newS, 0, newS.length); - - return baos.toByteArray(); - } +// public static byte[] DerEncoding(byte[] sign) +// { +// byte[] r = sign; +// byte[] s = new byte[32]; +// for(int i =0; i < 32; ++i) +// { +// s[i] = sign[i+32]; +// } +// +// byte[] newR = PackInteger(r); +// byte[] newS = PackInteger(s); +// +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// baos.write((byte)(newR.length+newS.length+2)); +// baos.write((byte)newR.length); +// baos.write(newR, 0, newR.length); +// baos.write((byte)newS.length); +// baos.write(newS, 0, newS.length); +// +// return baos.toByteArray(); +// } +// +// public static byte[] DerEncodingBI(BigInteger[] sign) +// { +// byte[] r = sign[0].toByteArray(); +// byte[] s = sign[1].toByteArray(); +// +// byte[] newR = PackInteger(r); +// byte[] newS = PackInteger(s); +// +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// baos.write((byte)(newR.length+newS.length+2)); +// +// baos.write((byte)newR.length); +// baos.write(newR, 0, newR.length); +// +// baos.write((byte)newS.length); +// baos.write(newS, 0, newS.length); +// +// return baos.toByteArray(); +// } } diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt index 21bf4593cf..cc1af2824a 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -7,6 +7,8 @@ import com.tangem.wallet.eth.EthEngine import com.tangem.wallet.token.TokenEngine import com.tangem.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain +import com.tangem.wallet.EOS.EosData +import com.tangem.wallet.EOS.EosEngine import com.tangem.wallet.binance.BinanceEngine import com.tangem.wallet.cardano.CardanoData import com.tangem.wallet.cardano.CardanoEngine @@ -45,6 +47,7 @@ object CoinEngineFactory { Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine() Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine() Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine() + Blockchain.Eos -> EosEngine() else -> null } } @@ -78,6 +81,8 @@ object CoinEngineFactory { MaticTokenEngine(context) else if (Blockchain.Stellar == context.blockchain || Blockchain.StellarTestNet == context.blockchain) XlmEngine(context) + else if (Blockchain.Eos == context.blockchain) + EosEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosData.java b/app/src/main/java/com/tangem/wallet/EOS/EosData.java new file mode 100644 index 0000000000..aee90b0118 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EOS/EosData.java @@ -0,0 +1,57 @@ +package com.tangem.wallet.EOS; + +import android.os.Bundle; +import android.util.Log; + +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; + +import java.math.BigInteger; + +import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; + +public class EosData extends CoinData { + private CoinEngine.Amount balance = null; + + @Override + public void clearInfo() { + super.clearInfo(); + balance = null; + } + + public CoinEngine.Amount getBalance() { + return balance; + } + + public void setBalance(CoinEngine.Amount value) { + balance = value; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) { + String currency = B.getString("BalanceCurrency"); + balance = new CoinEngine.Amount(B.getString("BalanceDecimal"), currency); + } else { + balance = null; + } + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (balance != null) { + B.putString("BalanceCurrency", balance.getCurrency()); + B.putString("BalanceDecimal", balance.toString()); + } + + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosEngine.java b/app/src/main/java/com/tangem/wallet/EOS/EosEngine.java new file mode 100644 index 0000000000..f7160a1483 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EOS/EosEngine.java @@ -0,0 +1,525 @@ +package com.tangem.wallet.EOS; + +import android.net.Uri; +import android.text.InputFilter; +import android.util.Log; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.tangem.Constant; +import com.tangem.card_common.data.TangemCard; +import com.tangem.card_common.reader.CardProtocol; +import com.tangem.card_common.reader.TLV; +import com.tangem.card_common.tasks.SignTask; +import com.tangem.card_common.util.Util; +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiEos; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.wallet.BTCUtils; +import com.tangem.wallet.BalanceValidator; +import com.tangem.wallet.BuildConfig; +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.R; +import com.tangem.wallet.TangemContext; + +import org.apache.commons.lang3.SerializationUtils; +import org.apache.commons.lang3.StringUtils; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.joda.time.DateTime; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; + +import io.jafka.jeos.EosApi; +import io.jafka.jeos.EosApiFactory; +import io.jafka.jeos.LocalApi; +import io.jafka.jeos.convert.Packer; +import io.jafka.jeos.core.common.SignArg; +import io.jafka.jeos.core.common.transaction.TransactionAction; +import io.jafka.jeos.core.common.transaction.TransactionAuthorization; +import io.jafka.jeos.core.request.chain.json2bin.TransferArg; +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.util.Base58; +import io.jafka.jeos.util.KeyUtil; +import io.jafka.jeos.util.Raw; +import io.jafka.jeos.util.ecc.Ripemd160; +import io.reactivex.Observer; +import io.reactivex.observers.DefaultObserver; + +public class EosEngine extends CoinEngine { + + private static final String TAG = EosEngine.class.getSimpleName(); + public EosData coinData = null; + + public EosEngine(TangemContext ctx) throws Exception { + super(ctx); + if (ctx.getCoinData() == null) { + coinData = new EosData(); + ctx.setCoinData(coinData); + } else if (ctx.getCoinData() instanceof EosData) { + coinData = (EosData) ctx.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName()); + } + } + + public EosEngine() { + super(); + } + + private static int getDecimals() { + return 4; + } + + @Override + public boolean awaitingConfirmation() { + return false; + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) { + return null; + } + return coinData.getBalance(); + } + + @Override + public String getBalanceHTML() { + Amount balance = getBalance(); + if (balance != null) { + return balance.toDescriptionString(getDecimals()); + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + return Blockchain.Eos.getCurrency(); + } + + @Override + public String getOfflineBalanceHTML() { + InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); + Amount offlineAmount = convertToAmount(offlineInternalAmount); + return offlineAmount.toDescriptionString(getDecimals()); + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getBalance() == null) return false; + return coinData.getBalance().notZero(); + } + + @Override + public String getFeeCurrency() { + return Blockchain.Eos.getCurrency(); + } + + public boolean isNeedCheckNode() { + return false; + } + + + @Override + public CoinData createCoinData() { + return new EosData(); + } + + @Override + public String getUnspentInputsDescription() { + return ""; + } + + public void defineWallet() throws CardProtocol.TangemException { + try { + String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); + ctx.getCoinData().setWallet(wallet); + } catch (Exception e) { + ctx.getCoinData().setWallet("ERROR"); + throw new CardProtocol.TangemException("Can't define wallet address"); + } + + } + +// BigDecimal convertToEth(String value) { +// BigInteger m = new BigInteger(value, 10); +// BigDecimal n = new BigDecimal(m); +// BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); +// d = d.setScale(8, RoundingMode.DOWN); +// return d; +// } + + @Override + public boolean validateAddress(String address) { + if (address.length() != 12) { + return false; + } + + if (!address.toLowerCase().equals(address)) { + return false; + } + + if (StringUtils.containsAny(address, "06789")) { + return false; + } + + return true; + } + + @Override + public String getBalanceEquivalent() { + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + return new Amount(internalAmount, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + return new InternalAmount(amount, getBalanceCurrency()); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + //throw new Exception("Not implemented"); + return null; + } + + + @Override + public byte[] convertToByteArray(InternalAmount amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public boolean hasBalanceInfo() { + return coinData.getBalance() != null; + } + + @Override + public Uri getShareWalletUri() { //TODO: check + return Uri.parse(ctx.getCoinData().getWallet()); + } + + @Override + public Uri getWalletExplorerUri() { + return Uri.parse("https://bloks.io/account/" + ctx.getCoinData().getWallet()); + } + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.cannot_obtain_data_from_blockchain); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.please_wait_while_previous); + } else { + return true; + } + return false; + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) { + return true; + } + Amount balance = getBalance(); + if (balance == null || amount.compareTo(balance) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) { + try { + BigDecimal cardBalance = getBalance(); + + if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0)) + return false; + + if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0) + return false; + + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + return true; + } + + @Override //TODO: check + public boolean validateBalance(BalanceValidator balanceValidator) { + if (getBalance() == null) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Unknown balance"); + balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + return false; + } + + if (coinData.isBalanceReceived()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine("Verified balance"); + balanceValidator.setSecondLine("Balance confirmed in blockchain"); + if (getBalance().isZero()) { + balanceValidator.setFirstLine("Empty wallet"); + balanceValidator.setSecondLine(""); + } + } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine("Verified offline balance"); + balanceValidator.setSecondLine("Restore internet connection to obtain trusted balance from blockchain"); + } + + return true; + + } + + @Override + public String evaluateFeeEquivalent(String fee) { + try { + Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency()); + return feeValue.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + @Override + public String calculateAddress(byte[] pkCompressed) { + String cid = Util.bytesToHex(ctx.getCard().getCID()); + String address = "testem" + cid.substring(2, 4) + cid.substring(11, 15);//TODO: change + address = address.replace("0", "o").replace("6","b").replace("7,","t").replace("8","s").replace("9","g"); + return address; + +// byte[] csum = Ripemd160.from(pkCompressed).bytes(); +// csum = Raw.copy(csum, 0, 4); +// byte[] addy = Raw.concat(pkCompressed, csum); +// StringBuffer bf = new StringBuffer("EOS"); +// bf.append(Base58.encode(addy)); +// return bf.toString() + " " + address; + } + + // reference - https://gist.github.com/adyliu/492503b94d0306371298f24e15481da4 + @Override + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws JsonProcessingException { + + // get the current state of blockchain + EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); + SignArg arg = eosApi.getSignArg(120); + System.out.println(eosApi.getObjectMapper().writeValueAsString(arg)); + + // --- prepare transaction for sign as in LocalApiImpl + String quantity = amountValue.setScale(4).toString(); + String memo = "test"; + + // ① pack transfer data + TransferArg transferArg = new TransferArg(coinData.getWallet(), targetAddress, quantity, memo); + String transferData = Packer.packTransfer(transferArg); + // + + // ③ create the authorization + List authorizations = Arrays.asList(new TransactionAuthorization(coinData.getWallet(), "active")); + + // ④ build the all actions + List actions = Arrays.asList(// + new TransactionAction("eosio.token", "transfer", authorizations, transferData)// + ); + + String blockTime = arg.getHeadBlockTime().toString(); + DateTime time = new DateTime(arg.getHeadBlockTime()).plusSeconds(arg.getExpiredSecond()); + String changedTime = time.toString(); + long epochSecond = time.getMillis() / 1000; + + // ⑤ build the packed transaction + EosPackedTransaction packedTransaction = new EosPackedTransaction(); + packedTransaction.setExpirationSec(epochSecond); + packedTransaction.setRefBlockNum(arg.getLastIrreversibleBlockNum()); + packedTransaction.setRefBlockPrefix(arg.getRefBlockPrefix()); + + packedTransaction.setMaxNetUsageWords(0); + packedTransaction.setMaxCpuUsageMs(0); + packedTransaction.setDelaySec(0); + packedTransaction.setActions(actions); + + Raw raw = EosPacker.packPackedTransaction(arg.getChainId(), packedTransaction); + raw.pack(ByteBuffer.allocate(33).array());// TODO: what's this? + Sha256Hash hashForSign = Sha256Hash.of(raw.bytes()); + + return new SignTask.TransactionToSign() { + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash; + } + + @Override + public byte[][] getHashesToSign() throws NoSuchAlgorithmException { + byte[][] hashesForSign = new byte[1][]; + hashesForSign[0] = hashForSign.getBytes(); + return hashesForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName()); + } + + @Override + public String getHashAlgToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName()); + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Transaction validation by issuer not supported in this version"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(Util.calculateSHA256(raw.bytes()), new ECKey.ECDSASignature(r, s), ctx.getCard().getWalletPublicKey()); + + if (!f) { + Log.e(this.getClass().getSimpleName() + "-CHECK", "sign Failed."); + } + + ECKey.ECDSASignature ecdsaSig = new ECKey.ECDSASignature(r, s); + int v = BruteRecoveryID(ecdsaSig, hashForSign, ctx.getCard().getWalletPublicKeyRar()); + v += 4; // compressed + v += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) + + byte[] pub_buf = new byte[65]; + pub_buf[0] = (byte) v; + System.arraycopy(r.toByteArray(), 0, pub_buf, 1, r.toByteArray().length); + System.arraycopy(s.toByteArray(), 0, pub_buf, r.toByteArray().length + 1, s.toByteArray().length); + + byte[] checksum = Ripemd160.from(Raw.concat(pub_buf, "K1".getBytes())).bytes(); + + byte[] signatureBytes = Raw.concat(pub_buf, Raw.copy(checksum, 0, 4)); + + String signatureString = "SIG_K1_" + Base58.encode(signatureBytes); + + EosPushTransactionRequest req = new EosPushTransactionRequest(); + req.setTransaction(packedTransaction); + req.setSignatures(Arrays.asList(signatureString)); + + //serialize + byte[] txForSend = SerializationUtils.serialize(req); + + notifyOnNeedSendTransaction(txForSend); + return txForSend; + } + }; + } + + private int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) { + Log.e("EOS_KZ", BTCUtils.toHex(thisKey)); + int recId = -1; + for (int i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, true); + + if (k == null) + continue; + byte[] recK = k.getPubKey(); + Log.e("EOS_k " + String.valueOf(i), BTCUtils.toHex(recK)); + if (k != null && Arrays.equals(recK, thisKey)) { + recId = i; + break; + } + } + return recId; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + Observer accountObserver = new DefaultObserver() { + @Override + public void onNext(Account account) { + String[] balanceStrings = account.getCoreLiquidBalance().split(" "); + coinData.setBalanceReceived(true); + coinData.setBalance(new Amount(balanceStrings[0], balanceStrings[1])); + } + + @Override + public void onError(Throwable e) { + Log.e(TAG, "requestBalanceAndUnspentTransactions error" + e.getMessage()); + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onComplete() { + blockchainRequestsCallbacks.onComplete(true); + } + }; + ServerApiEos.getBalance(coinData.getWallet(), accountObserver); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + // no fee in EOS + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException, ClassNotFoundException { + // deserialize + EosPushTransactionRequest req = SerializationUtils.deserialize(txForSend); + + try { + EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); + PushedTransaction pts = eosApi.pushTransaction(req); + + LocalApi localApi = EosApiFactory.createLocalApi(); + System.out.println(localApi.getObjectMapper().writeValueAsString(pts)); + blockchainRequestsCallbacks.onComplete(true); + } catch (Exception e) { + Log.e(TAG, "requestSendTransaction error" + e.getMessage()); + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + + public int pendingTransactionTimeoutInSeconds() { + return 10; + } + + @Override + public boolean needMultipleLinesForBalance() { + return true; + } +} diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java b/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java new file mode 100644 index 0000000000..0d55286d8d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java @@ -0,0 +1,15 @@ +package com.tangem.wallet.EOS; + +import io.jafka.jeos.core.common.transaction.PackedTransaction; + +public class EosPackedTransaction extends PackedTransaction { + private Long expirationSec; + + public Long getExpirationSec() { + return expirationSec; + } + + public void setExpirationSec(Long expirationSec) { + this.expirationSec = expirationSec; + } +} diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosPacker.java b/app/src/main/java/com/tangem/wallet/EOS/EosPacker.java new file mode 100644 index 0000000000..4b578c377b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EOS/EosPacker.java @@ -0,0 +1,58 @@ +package com.tangem.wallet.EOS; + +import io.jafka.jeos.convert.Packer; +import io.jafka.jeos.core.common.transaction.TransactionAction; +import io.jafka.jeos.core.common.transaction.TransactionAuthorization; +import io.jafka.jeos.util.Raw; +import io.jafka.jeos.util.ecc.Hex; + +public class EosPacker extends Packer { + public static Raw packPackedTransaction(String chainId, EosPackedTransaction t) { + Raw raw = new Raw(); + //chain + raw.pack(Hex.toBytes(chainId)); + //expiration + raw.packUint32(t.getExpirationSec()); + //ref_block_num + raw.packUint16(t.getRefBlockNum().intValue()); + //ref_block_prefix + raw.packUint32(t.getRefBlockPrefix()); + //max_net_usage_words + raw.packVarint32(t.getMaxNetUsageWords()); + //max_cpu_usage_ms + raw.packUint8(t.getMaxCpuUsageMs());//TODO: what the type? + //delay_sec + raw.packVarint32(t.getDelaySec()); + //context_free_actions + raw.packVarint32(t.getContextFreeActions().size()); + //TODO: getContextFreeActions + + //actions + raw.packVarint32(t.getActions().size()); + + for (TransactionAction a : t.getActions()) { + //action.account + raw.packName(a.getAccount())// + .packName(a.getName())// + .packVarint32(a.getAuthorization().size())// + ; + //action.authorization + for (TransactionAuthorization au : a.getAuthorization()) { + raw.packName(au.getActor())// + .packName(au.getPermission()); + } + + //action.data + byte[] dat = Hex.toBytes(a.getData()); + raw.packVarint32(dat.length); + raw.pack(dat); + } + //transaction_extensions + //raw.packVarint32(t.getTransactionExtensions().size()); + //TODO: getTransactionExtensions + + //context_free_data + //raw.packVarint32(t.getContextFreeActions().size()); + return raw; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java b/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java new file mode 100644 index 0000000000..0899f6a121 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java @@ -0,0 +1,8 @@ +package com.tangem.wallet.EOS; + +import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; + +import java.io.Serializable; + +public class EosPushTransactionRequest extends PushTransactionRequest implements Serializable { +}