diff --git a/app/src/main/assets/abi/abiMap.json b/app/src/main/assets/abi/abiMap.json index fdb9fadd..73d4bdb1 100644 --- a/app/src/main/assets/abi/abiMap.json +++ b/app/src/main/assets/abi/abiMap.json @@ -13,5 +13,5 @@ "0xb440dd674e1243644791a4adfe3a2abb0a92d309": "Synthetix.json", "0x448a5065aebb8e423f0896e6c5d525c040f59af3": "Maker.json", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "WETH.json", - "0x111111125434b319222cdbf8c261674adb56f3ae":"1inch exchange v2.json" + "0x111111125434b319222cdbf8c261674adb56f3ae": "1inch exchange v2.json" } \ No newline at end of file diff --git a/app/src/main/java/com/keystone/cold/Utilities.java b/app/src/main/java/com/keystone/cold/Utilities.java index f7e0d6ec..d8ec54f2 100644 --- a/app/src/main/java/com/keystone/cold/Utilities.java +++ b/app/src/main/java/com/keystone/cold/Utilities.java @@ -31,6 +31,7 @@ import com.keystone.cold.ui.modal.ModalDialog; import static android.content.Context.MODE_PRIVATE; +import static com.keystone.cold.ui.fragment.main.EthTxConfirmFragment.PREFERENCE_KEY_VISITS; import static com.keystone.cold.ui.fragment.setting.FingerprintPreferenceFragment.FINGERPRINT_UNLOCK; public class Utilities { @@ -57,6 +58,7 @@ public class Utilities { public static final String ATTACK_DETECTED = "attack_detected"; public static final String INPUT_SETTINGS_CLEARED = "input_settings_cleared"; + public static void alert(AppCompatActivity activity, @Nullable String title, @NonNull String message, String buttonText, Runnable action) { @@ -212,4 +214,14 @@ public static boolean isAttackDetected(Context context) { SharedPreferences sp = context.getSharedPreferences(PREFERENCE_SECRET, MODE_PRIVATE); return sp.getBoolean(ATTACK_DETECTED,false); } + + public static int getVisitsTimes(Context context) { + SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE); + return sp.getInt(PREFERENCE_KEY_VISITS, 0); + } + + public static void setVisitsTimes(Context context, int visits) { + SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE); + sp.edit().putInt(PREFERENCE_KEY_VISITS, visits).apply(); + } } diff --git a/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxConfirmFragment.java b/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxConfirmFragment.java index d6fcb733..5ac72503 100644 --- a/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxConfirmFragment.java +++ b/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxConfirmFragment.java @@ -28,10 +28,13 @@ import android.view.LayoutInflater; import android.view.View; +import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.ViewModelProviders; +import com.keystone.cold.MainApplication; import com.keystone.cold.R; +import com.keystone.cold.Utilities; import com.keystone.cold.callables.FingerprintPolicyCallable; import com.keystone.cold.databinding.AbiItemBinding; import com.keystone.cold.databinding.EthTxConfirmBinding; @@ -48,7 +51,6 @@ import org.json.JSONObject; import java.util.List; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,10 +61,11 @@ import static com.keystone.cold.ui.fragment.setup.PreImportFragment.ACTION; public class EthTxConfirmFragment extends BaseFragment { - + public static final String PREFERENCE_KEY_VISITS = "visits_times"; private EthTxConfirmViewModel viewModel; private SigningDialog signingDialog; private TxEntity txEntity; + public static boolean isFromTFCard; private final Runnable forgetPassword = () -> { Bundle bundle = new Bundle(); bundle.putString(ACTION, PreImportFragment.ACTION_RESET_PWD); @@ -72,6 +75,7 @@ public class EthTxConfirmFragment extends BaseFragment { public static Pattern pattern = Pattern.compile("(?<=\\()[^\\)]+"); public static Pattern pattern1 = Pattern.compile("(?<=\\[)[^]]+"); + @Override protected int setView() { return R.layout.eth_tx_confirm; @@ -80,6 +84,7 @@ protected int setView() { @Override protected void init(View view) { Bundle data = requireArguments(); + mBinding.ethTx.checkInfo.setVisibility(View.VISIBLE); mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp()); viewModel = ViewModelProviders.of(this).get(EthTxConfirmViewModel.class); try { @@ -96,6 +101,24 @@ protected void init(View view) { e.printStackTrace(); } mBinding.sign.setOnClickListener(v -> handleSign()); + mBinding.ethTx.info.setOnClickListener(view1 -> realShowDialog()); + showDialog(); + } + + private void showDialog() { + int visits = Utilities.getVisitsTimes(mActivity); + if (visits++ == 0) { + realShowDialog(); + Utilities.setVisitsTimes(mActivity, visits); + } + } + + private void realShowDialog() { + ModalDialog.showCommonModal((AppCompatActivity) getActivity(), + getString(R.string.tip), + getString(R.string.learn_more), + getString(R.string.know), + null); } private void handleParseException(Exception ex) { @@ -169,8 +192,12 @@ private void updateUI() { JSONObject abi = viewModel.getAbi(); if (abi != null) { updateAbiView(abi); + mBinding.ethTx.data.setVisibility(View.VISIBLE); + mBinding.ethTx.undecodedData.setVisibility(View.GONE); } else { mBinding.ethTx.data.setVisibility(View.GONE); + mBinding.ethTx.undecodedData.setVisibility(View.VISIBLE); + mBinding.ethTx.inputData.setText("0x" + viewModel.getInputData()); } mBinding.ethTx.setTx(txEntity); processAndUpdateTo(); @@ -194,16 +221,20 @@ private void processAndUpdateTo() { private void updateAbiView(JSONObject abi) { if (abi != null) { try { + if (isFromTFCard) { + isFromTFCard = false; + mBinding.ethTx.tfcardTip.setVisibility(View.VISIBLE); + } String contract = abi.getString("contract"); boolean isUniswap = contract.toLowerCase().contains("uniswap"); - List itemList = new AbiItemAdapter(txEntity.getFrom(),viewModel).adapt(abi); + List itemList = new AbiItemAdapter(txEntity.getFrom(), viewModel).adapt(abi); for (AbiItemAdapter.AbiItem item : itemList) { AbiItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mActivity), R.layout.abi_item, null, false); binding.key.setText(item.key); if (isUniswap && "to".equals(item.key)) { if (!item.value.equalsIgnoreCase(txEntity.getFrom())) { - item.value += String.format(" [%s]",getString(R.string.inconsistent_address)); + item.value += String.format(" [%s]", getString(R.string.inconsistent_address)); } binding.value.setText(highLight(item.value)); } else { @@ -221,18 +252,19 @@ private void updateAbiView(JSONObject abi) { protected void initData(Bundle savedInstanceState) { } + public static SpannableStringBuilder highLight(String content) { SpannableStringBuilder spannable = new SpannableStringBuilder(content); Matcher matcher = pattern.matcher(spannable); while (matcher.find()) - spannable.setSpan(new ForegroundColorSpan(0xff00cdc3), matcher.start() - 1 , + spannable.setSpan(new ForegroundColorSpan(MainApplication.getApplication().getColor(R.color.icon_select)), matcher.start() - 1, matcher.end() + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE); matcher = pattern1.matcher(spannable); while (matcher.find()) { - spannable.replace(matcher.start() - 1, matcher.start(),"("); - spannable.replace(matcher.end(), matcher.end() + 1,")"); - spannable.setSpan(new ForegroundColorSpan(Color.RED), matcher.start() - 1 , + spannable.replace(matcher.start() - 1, matcher.start(), "("); + spannable.replace(matcher.end(), matcher.end() + 1, ")"); + spannable.setSpan(new ForegroundColorSpan(Color.RED), matcher.start() - 1, matcher.end() + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE); } return spannable; diff --git a/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxFragment.java b/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxFragment.java index fe735094..370ebf3a 100644 --- a/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxFragment.java +++ b/app/src/main/java/com/keystone/cold/ui/fragment/main/EthTxFragment.java @@ -24,6 +24,7 @@ import android.view.LayoutInflater; import android.view.View; +import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.ViewModelProviders; @@ -32,6 +33,7 @@ import com.keystone.cold.databinding.EthTxBinding; import com.keystone.cold.db.entity.TxEntity; import com.keystone.cold.ui.fragment.BaseFragment; +import com.keystone.cold.ui.modal.ModalDialog; import com.keystone.cold.viewmodel.CoinListViewModel; import com.keystone.cold.viewmodel.EthTxConfirmViewModel; import com.keystone.cold.viewmodel.WatchWallet; @@ -42,7 +44,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Objects; import static com.keystone.cold.ui.fragment.main.EthTxConfirmFragment.highLight; import static com.keystone.cold.ui.fragment.main.TxFragment.KEY_TX_ID; @@ -71,6 +72,15 @@ protected void init(View view) { } }); viewModel = ViewModelProviders.of(this).get(EthTxConfirmViewModel.class); + mBinding.ethTx.info.setOnClickListener(view1 -> showDialog()); + } + + private void showDialog() { + ModalDialog.showCommonModal((AppCompatActivity) getActivity(), + getString(R.string.tip), + getString(R.string.learn_more), + getString(R.string.know), + null); } private void updateUI() { @@ -120,11 +130,23 @@ private void updateAbiView(JSONObject abi) { } mBinding.ethTx.container.addView(binding.getRoot()); } + mBinding.ethTx.data.setVisibility(View.VISIBLE); + mBinding.ethTx.undecodedData.setVisibility(View.GONE); } catch (JSONException e) { e.printStackTrace(); } } else { mBinding.ethTx.data.setVisibility(View.GONE); + mBinding.ethTx.undecodedData.setVisibility(View.VISIBLE); + JSONObject signData = null; + try { + signData = new JSONObject(txEntity.getSignedHex()); + } catch (JSONException e) { + e.printStackTrace(); + } + if (signData != null) { + mBinding.ethTx.inputData.setText("0x" + signData.optString("inputData")); + } } } diff --git a/app/src/main/java/com/keystone/cold/ui/fragment/main/QRCodeScanFragment.java b/app/src/main/java/com/keystone/cold/ui/fragment/main/QRCodeScanFragment.java index a1b1804e..ff79fe7d 100644 --- a/app/src/main/java/com/keystone/cold/ui/fragment/main/QRCodeScanFragment.java +++ b/app/src/main/java/com/keystone/cold/ui/fragment/main/QRCodeScanFragment.java @@ -215,7 +215,7 @@ private void handleUR(String res) { } } - private void handleException(Exception e) { + public void handleException(Exception e) { e.printStackTrace(); if (e instanceof InvalidTransactionException || e instanceof InvalidUOSException) { alert(getString(R.string.unresolve_tx), diff --git a/app/src/main/java/com/keystone/cold/viewmodel/EthTxConfirmViewModel.java b/app/src/main/java/com/keystone/cold/viewmodel/EthTxConfirmViewModel.java index e23e4a6d..dc9b6fee 100644 --- a/app/src/main/java/com/keystone/cold/viewmodel/EthTxConfirmViewModel.java +++ b/app/src/main/java/com/keystone/cold/viewmodel/EthTxConfirmViewModel.java @@ -34,6 +34,7 @@ import com.keystone.coinlib.interfaces.SignCallback; import com.keystone.coinlib.interfaces.Signer; import com.keystone.coinlib.path.CoinPath; +import com.keystone.coinlib.utils.AbiLoader; import com.keystone.coinlib.utils.Coins; import com.keystone.cold.AppExecutors; import com.keystone.cold.R; @@ -42,6 +43,7 @@ import com.keystone.cold.db.entity.CoinEntity; import com.keystone.cold.db.entity.TxEntity; import com.keystone.cold.encryption.ChipSigner; +import com.keystone.cold.ui.fragment.main.EthTxConfirmFragment; import org.json.JSONArray; import org.json.JSONException; @@ -69,6 +71,7 @@ public class EthTxConfirmViewModel extends TxConfirmViewModel { private String messageData; private String messageSignature; private String fromAddress; + private String inputData; public EthTxConfirmViewModel(@NonNull Application application) { super(application); @@ -93,7 +96,7 @@ public JSONObject getContractMap() { return contractMap; } - public String recognizeAddress(String to){ + public String recognizeAddress(String to) { try { String addressSymbol = null; JSONArray tokensMap = getTokensMap(); @@ -104,12 +107,13 @@ public String recognizeAddress(String to){ break; } } - if (addressSymbol == null) { JSONObject bundleMap = getContractMap(); String abiFile = bundleMap.optString(to.toLowerCase()); if (!TextUtils.isEmpty(abiFile)) { addressSymbol = abiFile.replace(".json", ""); + } else { + addressSymbol = recognizeAddressFromTFCard(to); } } return addressSymbol; @@ -119,6 +123,20 @@ public String recognizeAddress(String to){ return null; } + private String recognizeAddressFromTFCard(String to) { + String addressSymbol = null; + try { + String contentFromSdCard = AbiLoader.getContentFromSdCard(EthImpl.ABI_JSON_SDCARD_PATH, to); + if (!TextUtils.isEmpty(contentFromSdCard)) { + JSONObject sdCardJsonObject = new JSONObject(contentFromSdCard); + addressSymbol = sdCardJsonObject.optString("name"); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return addressSymbol; + } + public String getNetwork(int chainId) { Network network = Network.getNetwork(chainId); if (network == null) { @@ -126,7 +144,7 @@ public String getNetwork(int chainId) { } String networkName = network.name(); if (chainId != 1) { - networkName += String.format(" (%s)",context.getString(R.string.testnet)); + networkName += String.format(" (%s)", context.getString(R.string.testnet)); } return networkName; } @@ -146,7 +164,7 @@ public void parseTxData(JSONObject object) { hdPath = object.getString("hdPath"); signId = object.getString("signId"); txHex = object.getString("txHex"); - JSONObject ethTx = EthImpl.decodeRawTransaction(txHex); + JSONObject ethTx = EthImpl.decodeRawTransaction(txHex, () -> EthTxConfirmFragment.isFromTFCard = true); if (ethTx == null) { observableTx.postValue(null); parseTxException.postValue(new InvalidTransactionException("invalid transaction")); @@ -156,7 +174,9 @@ public void parseTxData(JSONObject object) { String data = ethTx.getString("data"); try { abi = new JSONObject(data); - } catch (JSONException ignore) { } + } catch (JSONException ignore) { + inputData = data; + } TxEntity tx = generateTxEntity(ethTx); observableTx.postValue(tx); @@ -324,6 +344,7 @@ protected TxEntity onSignSuccess(String txId, String rawTx) { signed.put("signId", signId); signed.put("chainId", chainId); signed.put("abi", abi); + signed.put("inputData", inputData); tx.setSignedHex(signed.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -344,6 +365,7 @@ private void signTransaction(@NonNull SignCallback callback, Signer signer) { callback.onSuccess(result.txId, result.txHex); } } + private void signMessage(@NonNull SignCallback callback, Signer signer) { if (signer == null) { callback.onFail(); @@ -360,7 +382,7 @@ private void signMessage(@NonNull SignCallback callback, Signer signer) { private Signer initSigner() { String authToken = getAuthToken(); if (TextUtils.isEmpty(authToken)) { - Log.w(TAG,"authToken null"); + Log.w(TAG, "authToken null"); return null; } return new ChipSigner(hdPath.toLowerCase(), authToken); @@ -381,4 +403,8 @@ public String getTxHex() { public int getChainId() { return chainId; } + + public String getInputData() { + return inputData; + } } diff --git a/app/src/main/java/com/keystone/cold/viewmodel/QrScanViewModel.java b/app/src/main/java/com/keystone/cold/viewmodel/QrScanViewModel.java index b7ccd8f2..3023a4de 100644 --- a/app/src/main/java/com/keystone/cold/viewmodel/QrScanViewModel.java +++ b/app/src/main/java/com/keystone/cold/viewmodel/QrScanViewModel.java @@ -30,6 +30,7 @@ import com.keystone.coinlib.coins.ETH.EthImpl; import com.keystone.coinlib.exception.CoinNotFindException; import com.keystone.coinlib.exception.InvalidTransactionException; +import com.keystone.cold.AppExecutors; import com.keystone.cold.R; import com.keystone.cold.callables.GetMasterFingerprintCallable; import com.keystone.cold.protocol.ZipUtil; @@ -106,25 +107,30 @@ private void decodeAndProcess(JSONObject object) handleSignXrpTx(object); return; } - break; + throw new InvalidTransactionException("unknown qr code type"); case KEYSTONE: if (object.optString("type").equals("TYPE_SIGN_TX")) { handleSign(object); return; } - break; + throw new InvalidTransactionException("unknown qr code type"); case METAMASK: String txHex = object.optString("txHex"); JSONObject data = object.optJSONObject("data"); - if (!TextUtils.isEmpty(txHex) && EthImpl.decodeRawTransaction(txHex) != null) { - handleSignMetamaskTx(object); - return; - } else if (data != null) { - handleSignMetamaskMessage(object); - return; - } + AppExecutors.getInstance().diskIO().execute(() -> { + try { + if (!TextUtils.isEmpty(txHex) && EthImpl.decodeRawTransaction(txHex, null) != null) { + handleSignMetamaskTx(object); + } else if (data != null) { + handleSignMetamaskMessage(object); + } else { + throw new InvalidTransactionException("unknown qr code type"); + } + } catch (XfpNotMatchException | InvalidTransactionException e) { + AppExecutors.getInstance().mainThread().execute(() -> fragment.handleException(e)); + } + }); } - throw new InvalidTransactionException("unknown qr code type"); } private boolean checkWebAuth(JSONObject object) throws JSONException { @@ -142,7 +148,7 @@ private void handleSignMetamaskTx(JSONObject object) throws XfpNotMatchException } Bundle bundle = new Bundle(); bundle.putString(KEY_TX_DATA, object.toString()); - fragment.navigate(R.id.action_to_ethTxConfirmFragment, bundle); + AppExecutors.getInstance().mainThread().execute(() -> fragment.navigate(R.id.action_to_ethTxConfirmFragment, bundle)); } private void handleSignMetamaskMessage(JSONObject object) throws XfpNotMatchException { @@ -151,7 +157,7 @@ private void handleSignMetamaskMessage(JSONObject object) throws XfpNotMatchExce } Bundle bundle = new Bundle(); bundle.putString(KEY_TX_DATA, object.toString()); - fragment.navigate(R.id.action_to_ethSignMessageFragment, bundle); + AppExecutors.getInstance().mainThread().execute(() -> fragment.navigate(R.id.action_to_ethSignMessageFragment, bundle)); } private void handleSignXrpTx(JSONObject object) { diff --git a/app/src/main/res/drawable-xhdpi/direction.png b/app/src/main/res/drawable-xhdpi/direction.png new file mode 100644 index 00000000..7537f607 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/direction.png differ diff --git a/app/src/main/res/layout/eth_tx_detail.xml b/app/src/main/res/layout/eth_tx_detail.xml index 0e667d45..e8cea67b 100644 --- a/app/src/main/res/layout/eth_tx_detail.xml +++ b/app/src/main/res/layout/eth_tx_detail.xml @@ -116,6 +116,18 @@ android:layout_alignParentBottom="true" /> + + + + android:orientation="vertical" + android:visibility="gone"> + + + + + + + + + + + + + + + + + + + + + + + android:src="@drawable/data_bg1" /> + + android:padding="10dp" + android:paddingTop="16dp"> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1389a1bf..c24a6e7a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -15,7 +15,7 @@ ~ in the file COPYING. If not, see . --> - + Keystone Scan QR Code 我的铠石钱包 @@ -520,6 +520,11 @@ 您触发了“创建自己的助记词”这一高级功能,请确保您已充分了解助记词生成的相关知识后,再使用本功能。否则请关闭当前页面。 我已充分了解随机熵、BIP39、私钥生成等相关知识。 须知 + 提示 + https://keyst.one/eth 了解详情。]]> + 注意!该数据未被解析。 + (解码资源来源于TF卡) + * 签名前,请仔细检查交易详情 请在以下单词中任选一个作为第24个单词 请先将TF卡插入到设备中 确认是否格式化? diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index dacd2dc4..6076bf93 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -38,4 +38,6 @@ #000000 #FF874B #ff0000 + #FF0B0B + #FBC31B diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6909fea3..fb47a72e 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -553,6 +553,11 @@ You have triggered an advanced function of "Create your Recovery Phrase". Please make sure that you fully understand the dangers of this feature before proceeding to generate a recovery phrase with it. Your funds might get compromised if you fail to do so. Proceed at your own risk! I fully understand entropy, BIP39, private key generation and other related material to generate my recovery phrase. DANGER! + Note + https://keyst.one/eth for details.]]> + * Please check transaction details before signing + Attention! It’s a undecoded data. + (Decoding resources are provided by MicroSD Card) Please choose one of the following words as the 24th word Please make sure you have inserted a microSD card Are you SURE ? diff --git a/app/version.properties b/app/version.properties index 36385016..52a1df96 100644 --- a/app/version.properties +++ b/app/version.properties @@ -17,5 +17,5 @@ #Fri Feb 21 13:33:46 CST 2020 major=1 -minor=1 +minor=2 patch=0 diff --git a/coinlib/src/main/java/com/keystone/coinlib/coins/ETH/EthImpl.java b/coinlib/src/main/java/com/keystone/coinlib/coins/ETH/EthImpl.java index 5df87aa9..3a7640c1 100644 --- a/coinlib/src/main/java/com/keystone/coinlib/coins/ETH/EthImpl.java +++ b/coinlib/src/main/java/com/keystone/coinlib/coins/ETH/EthImpl.java @@ -28,9 +28,11 @@ import com.keystone.coinlib.interfaces.Coin; import com.keystone.coinlib.interfaces.SignCallback; import com.keystone.coinlib.interfaces.Signer; +import com.keystone.coinlib.utils.AbiLoader; import com.keystone.coinlib.utils.Coins; import org.bouncycastle.util.encoders.Hex; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.web3j.abi.FunctionEncoder; @@ -49,6 +51,7 @@ import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpType; +import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -61,6 +64,7 @@ import static org.web3j.crypto.TransactionEncoder.asRlpValues; public class EthImpl implements Coin { + public static final String ABI_JSON_SDCARD_PATH = "contracts" + File.separator + "ethereum"; private final int chainId; @@ -109,7 +113,7 @@ protected RawTransaction createRawTransaction(JSONObject metaData) throws JSONEx return RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data); } - public static JSONObject decodeRawTransaction(String txHex) { + public static JSONObject decodeRawTransaction(String txHex, Callback callback) { JSONObject metaData = new JSONObject(); try { RawTransaction rawTx = TransactionDecoder.decode(txHex); @@ -128,7 +132,10 @@ public static JSONObject decodeRawTransaction(String txHex) { if (!TextUtils.isEmpty(abiFile)) { abi = readAsset("abi/" + abiFile); - contractName = abiFile.replace(".json",""); + contractName = abiFile.replace(".json", ""); + } else { + abi = readAbiFromTFCard(rawTx.getTo(), callback); + contractName = contractNameFromTFCard(rawTx.getTo()); } if (TextUtils.isEmpty(abi)) { @@ -162,10 +169,47 @@ public static JSONObject decodeRawTransaction(String txHex) { return metaData; } + private static String contractNameFromTFCard(String to) { + String result = null; + try { + String contentFromSdCard = AbiLoader.getContentFromSdCard(ABI_JSON_SDCARD_PATH, to); + if (!TextUtils.isEmpty(contentFromSdCard)) { + JSONObject sdCardJsonObject = new JSONObject(contentFromSdCard); + result = sdCardJsonObject.optString("name"); + if (TextUtils.isEmpty(result)) { + result = ""; + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + private static String readAbiFromTFCard(String to, Callback callback) { + String result = null; + try { + String contentFromSdCard = AbiLoader.getContentFromSdCard(ABI_JSON_SDCARD_PATH, to); + if (!TextUtils.isEmpty(contentFromSdCard)) { + JSONObject sdCardJsonObject = new JSONObject(contentFromSdCard); + JSONObject metadata = sdCardJsonObject.getJSONObject("metadata"); + JSONObject output = metadata.getJSONObject("output"); + JSONArray abi = output.getJSONArray("abi"); + result = abi.toString(); + if (result != null && callback != null) { + callback.fromTFCard(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + public static String getSignature(String signedHex) { SignedRawTransaction signedTx = (SignedRawTransaction) TransactionDecoder.decode(signedHex); Sign.SignatureData signatureData = signedTx.getSignatureData(); - byte[] signatureBytes = concat(concat(signatureData.getR(),signatureData.getS()),signatureData.getV()); + byte[] signatureBytes = concat(concat(signatureData.getR(), signatureData.getS()), signatureData.getV()); return Hex.toHexString(signatureBytes); } @@ -203,7 +247,7 @@ public byte[] signTransaction(RawTransaction transaction, Signer signer) { return encodeSignedTransaction(transaction, signatureData); } - public Sign.SignatureData getSignatureData(String signature) { + public Sign.SignatureData getSignatureData(String signature) { if (TextUtils.isEmpty(signature)) return null; byte[] r = Hex.decode(signature.substring(0, 64)); byte[] s = Hex.decode(signature.substring(64, 128)); @@ -240,7 +284,7 @@ public String signMessage(@NonNull String message, Signer signer) { byte[] messageHash = new StructuredDataEncoder(message).hashStructuredData(); String signature = signer.sign(Hex.toHexString(messageHash)); Sign.SignatureData signatureData = getSignatureData(signature); - byte[] sigBytes = concat(concat(signatureData.getR(),signatureData.getS()),signatureData.getV()); + byte[] sigBytes = concat(concat(signatureData.getR(), signatureData.getS()), signatureData.getV()); return Hex.toHexString(sigBytes); } catch (IOException e) { e.printStackTrace(); @@ -251,7 +295,7 @@ public String signMessage(@NonNull String message, Signer signer) { public String signPersonalMessage(@NonNull String message, Signer signer) { String signature = signer.sign(Hex.toHexString(hashPersonalMessage(message))); Sign.SignatureData signatureData = getSignatureData(signature); - byte[] sigBytes = concat(concat(signatureData.getR(),signatureData.getS()),signatureData.getV()); + byte[] sigBytes = concat(concat(signatureData.getR(), signatureData.getS()), signatureData.getV()); return Hex.toHexString(sigBytes); } @@ -269,4 +313,8 @@ public String generateAddress(@NonNull String publicKey) { public boolean isAddressValid(@NonNull String address) { return false; } + + public interface Callback { + void fromTFCard(); + } } diff --git a/coinlib/src/main/java/com/keystone/coinlib/utils/AbiLoader.java b/coinlib/src/main/java/com/keystone/coinlib/utils/AbiLoader.java new file mode 100644 index 00000000..33c7c823 --- /dev/null +++ b/coinlib/src/main/java/com/keystone/coinlib/utils/AbiLoader.java @@ -0,0 +1,71 @@ +package com.keystone.coinlib.utils; + +import android.content.Context; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; +import android.text.TextUtils; +import android.util.Log; + +import com.keystone.coinlib.Coinlib; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.List; + +public class AbiLoader { + private static final String TAG = "AbiLoader"; + + public static String getContentFromSdCard(String path, String fileName) { + if (TextUtils.isEmpty(externalSDCardPath())) { + Log.i(TAG, "sdCard is not exists"); + return ""; + } + File file = new File(externalSDCardPath() + File.separator + path, fileName + ".json"); + if (!file.exists()) { + return ""; + } + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bfr = null; + try { + bfr = new BufferedReader(new FileReader(file)); + String line = bfr.readLine(); + while (line != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + line = bfr.readLine(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (bfr != null) { + try { + bfr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return stringBuilder.toString(); + } + + private static String externalSDCardPath() { + String sdCardPath = ""; + try { + StorageManager storageManager = (StorageManager) Coinlib.sInstance.getContext().getSystemService(Context.STORAGE_SERVICE); + + // Android N started to have this method + List storageVolumes = storageManager.getStorageVolumes(); + Class volumeClass = Class.forName("android.os.storage.StorageVolume"); + Method getPath = volumeClass.getDeclaredMethod("getPath"); + getPath.setAccessible(true); + StorageVolume storageVolume = storageVolumes.get(storageVolumes.size() - 1); + sdCardPath = (String) getPath.invoke(storageVolume); + } catch (Exception e) { + e.printStackTrace(); + } + return sdCardPath; + } +} diff --git a/coinlib/src/main/java/com/keystone/coinlib/v8/ScriptLoader.java b/coinlib/src/main/java/com/keystone/coinlib/v8/ScriptLoader.java index e4c8f356..c62ef0d9 100644 --- a/coinlib/src/main/java/com/keystone/coinlib/v8/ScriptLoader.java +++ b/coinlib/src/main/java/com/keystone/coinlib/v8/ScriptLoader.java @@ -16,11 +16,12 @@ */ package com.keystone.coinlib.v8; + import android.content.res.AssetManager; import android.text.TextUtils; -import com.keystone.coinlib.Coinlib; import com.eclipsesource.v8.V8; +import com.keystone.coinlib.Coinlib; import org.json.JSONException; import org.json.JSONObject; @@ -31,6 +32,7 @@ import java.io.InputStreamReader; public class ScriptLoader { + private static final String TAG = "ScriptLoader"; public static ScriptLoader sInstance; @@ -83,4 +85,5 @@ public static String readAsset(String fileName) { } return stringBuilder.toString(); } + }