Browse Source

Electrum (#4)

support electrum
bug_fix
JunZhang 5 years ago
committed by GitHub
parent
commit
6570be80e4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/src/main/java/com/cobo/cold/DataRepository.java
  2. 3
      app/src/main/java/com/cobo/cold/db/dao/TxDao.java
  3. 17
      app/src/main/java/com/cobo/cold/ui/BindingAdapters.java
  4. 8
      app/src/main/java/com/cobo/cold/ui/MainActivity.java
  5. 35
      app/src/main/java/com/cobo/cold/ui/fragment/main/QRCodeScanFragment.java
  6. 60
      app/src/main/java/com/cobo/cold/ui/fragment/main/TransactionItem.java
  7. 91
      app/src/main/java/com/cobo/cold/ui/fragment/main/TransactionItemAdapter.java
  8. 99
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxConfirmFragment.java
  9. 23
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxFragment.java
  10. 12
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxListFragment.java
  11. 17
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/Callback.java
  12. 11
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumBroadcastTxFragment.java
  13. 11
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumExportFragment.java
  14. 58
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxConfirmFragment.java
  15. 39
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxFragment.java
  16. 4
      app/src/main/java/com/cobo/cold/ui/views/DrawerAdapter.java
  17. 90
      app/src/main/java/com/cobo/cold/viewmodel/ElectrumViewModel.java
  18. 53
      app/src/main/java/com/cobo/cold/viewmodel/TxConfirmViewModel.java
  19. 24
      app/src/main/java/com/cobo/cold/viewmodel/XpubNotMatchException.java
  20. 22
      app/src/main/res/drawable/change_bg.xml
  21. 173
      app/src/main/res/layout/electrum_export.xml
  22. 59
      app/src/main/res/layout/electrum_tx_detail.xml
  23. 17
      app/src/main/res/layout/electrum_txn.xml
  24. 16
      app/src/main/res/layout/qrcode_scan_fragment.xml
  25. 6
      app/src/main/res/layout/tx_detail.xml
  26. 19
      app/src/main/res/layout/tx_detail_item.xml
  27. 2
      app/src/main/res/layout/txn_list.xml
  28. 50
      app/src/main/res/values-zh-rCN/strings.xml
  29. 77
      app/src/main/res/values/strings.xml
  30. 8
      coinlib/src/main/java/com/cobo/coinlib/coins/BTC/Electrum/TxUtils.java
  31. 25
      coinlib/src/test/java/com/cobo/coinlib/coin/ElectrumTxTest.java

4
app/src/main/java/com/cobo/cold/DataRepository.java

@ -126,6 +126,10 @@ public class DataRepository {
return mDb.txDao().loadTxs(coinId); return mDb.txDao().loadTxs(coinId);
} }
public List<TxEntity> loadElectrumTxsSync(String coinId) {
return mDb.txDao().loadElectrumTxsSync(coinId);
}
public LiveData<TxEntity> loadTx(String txId) { public LiveData<TxEntity> loadTx(String txId) {
return mDb.txDao().load(txId); return mDb.txDao().load(txId);
} }

3
app/src/main/java/com/cobo/cold/db/dao/TxDao.java

@ -33,6 +33,9 @@ public interface TxDao {
@Query("SELECT * FROM txs where coinId = :coinId ORDER BY timeStamp DESC") @Query("SELECT * FROM txs where coinId = :coinId ORDER BY timeStamp DESC")
LiveData<List<TxEntity>> loadTxs(String coinId); LiveData<List<TxEntity>> loadTxs(String coinId);
@Query("SELECT * FROM txs where coinId = :coinId and signId = 'electrum_sign_id' ORDER BY timeStamp DESC")
List<TxEntity> loadElectrumTxsSync(String coinId);
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(TxEntity tx); void insert(TxEntity tx);

17
app/src/main/java/com/cobo/cold/ui/BindingAdapters.java

@ -17,7 +17,6 @@
package com.cobo.cold.ui; package com.cobo.cold.ui;
import android.content.Context; import android.content.Context;
import android.graphics.Paint;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
@ -74,17 +73,13 @@ public class BindingAdapters {
view.setData(data); view.setData(data);
} }
@BindingAdapter("underline1")
public static void setUnderline1(TextView view, boolean underline) {
if (underline) {
view.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG);
}
}
@BindingAdapter("time") @BindingAdapter("time")
public static void setTimeStamp(TextView view, long time) { public static void setTimeStamp(TextView view, long time) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", //timestamp before 20191001 is invalid
Locale.getDefault()); if (time > 1569859200000L) {
view.setText(formatter.format(time)); SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss",
Locale.getDefault());
view.setText(formatter.format(time));
}
} }
} }

8
app/src/main/java/com/cobo/cold/ui/MainActivity.java

@ -33,6 +33,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDestination; import androidx.navigation.NavDestination;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
@ -52,6 +53,7 @@ import com.cobo.cold.ui.fragment.setting.SettingFragment;
import com.cobo.cold.ui.views.DrawerAdapter; import com.cobo.cold.ui.views.DrawerAdapter;
import com.cobo.cold.ui.views.FullScreenDrawer; import com.cobo.cold.ui.views.FullScreenDrawer;
import com.cobo.cold.ui.views.UpdatingHelper; import com.cobo.cold.ui.views.UpdatingHelper;
import com.cobo.cold.viewmodel.ElectrumViewModel;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -73,6 +75,7 @@ public class MainActivity extends FullScreenActivity {
int currentFragmentIndex = R.id.drawer_wallet; int currentFragmentIndex = R.id.drawer_wallet;
private DrawerAdapter drawerAdapter; private DrawerAdapter drawerAdapter;
private ElectrumViewModel viewModel;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -89,6 +92,7 @@ public class MainActivity extends FullScreenActivity {
if (savedInstanceState == null) { if (savedInstanceState == null) {
new UpdatingHelper(this); new UpdatingHelper(this);
} }
viewModel = ViewModelProviders.of(this).get(ElectrumViewModel.class);
} }
private void initNavController() { private void initNavController() {
@ -223,8 +227,8 @@ public class MainActivity extends FullScreenActivity {
public void setSupportActionBar(@Nullable Toolbar toolbar) { public void setSupportActionBar(@Nullable Toolbar toolbar) {
super.setSupportActionBar(toolbar); super.setSupportActionBar(toolbar);
boolean supportFingerprint = FingerprintKit.isHardwareDetected(this); boolean supportFingerprint = FingerprintKit.isHardwareDetected(this);
if (!Utilities.hasUserClickFingerprint(this) if (!Utilities.hasUserClickPatternLock(this)
|| (supportFingerprint && !Utilities.hasUserClickPatternLock(this))) { || (supportFingerprint && !Utilities.hasUserClickFingerprint(this))) {
this.toolbar = toolbar; this.toolbar = toolbar;
showBadge(toolbar); showBadge(toolbar);
} }

35
app/src/main/java/com/cobo/cold/ui/fragment/main/QRCodeScanFragment.java

@ -19,6 +19,7 @@ package com.cobo.cold.ui.fragment.main;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -45,10 +46,12 @@ import com.cobo.cold.scan.view.PreviewFrame;
import com.cobo.cold.ui.MainActivity; import com.cobo.cold.ui.MainActivity;
import com.cobo.cold.ui.fragment.BaseFragment; import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.modal.ModalDialog; import com.cobo.cold.ui.modal.ModalDialog;
import com.cobo.cold.viewmodel.ElectrumViewModel;
import com.cobo.cold.viewmodel.QrScanViewModel; import com.cobo.cold.viewmodel.QrScanViewModel;
import com.cobo.cold.viewmodel.SharedDataViewModel; import com.cobo.cold.viewmodel.SharedDataViewModel;
import com.cobo.cold.viewmodel.UnknowQrCodeException; import com.cobo.cold.viewmodel.UnknowQrCodeException;
import com.cobo.cold.viewmodel.UuidNotMatchException; import com.cobo.cold.viewmodel.UuidNotMatchException;
import com.cobo.cold.viewmodel.XpubNotMatchException;
import org.json.JSONException; import org.json.JSONException;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
@ -90,6 +93,9 @@ public class QRCodeScanFragment extends BaseFragment<QrcodeScanFragmentBinding>
mBinding.frameView.setZxingConfig(mConfig); mBinding.frameView.setZxingConfig(mConfig);
QrScanViewModel.Factory factory = new QrScanViewModel.Factory(mActivity.getApplication(), isSetupVault); QrScanViewModel.Factory factory = new QrScanViewModel.Factory(mActivity.getApplication(), isSetupVault);
viewModel = ViewModelProviders.of(this, factory).get(QrScanViewModel.class); viewModel = ViewModelProviders.of(this, factory).get(QrScanViewModel.class);
if (!TextUtils.isEmpty(purpose)) {
mBinding.electrumScanHint.setVisibility(View.GONE);
}
} }
@ -175,10 +181,16 @@ public class QRCodeScanFragment extends BaseFragment<QrcodeScanFragmentBinding>
alert(getString(R.string.invalid_webauth_qrcode_hint)); alert(getString(R.string.invalid_webauth_qrcode_hint));
} else if ("address".equals(purpose)) { } else if ("address".equals(purpose)) {
navigateUp(); navigateUp();
} else if (tryParseElecturmTx(res) != null) {
handleElectrumTx(res);
} else { } else {
alert(getString(R.string.unsupported_qrcode)); try {
if (tryParseElecturmTx(res) != null) {
handleElectrumTx(res);
} else {
alert(getString(R.string.unsupported_qrcode));
}
} catch (XpubNotMatchException e) {
alert(getString(R.string.master_pubkey_not_match));
}
} }
} }
@ -189,16 +201,27 @@ public class QRCodeScanFragment extends BaseFragment<QrcodeScanFragmentBinding>
navigate(R.id.action_to_ElectrumTxConfirmFragment, bundle); navigate(R.id.action_to_ElectrumTxConfirmFragment, bundle);
} }
private ElectrumTx tryParseElecturmTx(String res) { private ElectrumTx tryParseElecturmTx(String res) throws XpubNotMatchException {
try { try {
byte[] data = Base43.decode(res); byte[] data = Base43.decode(res);
return ElectrumTx.parse(data); ElectrumTx tx = ElectrumTx.parse(data);
} catch (Exception e) { if (!checkElectrumExpub(tx)) {
throw new XpubNotMatchException("xpub not match");
}
return tx;
} catch (ElectrumTx.SerializationException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
private boolean checkElectrumExpub(ElectrumTx tx) {
String xpub = ViewModelProviders.of(mActivity).get(ElectrumViewModel.class).getXpub();
return tx.getInputs()
.stream()
.allMatch(input -> xpub.equals(input.pubKey.xpub));
}
@Override @Override
public void handleDecode(ScannedData[] res) { public void handleDecode(ScannedData[] res) {
try { try {

60
app/src/main/java/com/cobo/cold/ui/fragment/main/TransactionItem.java

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
*/
package com.cobo.cold.ui.fragment.main;
import com.cobo.coinlib.utils.Coins;
import java.text.NumberFormat;
public class TransactionItem {
final int id;
final String amount;
final String address;
public TransactionItem(int id, long amount, String address) {
this.id = id;
this.amount = formatSatoshi(amount);
this.address = address;
}
static String formatSatoshi(long satoshi) {
double value = satoshi / Math.pow(10, 8);
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(20);
return nf.format(value) + " " + Coins.BTC.coinCode();
}
public int getId() {
return id;
}
public String getAmount() {
return amount;
}
public String getAddress() {
return address;
}
public enum ItemType {
INPUT,
OUTPUT,
FROM,
TO,
}
}

91
app/src/main/java/com/cobo/cold/ui/fragment/main/TransactionItemAdapter.java

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
*/
package com.cobo.cold.ui.fragment.main;
import android.content.Context;
import android.view.View;
import com.cobo.cold.R;
import com.cobo.cold.databinding.TxDetailItemBinding;
import com.cobo.cold.ui.common.BaseBindingAdapter;
import java.util.Collections;
import java.util.List;
public class TransactionItemAdapter extends BaseBindingAdapter<TransactionItem, TxDetailItemBinding> {
private TransactionItem.ItemType type;
private List<String> changeAddress;
public TransactionItemAdapter(Context context, TransactionItem.ItemType type) {
this(context, type, Collections.emptyList());
}
public TransactionItemAdapter(Context context,
TransactionItem.ItemType type,
List<String> changeAddress) {
super(context);
this.type = type;
this.changeAddress = changeAddress;
}
@Override
protected int getLayoutResId(int viewType) {
return R.layout.tx_detail_item;
}
@Override
protected void onBindItem(TxDetailItemBinding binding, TransactionItem item) {
boolean isChange = changeAddress.contains(item.address);
if (getItemCount() == 1 && type == TransactionItem.ItemType.TO) {
binding.info.setText(item.address);
binding.label.setText(context.getString(R.string.tx_to));
} else {
binding.info.setText(item.amount + "\n" + item.address);
binding.label.setText(getLabel(item.id));
}
if (isChange && (type == TransactionItem.ItemType.TO
|| type == TransactionItem.ItemType.OUTPUT)) {
binding.change.setVisibility(View.VISIBLE);
} else {
binding.change.setVisibility(View.GONE);
}
}
private String getLabel(int index) {
int resId;
switch (type) {
case TO:
resId = R.string.receive_address;
break;
case FROM:
resId = R.string.send_address;
break;
case INPUT:
resId = R.string.input;
break;
case OUTPUT:
resId = R.string.output;
break;
default:
return "";
}
return context.getString(resId, index + 1);
}
}

99
app/src/main/java/com/cobo/cold/ui/fragment/main/TxConfirmFragment.java

@ -17,7 +17,6 @@
package com.cobo.cold.ui.fragment.main; package com.cobo.cold.ui.fragment.main;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.text.Spannable; import android.text.Spannable;
@ -36,12 +35,10 @@ import com.cobo.cold.R;
import com.cobo.cold.Utilities; import com.cobo.cold.Utilities;
import com.cobo.cold.config.FeatureFlags; import com.cobo.cold.config.FeatureFlags;
import com.cobo.cold.databinding.ProgressModalBinding; import com.cobo.cold.databinding.ProgressModalBinding;
import com.cobo.cold.databinding.ReceiveItemBinding;
import com.cobo.cold.databinding.TxConfirmFragmentBinding; import com.cobo.cold.databinding.TxConfirmFragmentBinding;
import com.cobo.cold.db.entity.TxEntity; import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.encryptioncore.utils.ByteFormatter; import com.cobo.cold.encryptioncore.utils.ByteFormatter;
import com.cobo.cold.ui.BindingAdapters; import com.cobo.cold.ui.BindingAdapters;
import com.cobo.cold.ui.common.BaseBindingAdapter;
import com.cobo.cold.ui.fragment.BaseFragment; import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.modal.ModalDialog; import com.cobo.cold.ui.modal.ModalDialog;
import com.cobo.cold.ui.modal.SigningDialog; import com.cobo.cold.ui.modal.SigningDialog;
@ -51,9 +48,9 @@ import com.cobo.cold.viewmodel.TxConfirmViewModel;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -222,9 +219,13 @@ public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
try { try {
JSONArray outputs = new JSONArray(to); JSONArray outputs = new JSONArray(to);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < outputs.length(); i++) {
JSONObject output = outputs.getJSONObject(i);
if (output.optBoolean("isChange")) {
continue;
}
items.add(new TransactionItem(i, items.add(new TransactionItem(i,
outputs.getJSONObject(i).getLong("value"), output.getLong("value"),
outputs.getJSONObject(i).getString("address") output.getString("address")
)); ));
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -309,92 +310,6 @@ public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
} }
public static class TransactionItem {
final int id;
final String amount;
final String address;
public TransactionItem(int id, long amount, String address) {
this.id = id;
this.amount = formatSatoshi(amount);
this.address = address;
}
static String formatSatoshi(long satoshi) {
double value = satoshi / Math.pow(10, 8);
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(20);
return nf.format(value) + " " + Coins.BTC.coinCode();
}
public int getId() {
return id;
}
public String getAmount() {
return amount;
}
public String getAddress() {
return address;
}
public enum ItemType {
INPUT,
OUTPUT,
FROM,
TO,
}
}
public static class TransactionItemAdapter extends BaseBindingAdapter<TransactionItem, ReceiveItemBinding> {
private TransactionItem.ItemType type;
public TransactionItemAdapter(Context context, TransactionItem.ItemType type) {
super(context);
this.type = type;
}
@Override
protected int getLayoutResId(int viewType) {
return R.layout.receive_item;
}
@Override
protected void onBindItem(ReceiveItemBinding binding, TransactionItem item) {
if (getItemCount() == 1 && type == TransactionItem.ItemType.TO) {
binding.info.setText(item.address);
binding.label.setText(context.getString(R.string.tx_to));
} else {
binding.info.setText(item.amount + "\n" + item.address);
binding.label.setText(getLabel(item.id));
}
}
private String getLabel(int index) {
int resId;
switch (type) {
case TO:
resId = R.string.receive_address;
break;
case FROM:
resId = R.string.send_address;
break;
case INPUT:
resId = R.string.input;
break;
case OUTPUT:
resId = R.string.output;
break;
default:
return "";
}
return context.getString(resId, index + 1);
}
}
} }

23
app/src/main/java/com/cobo/cold/ui/fragment/main/TxFragment.java

@ -38,6 +38,7 @@ import com.cobo.cold.viewmodel.CoinListViewModel;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -115,21 +116,25 @@ public class TxFragment extends BaseFragment<TxBinding> {
private void refreshReceiveList() { private void refreshReceiveList() {
String to = txEntity.getTo(); String to = txEntity.getTo();
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray outputs = new JSONArray(to); JSONArray outputs = new JSONArray(to);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < outputs.length(); i++) {
items.add(new TxConfirmFragment.TransactionItem(i, JSONObject output = outputs.getJSONObject(i);
outputs.getJSONObject(i).getLong("value"), if (output.optBoolean("isChange")) {
outputs.getJSONObject(i).getString("address") continue;
}
items.add(new TransactionItem(i,
output.getLong("value"),
output.getString("address")
)); ));
} }
} catch (JSONException e) { } catch (JSONException e) {
return; return;
} }
TxConfirmFragment.TransactionItemAdapter adapter = TransactionItemAdapter adapter =
new TxConfirmFragment.TransactionItemAdapter(mActivity, new TransactionItemAdapter(mActivity,
TxConfirmFragment.TransactionItem.ItemType.TO); TransactionItem.ItemType.TO);
adapter.setItems(items); adapter.setItems(items);
mBinding.txDetail.toList.setVisibility(View.VISIBLE); mBinding.txDetail.toList.setVisibility(View.VISIBLE);
mBinding.txDetail.toInfo.setVisibility(View.GONE); mBinding.txDetail.toInfo.setVisibility(View.GONE);
@ -139,11 +144,11 @@ public class TxFragment extends BaseFragment<TxBinding> {
private void refreshFromList() { private void refreshFromList() {
String from = txEntity.getFrom(); String from = txEntity.getFrom();
mBinding.txDetail.from.setText(from); mBinding.txDetail.from.setText(from);
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray inputs = new JSONArray(from); JSONArray inputs = new JSONArray(from);
for (int i = 0; i < inputs.length(); i++) { for (int i = 0; i < inputs.length(); i++) {
items.add(new TxConfirmFragment.TransactionItem(i, items.add(new TransactionItem(i,
inputs.getJSONObject(i).getLong("value"), inputs.getJSONObject(i).getLong("value"),
inputs.getJSONObject(i).getString("address") inputs.getJSONObject(i).getString("address")
)); ));

12
app/src/main/java/com/cobo/cold/ui/fragment/main/TxListFragment.java

@ -40,6 +40,7 @@ import com.cobo.cold.viewmodel.CoinListViewModel;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -53,6 +54,7 @@ public class TxListFragment extends BaseFragment<TxListBinding> {
private TxAdapter adapter; private TxAdapter adapter;
private TxCallback txCallback; private TxCallback txCallback;
private String query; private String query;
private Comparator<TxEntity> txEntityComparator;
static Fragment newInstance(@NonNull String coinId, @NonNull String coinCode) { static Fragment newInstance(@NonNull String coinId, @NonNull String coinCode) {
@ -90,8 +92,18 @@ public class TxListFragment extends BaseFragment<TxListBinding> {
viewModel.loadTxs(data.getString(KEY_COIN_ID)) viewModel.loadTxs(data.getString(KEY_COIN_ID))
.observe(this, txEntities -> { .observe(this, txEntities -> {
txEntityComparator = (o1, o2) -> {
if (o1.getSignId().equals(o2.getSignId())) {
return (int) (o2.getTimeStamp() - o1.getTimeStamp());
} else if (ELECTRUM_SIGN_ID.equals(o1.getSignId())) {
return -1;
} else {
return 1;
}
};
txEntities = txEntities.stream() txEntities = txEntities.stream()
.filter(this::shouldShow) .filter(this::shouldShow)
.sorted(txEntityComparator)
.collect(Collectors.toList()); .collect(Collectors.toList());
adapter.setItems(txEntities); adapter.setItems(txEntities);
}); });

17
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/Callback.java

@ -1,3 +1,20 @@
/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
*/
package com.cobo.cold.ui.fragment.main.electrum; package com.cobo.cold.ui.fragment.main.electrum;
public interface Callback { public interface Callback {

11
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumBroadcastTxFragment.java

@ -22,6 +22,7 @@ import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
@ -65,16 +66,16 @@ public class ElectrumBroadcastTxFragment extends BaseFragment<BroadcastElectrumT
}); });
mBinding.hint.setOnClickListener(v -> { mBinding.hint.setOnClickListener(v -> {
if (txEntity != null) { if (txEntity != null) {
showExportTxnDialog(mActivity, txEntity.getTxId(), txEntity.getSignedHex()); showExportTxnDialog(mActivity, txEntity.getTxId(), txEntity.getSignedHex(),null);
} }
}); });
mBinding.info.setOnClickListener(v -> showElectrumInfo()); mBinding.info.setOnClickListener(v -> showElectrumInfo(mActivity));
} }
private void showElectrumInfo() { static void showElectrumInfo(AppCompatActivity activity) {
ModalDialog modalDialog = ModalDialog.newInstance(); ModalDialog modalDialog = ModalDialog.newInstance();
CommonModalBinding binding = DataBindingUtil.inflate( CommonModalBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(mActivity), R.layout.common_modal, LayoutInflater.from(activity), R.layout.common_modal,
null, false); null, false);
binding.title.setText(R.string.electrum_broadcast_guide); binding.title.setText(R.string.electrum_broadcast_guide);
binding.subTitle.setText(R.string.electrum_broadcast_action_guide); binding.subTitle.setText(R.string.electrum_broadcast_action_guide);
@ -83,7 +84,7 @@ public class ElectrumBroadcastTxFragment extends BaseFragment<BroadcastElectrumT
binding.confirm.setText(R.string.know); binding.confirm.setText(R.string.know);
binding.confirm.setOnClickListener(vv -> modalDialog.dismiss()); binding.confirm.setOnClickListener(vv -> modalDialog.dismiss());
modalDialog.setBinding(binding); modalDialog.setBinding(binding);
modalDialog.show(mActivity.getSupportFragmentManager(), ""); modalDialog.show(activity.getSupportFragmentManager(), "");
} }
@Override @Override

11
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumExportFragment.java

@ -30,6 +30,7 @@ import com.cobo.cold.R;
import com.cobo.cold.databinding.CommonModalBinding; import com.cobo.cold.databinding.CommonModalBinding;
import com.cobo.cold.databinding.ElectrumExportBinding; import com.cobo.cold.databinding.ElectrumExportBinding;
import com.cobo.cold.databinding.ExportSdcardModalBinding; import com.cobo.cold.databinding.ExportSdcardModalBinding;
import com.cobo.cold.ui.MainActivity;
import com.cobo.cold.ui.fragment.BaseFragment; import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.modal.ModalDialog; import com.cobo.cold.ui.modal.ModalDialog;
import com.cobo.cold.update.utils.Storage; import com.cobo.cold.update.utils.Storage;
@ -52,8 +53,8 @@ public class ElectrumExportFragment extends BaseFragment<ElectrumExportBinding>
@Override @Override
protected void init(View view) { protected void init(View view) {
mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp()); mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp());
ElectrumViewModel viewModel = ViewModelProviders.of(this).get(ElectrumViewModel.class); ElectrumViewModel viewModel = ViewModelProviders.of(mActivity).get(ElectrumViewModel.class);
viewModel.getExPub().observe(this, s -> { viewModel.getMasterPublicKey().observe(this, s -> {
if (!TextUtils.isEmpty(s)) { if (!TextUtils.isEmpty(s)) {
exPub = s; exPub = s;
mBinding.qrcode.setData(s); mBinding.qrcode.setData(s);
@ -61,6 +62,10 @@ public class ElectrumExportFragment extends BaseFragment<ElectrumExportBinding>
} }
}); });
mBinding.info.setOnClickListener(v -> showElectrumInfo()); mBinding.info.setOnClickListener(v -> showElectrumInfo());
mBinding.done.setOnClickListener(v->{
MainActivity activity = (MainActivity) mActivity;
activity.getNavController().popBackStack(R.id.assetFragment,false);
});
mBinding.exportToSdcard.setOnClickListener(v -> { mBinding.exportToSdcard.setOnClickListener(v -> {
Storage storage = Storage.createByEnvironment(mActivity); Storage storage = Storage.createByEnvironment(mActivity);
if (storage == null || storage.getExternalDir() == null) { if (storage == null || storage.getExternalDir() == null) {
@ -76,7 +81,7 @@ public class ElectrumExportFragment extends BaseFragment<ElectrumExportBinding>
binding.confirm.setOnClickListener(vv -> { binding.confirm.setOnClickListener(vv -> {
modalDialog.dismiss(); modalDialog.dismiss();
if (writeToSdcard(storage, exPub, EXTEND_PUB_FILE_NAME)) { if (writeToSdcard(storage, exPub, EXTEND_PUB_FILE_NAME)) {
exportSuccess(mActivity); exportSuccess(mActivity, null);
} }
}); });
modalDialog.setBinding(binding); modalDialog.setBinding(binding);

58
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxConfirmFragment.java

@ -41,14 +41,17 @@ import com.cobo.cold.databinding.ProgressModalBinding;
import com.cobo.cold.db.entity.TxEntity; import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.encryptioncore.utils.ByteFormatter; import com.cobo.cold.encryptioncore.utils.ByteFormatter;
import com.cobo.cold.ui.fragment.BaseFragment; import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.fragment.main.TxConfirmFragment; import com.cobo.cold.ui.fragment.main.TransactionItem;
import com.cobo.cold.ui.fragment.main.TransactionItemAdapter;
import com.cobo.cold.ui.modal.ModalDialog; import com.cobo.cold.ui.modal.ModalDialog;
import com.cobo.cold.ui.modal.ProgressModalDialog; import com.cobo.cold.ui.modal.ProgressModalDialog;
import com.cobo.cold.ui.modal.SigningDialog; import com.cobo.cold.ui.modal.SigningDialog;
import com.cobo.cold.ui.views.AuthenticateModal; import com.cobo.cold.ui.views.AuthenticateModal;
import com.cobo.cold.update.utils.Storage; import com.cobo.cold.update.utils.Storage;
import com.cobo.cold.util.KeyStoreUtil; import com.cobo.cold.util.KeyStoreUtil;
import com.cobo.cold.viewmodel.ElectrumViewModel;
import com.cobo.cold.viewmodel.TxConfirmViewModel; import com.cobo.cold.viewmodel.TxConfirmViewModel;
import com.cobo.cold.viewmodel.XpubNotMatchException;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -80,8 +83,10 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
private TxEntity txEntity; private TxEntity txEntity;
private ModalDialog addingAddressDialog; private ModalDialog addingAddressDialog;
private String txnData; private String txnData;
private List<String> changeAddress = new ArrayList<>();
public static void showExportTxnDialog(AppCompatActivity activity, String txId, String hex) { public static void showExportTxnDialog(AppCompatActivity activity, String txId, String hex,
Runnable onExportSuccess) {
ModalDialog modalDialog = ModalDialog.newInstance(); ModalDialog modalDialog = ModalDialog.newInstance();
ExportSdcardModalBinding binding = DataBindingUtil.inflate(LayoutInflater.from(activity), ExportSdcardModalBinding binding = DataBindingUtil.inflate(LayoutInflater.from(activity),
R.layout.export_sdcard_modal, null, false); R.layout.export_sdcard_modal, null, false);
@ -96,7 +101,7 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
Storage storage = Storage.createByEnvironment(activity); Storage storage = Storage.createByEnvironment(activity);
boolean result = writeToSdcard(storage, generateElectrumTxn(hex), fileName); boolean result = writeToSdcard(storage, generateElectrumTxn(hex), fileName);
if (result) { if (result) {
exportSuccess(activity); exportSuccess(activity, onExportSuccess);
} }
} else { } else {
showNoSdcardModal(activity); showNoSdcardModal(activity);
@ -130,15 +135,17 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
Bundle bundle = Objects.requireNonNull(getArguments()); Bundle bundle = Objects.requireNonNull(getArguments());
mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp()); mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp());
mBinding.txDetail.txIdInfo.setVisibility(View.GONE); mBinding.txDetail.txIdInfo.setVisibility(View.GONE);
mBinding.txDetail.qrcodeLayout.qrcode.setVisibility(View.GONE);
mBinding.txDetail.export.setVisibility(View.GONE); mBinding.txDetail.export.setVisibility(View.GONE);
mBinding.txDetail.qr.setVisibility(View.GONE);
txnData = bundle.getString("txn"); txnData = bundle.getString("txn");
viewModel = ViewModelProviders.of(this).get(TxConfirmViewModel.class); viewModel = ViewModelProviders.of(this).get(TxConfirmViewModel.class);
mBinding.setViewModel(viewModel); mBinding.setViewModel(viewModel);
subscribeTxEntityState(); subscribeTxEntityState();
mBinding.sign.setOnClickListener(v -> handleSign()); mBinding.sign.setOnClickListener(v -> handleSign());
ViewModelProviders.of(mActivity)
.get(ElectrumViewModel.class)
.getChangeAddress()
.observe(this, address -> this.changeAddress = address);
} }
private void handleSign() { private void handleSign() {
@ -213,9 +220,14 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
if (ex != null) { if (ex != null) {
ex.printStackTrace(); ex.printStackTrace();
dialog.dismiss(); dialog.dismiss();
String errorMessage = getString(R.string.incorrect_tx_data);
if (ex instanceof XpubNotMatchException) {
errorMessage = getString(R.string.master_pubkey_not_match);
}
ModalDialog.showCommonModal(mActivity, ModalDialog.showCommonModal(mActivity,
getString(R.string.electrum_decode_txn_fail), getString(R.string.electrum_decode_txn_fail),
getString(R.string.incorrect_tx_data), errorMessage,
getString(R.string.confirm), getString(R.string.confirm),
null); null);
navigateUp(); navigateUp();
@ -232,11 +244,11 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
private void refreshReceiveList() { private void refreshReceiveList() {
String to = txEntity.getTo(); String to = txEntity.getTo();
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray outputs = new JSONArray(to); JSONArray outputs = new JSONArray(to);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < outputs.length(); i++) {
items.add(new TxConfirmFragment.TransactionItem(i, items.add(new TransactionItem(i,
outputs.getJSONObject(i).getLong("value"), outputs.getJSONObject(i).getLong("value"),
outputs.getJSONObject(i).getString("address") outputs.getJSONObject(i).getString("address")
)); ));
@ -244,31 +256,32 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
} catch (JSONException e) { } catch (JSONException e) {
return; return;
} }
TxConfirmFragment.TransactionItemAdapter adapter TransactionItemAdapter adapter
= new TxConfirmFragment.TransactionItemAdapter(mActivity, = new TransactionItemAdapter(mActivity,
TxConfirmFragment.TransactionItem.ItemType.OUTPUT); TransactionItem.ItemType.OUTPUT,
changeAddress);
adapter.setItems(items); adapter.setItems(items);
mBinding.txDetail.toList.setVisibility(View.VISIBLE); mBinding.txDetail.toList.setVisibility(View.VISIBLE);
mBinding.txDetail.toList.setAdapter(adapter); mBinding.txDetail.toList.setAdapter(adapter);
} }
private void refreshFromList() { private void refreshFromList() {
String to = txEntity.getFrom(); String from = txEntity.getFrom();
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray outputs = new JSONArray(to); JSONArray inputs = new JSONArray(from);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < inputs.length(); i++) {
JSONObject out = outputs.getJSONObject(i); JSONObject out = inputs.getJSONObject(i);
items.add(new TxConfirmFragment.TransactionItem(i, items.add(new TransactionItem(i,
out.getLong("value"), out.getLong("value"),
out.getString("address"))); out.getString("address")));
} }
} catch (JSONException e) { } catch (JSONException e) {
return; return;
} }
TxConfirmFragment.TransactionItemAdapter adapter TransactionItemAdapter adapter
= new TxConfirmFragment.TransactionItemAdapter(mActivity, = new TransactionItemAdapter(mActivity,
TxConfirmFragment.TransactionItem.ItemType.INPUT); TransactionItem.ItemType.INPUT);
adapter.setItems(items); adapter.setItems(items);
mBinding.txDetail.fromList.setVisibility(View.VISIBLE); mBinding.txDetail.fromList.setVisibility(View.VISIBLE);
mBinding.txDetail.fromList.setAdapter(adapter); mBinding.txDetail.fromList.setAdapter(adapter);
@ -321,7 +334,8 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
data.putString(KEY_TXID, txId); data.putString(KEY_TXID, txId);
navigate(R.id.action_to_broadcastElectrumTxFragment, data); navigate(R.id.action_to_broadcastElectrumTxFragment, data);
} else { } else {
showExportTxnDialog(mActivity, viewModel.getTxId(), viewModel.getTxHex()); showExportTxnDialog(mActivity, viewModel.getTxId(),
viewModel.getTxHex(), this::navigateUp);
} }
} }

39
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxFragment.java

@ -31,8 +31,10 @@ import com.cobo.cold.R;
import com.cobo.cold.databinding.ElectrumTxBinding; import com.cobo.cold.databinding.ElectrumTxBinding;
import com.cobo.cold.db.entity.TxEntity; import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.ui.fragment.BaseFragment; import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.fragment.main.TxConfirmFragment; import com.cobo.cold.ui.fragment.main.TransactionItem;
import com.cobo.cold.ui.fragment.main.TransactionItemAdapter;
import com.cobo.cold.viewmodel.CoinListViewModel; import com.cobo.cold.viewmodel.CoinListViewModel;
import com.cobo.cold.viewmodel.ElectrumViewModel;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -43,6 +45,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static com.cobo.cold.ui.fragment.main.electrum.ElectrumBroadcastTxFragment.showElectrumInfo;
import static com.cobo.cold.ui.fragment.main.electrum.ElectrumTxConfirmFragment.showExportTxnDialog; import static com.cobo.cold.ui.fragment.main.electrum.ElectrumTxConfirmFragment.showExportTxnDialog;
@ -50,6 +53,7 @@ public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
public static final String KEY_TX_ID = "txid"; public static final String KEY_TX_ID = "txid";
private TxEntity txEntity; private TxEntity txEntity;
private List<String> changeAddress = new ArrayList<>();
@Override @Override
protected int setView() { protected int setView() {
@ -67,35 +71,44 @@ public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
String signTx = getSignTxString(txEntity); String signTx = getSignTxString(txEntity);
if (signTx.length() <= 1000) { if (signTx.length() <= 1000) {
new Handler().postDelayed(() -> mBinding.txDetail.qrcodeLayout.qrcode.setData(signTx), 500); new Handler().postDelayed(() -> mBinding.txDetail.qrcodeLayout.qrcode.setData(signTx), 500);
mBinding.txDetail.export.setVisibility(View.GONE);
mBinding.txDetail.exportToSdcardHint.setOnClickListener(v ->
showExportTxnDialog(mActivity, txEntity.getTxId(), txEntity.getSignedHex(), null));
mBinding.txDetail.info.setOnClickListener(v -> showElectrumInfo(mActivity));
} else { } else {
mBinding.txDetail.qrcodeLayout.qrcode.setVisibility(View.GONE); mBinding.txDetail.qr.setVisibility(View.GONE);
} }
refreshAmount(); refreshAmount();
refreshFromList(); refreshFromList();
refreshReceiveList(); refreshReceiveList();
mBinding.txDetail.exportToSdcard.setOnClickListener(v -> { mBinding.txDetail.exportToSdcard.setOnClickListener(v -> {
showExportTxnDialog(mActivity, txEntity.getTxId(), txEntity.getSignedHex()); showExportTxnDialog(mActivity, txEntity.getTxId(), txEntity.getSignedHex(),null);
}); });
}); });
ViewModelProviders.of(mActivity)
.get(ElectrumViewModel.class)
.getChangeAddress()
.observe(this, address -> this.changeAddress = address);
} }
private void refreshFromList() { private void refreshFromList() {
String from = txEntity.getFrom(); String from = txEntity.getFrom();
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray outputs = new JSONArray(from); JSONArray outputs = new JSONArray(from);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < outputs.length(); i++) {
JSONObject out = outputs.getJSONObject(i); JSONObject out = outputs.getJSONObject(i);
items.add(new TxConfirmFragment.TransactionItem(i, items.add(new TransactionItem(i,
out.getLong("value"), out.getString("address"))); out.getLong("value"), out.getString("address")));
} }
} catch (JSONException e) { } catch (JSONException e) {
return; return;
} }
TxConfirmFragment.TransactionItemAdapter adapter TransactionItemAdapter adapter
= new TxConfirmFragment.TransactionItemAdapter(mActivity, = new TransactionItemAdapter(mActivity,
TxConfirmFragment.TransactionItem.ItemType.INPUT); TransactionItem.ItemType.INPUT, changeAddress);
adapter.setItems(items); adapter.setItems(items);
mBinding.txDetail.fromList.setAdapter(adapter); mBinding.txDetail.fromList.setAdapter(adapter);
} }
@ -109,11 +122,11 @@ public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
private void refreshReceiveList() { private void refreshReceiveList() {
String to = txEntity.getTo(); String to = txEntity.getTo();
List<TxConfirmFragment.TransactionItem> items = new ArrayList<>(); List<TransactionItem> items = new ArrayList<>();
try { try {
JSONArray outputs = new JSONArray(to); JSONArray outputs = new JSONArray(to);
for (int i = 0; i < outputs.length(); i++) { for (int i = 0; i < outputs.length(); i++) {
items.add(new TxConfirmFragment.TransactionItem(i, items.add(new TransactionItem(i,
outputs.getJSONObject(i).getLong("value"), outputs.getJSONObject(i).getLong("value"),
outputs.getJSONObject(i).getString("address") outputs.getJSONObject(i).getString("address")
)); ));
@ -121,8 +134,10 @@ public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
} catch (JSONException e) { } catch (JSONException e) {
return; return;
} }
TxConfirmFragment.TransactionItemAdapter adapter = TransactionItemAdapter adapter =
new TxConfirmFragment.TransactionItemAdapter(mActivity, TxConfirmFragment.TransactionItem.ItemType.OUTPUT); new TransactionItemAdapter(mActivity,
TransactionItem.ItemType.OUTPUT,
changeAddress);
adapter.setItems(items); adapter.setItems(items);
mBinding.txDetail.toList.setAdapter(adapter); mBinding.txDetail.toList.setAdapter(adapter);
} }

4
app/src/main/java/com/cobo/cold/ui/views/DrawerAdapter.java

@ -63,8 +63,8 @@ public class DrawerAdapter extends RecyclerView.Adapter<DrawerAdapter.Holder> {
boolean supportFingerprint = FingerprintKit.isHardwareDetected(view.getContext()); boolean supportFingerprint = FingerprintKit.isHardwareDetected(view.getContext());
view.setText(item.titleRes); view.setText(item.titleRes);
if (item.index == R.id.drawer_settings) { if (item.index == R.id.drawer_settings) {
if (!Utilities.hasUserClickFingerprint(view.getContext()) if (!Utilities.hasUserClickPatternLock(view.getContext())
|| (supportFingerprint && !Utilities.hasUserClickPatternLock(view.getContext()))) { || (supportFingerprint && !Utilities.hasUserClickFingerprint(view.getContext()))) {
BadgeFactory.create(view.getContext()) BadgeFactory.create(view.getContext())
.setWidthAndHeight(10, 10) .setWidthAndHeight(10, 10)
.setBadgeBackground(Color.RED) .setBadgeBackground(Color.RED)

90
app/src/main/java/com/cobo/cold/viewmodel/ElectrumViewModel.java

@ -20,7 +20,7 @@ package com.cobo.cold.viewmodel;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -32,6 +32,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cobo.coinlib.Util; import com.cobo.coinlib.Util;
import com.cobo.coinlib.coins.AbsDeriver;
import com.cobo.coinlib.coins.BTC.Btc;
import com.cobo.coinlib.coins.BTC.Electrum.ElectrumTx; import com.cobo.coinlib.coins.BTC.Electrum.ElectrumTx;
import com.cobo.coinlib.coins.BTC.Electrum.TransactionInput; import com.cobo.coinlib.coins.BTC.Electrum.TransactionInput;
import com.cobo.coinlib.coins.BTC.Electrum.TransactionOutput; import com.cobo.coinlib.coins.BTC.Electrum.TransactionOutput;
@ -45,17 +47,14 @@ import com.cobo.cold.R;
import com.cobo.cold.databinding.CommonModalBinding; import com.cobo.cold.databinding.CommonModalBinding;
import com.cobo.cold.db.entity.AccountEntity; import com.cobo.cold.db.entity.AccountEntity;
import com.cobo.cold.db.entity.CoinEntity; import com.cobo.cold.db.entity.CoinEntity;
import com.cobo.cold.protobuf.TransactionProtoc;
import com.cobo.cold.ui.modal.ExportToSdcardDialog; import com.cobo.cold.ui.modal.ExportToSdcardDialog;
import com.cobo.cold.ui.modal.ModalDialog; import com.cobo.cold.ui.modal.ModalDialog;
import com.cobo.cold.update.utils.FileUtils; import com.cobo.cold.update.utils.FileUtils;
import com.cobo.cold.update.utils.Storage; import com.cobo.cold.update.utils.Storage;
import com.googlecode.protobuf.format.JsonFormat;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.spongycastle.util.encoders.Hex;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -67,16 +66,38 @@ import java.util.regex.Pattern;
public class ElectrumViewModel extends AndroidViewModel { public class ElectrumViewModel extends AndroidViewModel {
public static final String ELECTRUM_SIGN_ID = "electrum_sign_id"; public static final String ELECTRUM_SIGN_ID = "electrum_sign_id";
private static final int DEFAULT_CHANGE_ADDRESS_NUM = 100;
private static Pattern signedTxnPattern = Pattern.compile("[0-9a-f]{5}-signed.txn$"); private static Pattern signedTxnPattern = Pattern.compile("[0-9a-f]{5}-signed.txn$");
private final DataRepository mRepo; private final DataRepository mRepo;
private MutableLiveData<String> exPub = new MutableLiveData<>(); private MutableLiveData<String> exPub = new MutableLiveData<>();
private Storage storage; private Storage storage;
private MutableLiveData<List<String>> changeAddress = new MutableLiveData<>();
private String xpub;
public ElectrumViewModel(@NonNull Application application) { public ElectrumViewModel(@NonNull Application application) {
super(application); super(application);
mRepo = MainApplication.getApplication().getRepository(); mRepo = MainApplication.getApplication().getRepository();
storage = Storage.createByEnvironment(application); storage = Storage.createByEnvironment(application);
deriveChangeAddress();
}
private void deriveChangeAddress() {
AppExecutors.getInstance().networkIO().execute(()->{
if (TextUtils.isEmpty(xpub)) {
xpub = new ExpubInfo().invoke().expub;
}
List<String> changes = new ArrayList<>();
AbsDeriver btcDeriver = new Btc.Deriver();
for (int i = 0; i < DEFAULT_CHANGE_ADDRESS_NUM; i++) {
changes.add(btcDeriver.derive(xpub,1, i));
}
changeAddress.postValue(changes);
});
}
public LiveData<List<String>> getChangeAddress() {
return changeAddress;
} }
public static boolean hasSdcard(Context context) { public static boolean hasSdcard(Context context) {
@ -103,27 +124,18 @@ public class ElectrumViewModel extends AndroidViewModel {
modalDialog.show(activity.getSupportFragmentManager(), ""); modalDialog.show(activity.getSupportFragmentManager(), "");
} }
public static void exportSuccess(AppCompatActivity activity) { public static void exportSuccess(AppCompatActivity activity, Runnable runnable) {
ExportToSdcardDialog dialog = new ExportToSdcardDialog(); ExportToSdcardDialog dialog = new ExportToSdcardDialog();
dialog.show(activity.getSupportFragmentManager(), ""); dialog.show(activity.getSupportFragmentManager(), "");
new Handler().postDelayed(dialog::dismiss, 1000); new Handler().postDelayed(() -> {
} dialog.dismiss();
if (runnable != null) {
static JSONObject parseElectrumTxHex(String hex) throws JSONException, ElectrumTx.SerializationException { runnable.run();
ElectrumTx tx = ElectrumTx.parse(Hex.decode(hex)); }
JSONObject btcTx = adapt(tx); }, 1000);
TransactionProtoc.SignTransaction.Builder builder = TransactionProtoc.SignTransaction.newBuilder();
builder.setCoinCode(Coins.BTC.coinCode())
.setSignId(ELECTRUM_SIGN_ID)
.setTimestamp(System.currentTimeMillis())
.setDecimal(8);
String signTransaction = new JsonFormat().printToString(builder.build());
JSONObject signTx = new JSONObject(signTransaction);
signTx.put("btcTx", btcTx);
return signTx;
} }
private static JSONObject adapt(ElectrumTx tx) throws JSONException { public static JSONObject adapt(ElectrumTx tx) throws JSONException {
JSONObject object = new JSONObject(); JSONObject object = new JSONObject();
JSONArray inputs = new JSONArray(); JSONArray inputs = new JSONArray();
JSONArray outputs = new JSONArray(); JSONArray outputs = new JSONArray();
@ -162,12 +174,16 @@ public class ElectrumViewModel extends AndroidViewModel {
} }
} }
public LiveData<String> getExPub() { public String getXpub() {
return xpub;
}
public LiveData<String> getMasterPublicKey() {
AppExecutors.getInstance().diskIO().execute(() -> { AppExecutors.getInstance().diskIO().execute(() -> {
CoinEntity btc = mRepo.loadCoinSync(Coins.coinIdFromCoinCode("BTC")); ExpubInfo expubInfo = new ExpubInfo().invoke();
AccountEntity accountEntity = mRepo.loadAccountsForCoin(btc).get(0); String hdPath = expubInfo.getHdPath();
String hdPath = accountEntity.getHdPath(); String expub = expubInfo.getExpub();
String expub = accountEntity.getExPub(); xpub = expub;
try { try {
Account account = Account.parseAccount(hdPath); Account account = Account.parseAccount(hdPath);
if (account.getParent().getParent().getValue() == 49 && expub.startsWith("xpub")) { if (account.getParent().getParent().getValue() == 49 && expub.startsWith("xpub")) {
@ -196,7 +212,6 @@ public class ElectrumViewModel extends AndroidViewModel {
File[] files = storage.getElectrumDir().listFiles(); File[] files = storage.getElectrumDir().listFiles();
if (files != null) { if (files != null) {
for (File f : files) { for (File f : files) {
Log.w("kkk", f.getName() + " " + f.length());
if (f.getName().endsWith(".txn") if (f.getName().endsWith(".txn")
&& !isSignedTxn(f.getName())) { && !isSignedTxn(f.getName())) {
fileList.add(f.getName()); fileList.add(f.getName());
@ -224,4 +239,25 @@ public class ElectrumViewModel extends AndroidViewModel {
}); });
return txnHex; return txnHex;
} }
private class ExpubInfo {
private String hdPath;
private String expub;
public String getHdPath() {
return hdPath;
}
public String getExpub() {
return expub;
}
public ExpubInfo invoke() {
CoinEntity btc = mRepo.loadCoinSync(Coins.coinIdFromCoinCode("BTC"));
AccountEntity accountEntity = mRepo.loadAccountsForCoin(btc).get(0);
hdPath = accountEntity.getHdPath();
expub = accountEntity.getExPub();
return this;
}
}
} }

53
app/src/main/java/com/cobo/cold/viewmodel/TxConfirmViewModel.java

@ -53,7 +53,9 @@ import com.cobo.cold.db.entity.AddressEntity;
import com.cobo.cold.db.entity.CoinEntity; import com.cobo.cold.db.entity.CoinEntity;
import com.cobo.cold.db.entity.TxEntity; import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.encryption.ChipSigner; import com.cobo.cold.encryption.ChipSigner;
import com.cobo.cold.protobuf.TransactionProtoc;
import com.cobo.cold.ui.views.AuthenticateModal; import com.cobo.cold.ui.views.AuthenticateModal;
import com.googlecode.protobuf.format.JsonFormat;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -63,6 +65,8 @@ import org.spongycastle.util.encoders.Hex;
import java.security.SignatureException; import java.security.SignatureException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -70,7 +74,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.cobo.cold.viewmodel.ElectrumViewModel.parseElectrumTxHex; import static com.cobo.coinlib.coins.BTC.Electrum.TxUtils.isMasterPublicKeyMatch;
import static com.cobo.cold.viewmodel.ElectrumViewModel.ELECTRUM_SIGN_ID;
import static com.cobo.cold.viewmodel.ElectrumViewModel.adapt;
public class TxConfirmViewModel extends AndroidViewModel { public class TxConfirmViewModel extends AndroidViewModel {
@ -133,7 +139,7 @@ public class TxConfirmViewModel extends AndroidViewModel {
nf.setMaximumFractionDigits(20); nf.setMaximumFractionDigits(20);
coinCode = Objects.requireNonNull(transaction).getCoinCode(); coinCode = Objects.requireNonNull(transaction).getCoinCode();
tx.setSignId(object.getString("signId")); tx.setSignId(object.getString("signId"));
tx.setTimeStamp(object.getLong("timestamp")); tx.setTimeStamp(object.optLong("timestamp"));
tx.setCoinCode(coinCode); tx.setCoinCode(coinCode);
tx.setCoinId(Coins.coinIdFromCoinCode(coinCode)); tx.setCoinId(Coins.coinIdFromCoinCode(coinCode));
tx.setFrom(getFromAddress()); tx.setFrom(getFromAddress());
@ -146,16 +152,50 @@ public class TxConfirmViewModel extends AndroidViewModel {
} }
public void parseTxnData(String txnData) { public void parseTxnData(String txnData) {
AppExecutors.getInstance().networkIO().execute(()-> { AppExecutors.getInstance().networkIO().execute(() -> {
try { try {
JSONObject signTx = parseElectrumTxHex(txnData); String xpub = mRepository.loadCoinEntityByCoinCode(Coins.BTC.coinCode()).getExPub();
ElectrumTx tx = ElectrumTx.parse(Hex.decode(txnData));
if (!isMasterPublicKeyMatch(xpub, tx)) {
throw new XpubNotMatchException("xpub not match");
}
JSONObject signTx = parseElectrumTxHex(tx);
parseTxData(signTx.toString()); parseTxData(signTx.toString());
} catch (ElectrumTx.SerializationException | JSONException e) { } catch (ElectrumTx.SerializationException | JSONException e) {
e.printStackTrace(); e.printStackTrace();
parseTxException.postValue(new InvalidTransactionException("invalid transaction"));
} catch (XpubNotMatchException e) {
e.printStackTrace();
parseTxException.postValue(new XpubNotMatchException("invalid transaction"));
} }
}); });
} }
private JSONObject parseElectrumTxHex(ElectrumTx tx) throws JSONException {
JSONObject btcTx = adapt(tx);
TransactionProtoc.SignTransaction.Builder builder = TransactionProtoc.SignTransaction.newBuilder();
builder.setCoinCode(Coins.BTC.coinCode())
.setSignId(ELECTRUM_SIGN_ID)
.setTimestamp(generateElectrumTimestamp())
.setDecimal(8);
String signTransaction = new JsonFormat().printToString(builder.build());
JSONObject signTx = new JSONObject(signTransaction);
signTx.put("btcTx", btcTx);
return signTx;
}
private long generateElectrumTimestamp() {
List<TxEntity> txEntityList = mRepository.loadElectrumTxsSync(Coins.BTC.coinId());
if (txEntityList == null || txEntityList.isEmpty()) {
return 0;
}
return txEntityList.stream()
.max(Comparator.comparing(TxEntity::getTimeStamp))
.get()
.getTimeStamp() + 1;
}
private boolean checkChangeAddress(AbsTx utxoTx) { private boolean checkChangeAddress(AbsTx utxoTx) {
UtxoTx.ChangeAddressInfo changeAddressInfo = ((UtxoTx) utxoTx).getChangeAddressInfo(); UtxoTx.ChangeAddressInfo changeAddressInfo = ((UtxoTx) utxoTx).getChangeAddressInfo();
if (changeAddressInfo == null) { if (changeAddressInfo == null) {
@ -182,7 +222,10 @@ public class TxConfirmViewModel extends AndroidViewModel {
String to = transaction.getTo(); String to = transaction.getTo();
if (transaction instanceof UtxoTx) { if (transaction instanceof UtxoTx) {
to = ((UtxoTx) transaction).getOutputs().toString(); JSONArray outputs = ((UtxoTx) transaction).getOutputs();
if (outputs != null) {
return outputs.toString();
}
} }
return to; return to;

24
app/src/main/java/com/cobo/cold/viewmodel/XpubNotMatchException.java

@ -0,0 +1,24 @@
/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
*/
package com.cobo.cold.viewmodel;
public class XpubNotMatchException extends Exception {
public XpubNotMatchException(String message) {
super(message);
}
}

22
app/src/main/res/drawable/change_bg.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2020 Cobo
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffff00" />
<corners android:radius="5dp"/>
</shape>

173
app/src/main/res/layout/electrum_export.xml

@ -52,88 +52,109 @@
<include layout="@layout/divider" /> <include layout="@layout/divider" />
<LinearLayout <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_marginTop="40dp"
android:gravity="center"> <LinearLayout
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/use_electrum_scan_xpub"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/info"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5dp"
android:src="@drawable/info"/>
</LinearLayout>
<com.cobo.cold.ui.views.qrcode.QrCodeView
android:id="@+id/qrcode"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_marginHorizontal="34dp"
android:layout_marginTop="24dp"
android:background="@color/white"
android:padding="5dp"
android:keepScreenOn="true">
<ImageView
android:id="@+id/img"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="visible" android:gravity="center_horizontal"
tools:ignore="ContentDescription" /> android:orientation="vertical">
<ProgressBar <LinearLayout
android:id="@+id/progress" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="20dp"
android:layout_gravity="center" android:gravity="center">
android:visibility="visible" />
</com.cobo.cold.ui.views.qrcode.QrCodeView>
<com.cobo.cold.ui.views.SpanedTextView <TextView
android:id="@+id/export_to_sdcard" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:gravity="center"
android:layout_marginHorizontal="16dp" android:text="@string/use_electrum_scan_xpub"
android:layout_marginTop="26dp" android:textColor="@color/white"
android:gravity="center" android:textSize="15sp"
android:text="@string/electrum_qrcode_hint" android:textStyle="bold" />
android:textColor="@color/white"
android:textSize="12sp" />
<ImageView
android:id="@+id/info"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5dp"
android:src="@drawable/info" />
</LinearLayout>
<androidx.legacy.widget.Space <com.cobo.cold.ui.views.qrcode.QrCodeView
android:layout_width="match_parent" android:id="@+id/qrcode"
android:layout_height="0dp" android:layout_width="160dp"
android:layout_weight="1"/> android:layout_height="160dp"
<TextView android:layout_marginHorizontal="34dp"
android:layout_width="wrap_content" android:layout_marginTop="20dp"
android:layout_height="wrap_content" android:background="@color/white"
android:layout_marginTop="40dp" android:keepScreenOn="true"
android:gravity="center" android:padding="5dp">
android:text="@string/master_xpub"
android:textColor="@color/white" <ImageView
android:textSize="15sp" android:id="@+id/img"
android:textStyle="bold" /> android:layout_width="match_parent"
android:layout_height="match_parent"
<TextView android:visibility="visible"
android:id="@+id/expub" tools:ignore="ContentDescription" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" <ProgressBar
android:layout_marginTop="8dp" android:id="@+id/progress"
android:gravity="center" android:layout_width="wrap_content"
tools:text="ypub6D3i46Y43SFfjEBYheBK3btYMRm9Cfb8Tt4M5Bv16tArNBw5ATNyJWjdcMyLxoCdHWTvm3ak7j2BWacq5Lw478aYUeARoYm4dvaQgJBAGsb" android:layout_height="wrap_content"
android:textColor="@color/white40" android:layout_gravity="center"
android:layout_marginHorizontal="16dp" android:visibility="visible" />
android:layout_marginBottom="48dp" </com.cobo.cold.ui.views.qrcode.QrCodeView>
android:textSize="15sp" />
<com.cobo.cold.ui.views.SpanedTextView
android:id="@+id/export_to_sdcard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/electrum_qrcode_hint"
android:textColor="@color/white"
android:textSize="12sp" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/master_xpub"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/expub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:gravity="center"
android:textColor="@color/white40"
android:textSize="15sp"
tools:text="ypub6D3i46Y43SFfjEBYheBK3btYMRm9Cfb8Tt4M5Bv16tArNBw5ATNyJWjdcMyLxoCdHWTvm3ak7j2BWacq5Lw478aYUeARoYm4dvaQgJBAGsb" />
<Button
android:id="@+id/done"
android:layout_width="match_parent"
android:layout_margin="16dp"
style="@style/AcceptButton"
android:text="@string/complete"/>
</LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>
</layout> </layout>

59
app/src/main/res/layout/electrum_tx_detail.xml

@ -182,7 +182,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/receive_item" tools:listitem="@layout/tx_detail_item"
tools:itemCount="1" tools:itemCount="1"
android:visibility="visible" /> android:visibility="visible" />
<RelativeLayout <RelativeLayout
@ -203,7 +203,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:itemCount="1" tools:itemCount="1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/receive_item" tools:listitem="@layout/tx_detail_item"
android:visibility="visible" /> android:visibility="visible" />
<include <include
@ -280,11 +280,58 @@
android:layout_height="1dp" android:layout_height="1dp"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>
<include
android:id="@+id/qrcode_layout" <LinearLayout
layout="@layout/qrcode" android:id="@+id/qr"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="match_parent"/> android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<View
android:layout_width="30dp"
android:layout_height="match_parent" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/use_electrum_to_broadcast"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/info"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5dp"
android:src="@drawable/info" />
</LinearLayout>
<include
android:id="@+id/qrcode_layout"
layout="@layout/qrcode"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<com.cobo.cold.ui.views.SpanedTextView
android:id="@+id/export_to_sdcard_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/electrum_qrcode_hint"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/export" android:id="@+id/export"

17
app/src/main/res/layout/electrum_txn.xml

@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2020 Cobo
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">

16
app/src/main/res/layout/qrcode_scan_fragment.xml

@ -75,14 +75,26 @@
android:text="@{progress}" /> android:text="@{progress}" />
<TextView <TextView
android:id="@+id/align_hint"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginBottom="145dp" android:layout_below="@id/toolbar"
android:layout_alignParentBottom="true" android:layout_marginTop="300dp"
android:textSize="12sp" android:textSize="12sp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:text="@string/scan_hint" /> android:text="@string/scan_hint" />
<TextView
android:id="@+id/electrum_scan_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/align_hint"
android:layout_marginTop="10dp"
android:layout_marginHorizontal="16dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:text="@string/scan_electrum_hint"/>
</RelativeLayout> </RelativeLayout>
</layout> </layout>

6
app/src/main/res/layout/tx_detail.xml

@ -28,7 +28,7 @@
</data> </data>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
tools:showIn="@layout/tx_confirm_fragment"> tools:showIn="@layout/tx_confirm_fragment">
@ -175,7 +175,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/receive_item" tools:listitem="@layout/tx_detail_item"
tools:itemCount="2" tools:itemCount="2"
android:visibility="gone" /> android:visibility="gone" />
<RelativeLayout <RelativeLayout
@ -223,7 +223,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/receive_item" tools:listitem="@layout/tx_detail_item"
android:visibility="gone" /> android:visibility="gone" />
<include <include

19
app/src/main/res/layout/receive_item.xml → app/src/main/res/layout/tx_detail_item.xml

@ -23,7 +23,7 @@
<variable <variable
name="item" name="item"
type="com.cobo.cold.ui.fragment.main.TxConfirmFragment.TransactionItem" /> type="com.cobo.cold.ui.fragment.main.TransactionItem" />
</data> </data>
<RelativeLayout <RelativeLayout
@ -38,7 +38,22 @@
android:layout_marginTop="14dp" android:layout_marginTop="14dp"
android:textColor="@color/white40" android:textColor="@color/white40"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:text="Output 1" />
<TextView
android:id="@+id/change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/label"
android:layout_alignStart="@id/label"
android:layout_marginTop="2dp"
android:background="@drawable/change_bg"
android:paddingHorizontal="5dp"
android:text="@string/change"
android:textColor="@color/black"
android:textSize="14sp"
android:visibility="gone"/>
<TextView <TextView
android:id="@+id/info" android:id="@+id/info"

2
app/src/main/res/layout/txn_list.xml

@ -40,7 +40,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="对TF卡内文件签名" android:text="@string/sign_txn_in_sdcard"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="15sp" /> android:textSize="15sp" />
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>

50
app/src/main/res/values-zh-rCN/strings.xml

@ -181,7 +181,7 @@
<string name="password_unlock">密码解锁</string> <string name="password_unlock">密码解锁</string>
<string name="forget_password">忘记密码?</string> <string name="forget_password">忘记密码?</string>
<string name="unlock">解锁</string> <string name="unlock">解锁</string>
<string name="eos_pubkey_sign_only">1. 若您已经创建了该公钥的地址,您可在Cobo 金库移动端进行收发币,用本设备签名时将自动同步账户信息\n2. 若您还未创建地址,您可邀请持有%s的朋友扫描以上公钥信息创建%s地址</string> <string name="eos_pubkey_sign_only">1. 若您已经创建了该公钥的地址,您可在 Cobo 金库移动端进行收发币,用本设备签名时将自动同步账户信息\n2. 若您还未创建地址,您可邀请持有%s的朋友扫描以上公钥信息创建%s地址</string>
<string name="secret_modal_title">请环顾四周</string> <string name="secret_modal_title">请环顾四周</string>
<string name="secret_modal_msg">任何人获得了助记词,意味着获取了您的资产,请确保周围无人,无摄像头。</string> <string name="secret_modal_msg">任何人获得了助记词,意味着获取了您的资产,请确保周围无人,无摄像头。</string>
<string name="select_mnemonic_count">选择助记词数量</string> <string name="select_mnemonic_count">选择助记词数量</string>
@ -255,7 +255,7 @@
<string name="tag">标签</string> <string name="tag">标签</string>
<string name="confirm_to_verify_mnemonic">确认,去验证</string> <string name="confirm_to_verify_mnemonic">确认,去验证</string>
<string name="pattern_unlock_hint">手势密码将用于解锁设备</string> <string name="pattern_unlock_hint">手势密码将用于解锁设备</string>
<string name="new_version_footer_hint">弹框关闭后,您可前往【菜单—设置—版本】,点击“可更新”也可触发更新操作</string> <string name="new_version_footer_hint">弹框关闭后,您可前往【菜单—设置—版本】,点击“可更新”</string>
<string name="factory_reset_warning">将清除该设备的所有数据,设备会恢复到默认的出厂状态,确认是否继续该操作。</string> <string name="factory_reset_warning">将清除该设备的所有数据,设备会恢复到默认的出厂状态,确认是否继续该操作。</string>
<string-array name="create_vault_step"> <string-array name="create_vault_step">
<item>"金库生成中…"</item> <item>"金库生成中…"</item>
@ -264,39 +264,43 @@
<item>"芯片安全检测中…"</item> <item>"芯片安全检测中…"</item>
<item>"写入安全芯片…"</item> <item>"写入安全芯片…"</item>
</string-array> </string-array>
<string name="export_to_electrum">导出观察钱包到Electrum</string> <string name="export_to_electrum">在 Electrum 创建观察钱包</string>
<string name="export_to_electrum_guide">1. 打开Electrum,设置钱包名称,点击下一步 \n2. 选择“标准钱包”后点击下一步 \n3. 选择“使用主密钥”后点击下一步,来到输入主公钥页面 \n4. 在输入主公钥页面点击照相机按钮,摄像头打开后扫描Cobo Vault主公钥生成的二维码 \n5. 扫描完成后点击下一步设置钱包密码 \n6. 密码设置完成后进入观察钱包</string> <string name="export_to_electrum_guide">1. 打开 Electrum,新建钱包 \n2. 选择“标准钱包” \n3. 选择“使用主公钥” \n4. 在输入主公钥页面点击照相机按钮,扫描主公钥二维码(点击下方按钮查看二维码)\n5. 设置钱包密码 \n6. 进入观察钱包</string>
<string name="export_xpub">导出钱包的主公钥进行适配</string> <string name="export_xpub">查看主公钥二维码</string>
<string name="export_to_electrum_guide_hint">导出观察钱包到Electrum指引</string> <string name="export_to_electrum_guide_hint">Electrum 创建指引</string>
<string name="send_address">发送地址 %d</string> <string name="send_address">发送地址 %d</string>
<string name="input">输入 %d</string> <string name="input">输入 %d</string>
<string name="output">输出 %d</string> <string name="output">输出 %d</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
<string name="read_sdcard">读取TF卡</string> <string name="read_sdcard">读取TF卡</string>
<string name="no_sdcard">未检测到TF卡</string> <string name="no_sdcard">未检测到TF卡</string>
<string name="no_sdcard_hint">确保TF卡成功插入到该设备中该设备仅支持FAT32格式,容量不能超过32GB</string> <string name="no_sdcard_hint">先插入TF卡(仅支持FAT32格式,容量不能超过32GB)</string>
<string name="no_unsigned_txn">未检测到待签文件</string> <string name="no_unsigned_txn">未检测到待签文件</string>
<string name="no_unsigned_txn_hint">请检查待签文件是否已成功保存在TF卡中</string> <string name="no_unsigned_txn_hint">请检查TF卡中是否存有待签文件</string>
<string name="electrum_broadcast_guide">Electrum广播交易引导</string> <string name="electrum_broadcast_guide">Electrum 广播指引</string>
<string name="electrum_broadcast_action_guide">打开Electrum,点击【工具--加载交易--从二维码】扫描Cobo Vault的二维码\n\n 若扫码苦难,可将已签名交易导出文件,从文件加载交易后进行广播</string> <string name="electrum_broadcast_action_guide">打开 Electrum,点击【工具--加载交易--从二维码】扫描 Cobo 金库\n\n若扫码困难,可将已签名交易导出文件,从文件加载交易。</string>
<string name="electrum_decode_txn_fail">解析失败</string> <string name="electrum_decode_txn_fail">解析失败</string>
<string name="electrum_import_xpub_guide_title">Electrum扫描主公钥二维码引导</string> <string name="electrum_import_xpub_guide_title">Electrum 扫描主公钥二维码</string>
<string name="electrum_import_xpub_action_guide">1. 打开Electrum,设置钱包名称,点击下一步。\n2. 选择“标准钱包”后点击下一步。\n3. 选择“使用主密钥”后点击下一步,来到输入主公钥页面。\n4. 在输入主公钥页面点击相机按钮,摄像头开启后,扫描主公钥二维码。\n\n若扫码困难,可将主密钥导出文本文件,Electrum打开该文件也可导入</string> <string name="electrum_import_xpub_action_guide">1. 打开 Electrum,新建钱包\n2. 选择“标准钱包”\n3. 选择“使用主公钥”\n4. 在输入主公钥页面点击相机按钮,扫描主公钥二维码\n\n若扫码困难,可将主公钥导出文件。</string>
<string name="error_txn_file">错误的txn文件</string> <string name="error_txn_file">请重新导出,导出时请不要更改文件内容或格式。</string>
<string name="export_signed_txn">导出文件进行广播</string> <string name="export_signed_txn">导出文件进行广播</string>
<string name="electrum_import_signed_txn">导出后,electrum--工具--加载交易--从文件,打开该文件</string> <string name="electrum_import_signed_txn">导出后,前往【electrum--工具--加载交易--从文件,打开该文件</string>
<string name="insert_sdcard_hint">请先插入TF卡(仅支持FAT32格式,容量不能超过32G)</string> <string name="insert_sdcard_hint">请先插入TF卡(仅支持FAT32格式,容量不能超过32G),再前往签名记录里导出文件。</string>
<string name="electrum_import_xpub_action">Electrum 从一个主密钥创建密钥库 页面打开该文件</string> <string name="electrum_import_xpub_action"> Electrum 输入主公钥页面打开该文件</string>
<string name="transaction_source_label">热端来源</string> <string name="transaction_source_label">热端钱包</string>
<string name="use_electrum_to_broadcast">使用Electrum扫描二维码进行广播</string> <string name="use_electrum_to_broadcast">请用 Electrum 扫描二维码进行广播</string>
<string name="transaction_from_electrum">该笔交易热端来源于Electrum</string> <string name="transaction_from_electrum">该笔交易热端来源于Electrum</string>
<string name="add_address">添加地址</string> <string name="add_address">添加地址</string>
<string name="use_electrum_scan_xpub">请用Electrum扫描主公钥二维码</string> <string name="use_electrum_scan_xpub">请用 Electrum 扫描主公钥二维码</string>
<string name="export_signed_txn_file">导出已签名文件</string> <string name="export_signed_txn_file">导出已签名文件</string>
<string name="export_signed_txn_action_guide">导出后,用Electrum进行广播\n前往【Electrum--工具--加载交易--从文件】,打开该文件后点击广播</string> <string name="export_signed_txn_action_guide">导出后,用 Electrum 进行广播\n前往【Electrum--工具--加载交易--从文件】,打开该文件</string>
<string name="export">导出</string> <string name="export">导出</string>
<string name="export_xpub_text_file">导出主密钥文本文件</string> <string name="export_xpub_text_file">导出主公钥文件</string>
<string name="file_name_label">该文件的文件名为:</string> <string name="file_name_label">该文件的文件名为:</string>
<string name="master_xpub">主公钥(P2SH)</string> <string name="master_xpub">主公钥(隔离见证兼容)</string>
<string name="electrum_qrcode_hint"><![CDATA[扫码困难?点击二维码进行放大<br>或<u>\"点击此处导出文件\"</u>]]></string> <string name="electrum_qrcode_hint"><![CDATA[扫码困难?点击二维码进行放大<br>或<u>\点击此处导出文件\</u>]]></string>
<string name="scan_electrum_hint">若扫描 Electrum 困难,可将待签交易导出成文件,前往【菜单--读取TF卡】对文件签名。</string>
<string name="sign_txn_in_sdcard">对文件签名</string>
<string name="master_pubkey_not_match">主公钥不匹配</string>
<string name="change">找零</string>
</resources> </resources>

77
app/src/main/res/values/strings.xml

@ -166,7 +166,7 @@
<string name="Update">Update</string> <string name="Update">Update</string>
<string name="draw_pattern_again">Please draw again to confirm</string> <string name="draw_pattern_again">Please draw again to confirm</string>
<string name="scan_to_auth">Scan QR Code</string> <string name="scan_to_auth">Scan QR Code</string>
<string name="signing_history">Signing History</string> <string name="signing_history">Signing Details</string>
<string name="add_coins">Add Cryptocurrency</string> <string name="add_coins">Add Cryptocurrency</string>
<string name="not_in_whitelist_reject">Receiving address unauthorized, transaction declined!</string> <string name="not_in_whitelist_reject">Receiving address unauthorized, transaction declined!</string>
<string name="invalid_mnemonic">recovery phrase incorrect, please check</string> <string name="invalid_mnemonic">recovery phrase incorrect, please check</string>
@ -278,7 +278,7 @@
<string name="tag">Memo</string> <string name="tag">Memo</string>
<string name="confirm_to_verify_mnemonic">Next Step</string> <string name="confirm_to_verify_mnemonic">Next Step</string>
<string name="pattern_unlock_hint">Pattern password is used to unlock the vault</string> <string name="pattern_unlock_hint">Pattern password is used to unlock the vault</string>
<string name="new_version_footer_hint">You can also go to [Menu--Settings--Version] and touch “Update Now” later.</string> <string name="new_version_footer_hint">You can also go to [Menu > Settings > Version] and touch “Update Now” later.</string>
<string name="factory_reset_warning">Are you sure you want to permanently wipe the device?</string> <string name="factory_reset_warning">Are you sure you want to permanently wipe the device?</string>
<string-array name="create_vault_step"> <string-array name="create_vault_step">
<item>"Creating vault…"</item> <item>"Creating vault…"</item>
@ -287,39 +287,44 @@
<item>"Secure Element verification in progress…"</item> <item>"Secure Element verification in progress…"</item>
<item>"Writing Secure Element…"</item> <item>"Writing Secure Element…"</item>
</string-array> </string-array>
<string name="export_to_electrum">导出观察钱包到Electrum</string> <string name="export_to_electrum">Create Watch-Only Wallet in Electrum</string>
<string name="export_to_electrum_guide">1. 打开Electrum,设置钱包名称,点击下一步 \n2. 选择“标准钱包”后点击下一步 \n3. 选择“使用主密钥”后点击下一步,来到输入主公钥页面 \n4. 在输入主公钥页面点击照相机按钮,摄像头打开后扫描Cobo Vault主公钥生成的二维码 \n5. 扫描完成后点击下一步设置钱包密码 \n6. 密码设置完成后进入观察钱包</string> <string name="export_to_electrum_guide">1. Open Electrum and create a new wallet. \n2. Choose “Standard wallet”. \n3. Choose “Use a master key”. \n4. Touch “Show Master Public Key” below then click the camera icon in the “Create keystore from a master key” window in Electrum to scan the QR code that displays on Cobo Vault.
<string name="export_xpub">导出钱包的主公钥进行适配</string> \n5. Set a password. \n6. Enter the watch-only wallet.</string>
<string name="export_to_electrum_guide_hint">导出观察钱包到Electrum指引</string> <string name="export_xpub">Show Master Public Key</string>
<string name="export_to_electrum_guide_hint">How to create watch-only wallet in Electrum</string>
<string name="send_address">From %d</string> <string name="send_address">From %d</string>
<string name="input">input %d</string> <string name="input">Input %d</string>
<string name="output">output %d</string> <string name="output">Output %d</string>
<string name="export_success">导出成功</string> <string name="export_success">Export Successful</string>
<string name="read_sdcard">读取TF卡</string> <string name="read_sdcard">MicroSD card</string>
<string name="no_sdcard">未检测到TF卡</string> <string name="no_sdcard">MicroSD Card Not Found</string>
<string name="no_sdcard_hint">请确保TF卡成功插入到该设备中该设备仅支持FAT32格式,容量不能超过32GB</string> <string name="no_sdcard_hint">Please make sure you have inserted a FAT32 format microSD card with capacity 32GB or less.</string>
<string name="no_unsigned_txn">未检测到待签文件</string> <string name="no_unsigned_txn">File Not Found</string>
<string name="no_unsigned_txn_hint">请检查待签文件是否已成功保存在TF卡中</string> <string name="no_unsigned_txn_hint">Please make sure the file you wish to sign was successfully saved to your microSD card.</string>
<string name="electrum_broadcast_guide">Electrum广播交易引导</string> <string name="electrum_broadcast_guide">How to Broadcast</string>
<string name="electrum_broadcast_action_guide">打开Electrum,点击【工具--加载交易--从二维码】扫描Cobo Vault的二维码\n\n 若扫码苦难,可将已签名交易导出文件,从文件加载交易后进行广播。</string> <string name="electrum_broadcast_action_guide">In Electrum, go to Tools > Load Transaction > From QR Code\n\nDifficulty scanning? You can export the signed transaction data as a file using a microSD card and broadcast the transaction from a file with Electrum.</string>
<string name="electrum_decode_txn_fail">解析失败</string> <string name="electrum_decode_txn_fail">Notice</string>
<string name="electrum_import_xpub_guide_title">Electrum扫描主公钥二维码引导</string> <string name="electrum_import_xpub_guide_title">How to scan QR code</string>
<string name="electrum_import_xpub_action_guide">1. 打开Electrum,设置钱包名称,点击下一步。\n2. 选择“标准钱包”后点击下一步。\n3. 选择“使用主密钥”后点击下一步,来到输入主公钥页面。\n4. 在输入主公钥页面点击相机按钮,摄像头开启后,扫描主公钥二维码。\n\n若扫码困难,可将主密钥导出文本文件,Electrum打开该文件也可导入</string> <string name="electrum_import_xpub_action_guide">1. Open Electrumand and create a new wallet.\n2. Choose “Standard wallet”.\n3. Choose “Use a master key”. \n4. Click the camera icon in the “Create keystore from a master key” window in Electrum to scan the QR code that displays on Cobo Vault.\n\nDifficulty scanning? You can also export the master public key file using a microSD card.</string>
<string name="error_txn_file">错误的txn文件</string> <string name="error_txn_file">Unable to parse file, please try exporting again.</string>
<string name="export_signed_txn">导出文件进行广播</string> <string name="export_signed_txn">Export File to Broadcast</string>
<string name="electrum_import_signed_txn">导出后,electrum--工具--加载交易--从文件,打开该文件</string> <string name="electrum_import_signed_txn">In Electrum, go to Tools > Load Transaction > From File to open the file. </string>
<string name="insert_sdcard_hint">请先插入TF卡(仅支持FAT32格式,容量不能超过32G)</string> <string name="insert_sdcard_hint">Please make sure you have inserted a FAT32 format microSD card with capacity 32GB or less, and that the signed transaction information has been uploaded to it.</string>
<string name="electrum_import_xpub_action">在Electrum 从一个主密钥创建密钥库 页面打开该文件</string> <string name="electrum_import_xpub_action">Open when inputting your master public key on Electrum.</string>
<string name="transaction_source_label">热端来源</string> <string name="transaction_source_label">Wallet</string>
<string name="use_electrum_to_broadcast">请使用Electrum扫描二维码进行广播</string> <string name="use_electrum_to_broadcast">Scan the QR code with Electrum to broadcast</string>
<string name="transaction_from_electrum">该笔交易热端来源于Electrum</string> <string name="transaction_from_electrum">This transaction is from Electrum</string>
<string name="add_address">添加地址</string> <string name="add_address">Add Address</string>
<string name="use_electrum_scan_xpub">请用Electrum扫描主公钥二维码</string> <string name="use_electrum_scan_xpub">Scan master public key using Electrum</string>
<string name="export_signed_txn_file">导出已签名文件</string> <string name="export_signed_txn_file">Export Signed File</string>
<string name="export_signed_txn_action_guide">导出后,用Electrum进行广播\n前往【Electrum--工具--加载交易--从文件】,打开该文件后点击广播</string> <string name="export_signed_txn_action_guide">How to broadcast with Electrum:\nIn Electrum, go to Tools > Load Transaction > From File. Open the file, and click “Broadcast”.</string>
<string name="export">导出</string> <string name="export">Export</string>
<string name="export_xpub_text_file">导出主密钥文本文件</string> <string name="export_xpub_text_file">Export Master Public Key File</string>
<string name="file_name_label">该文件的文件名为:</string> <string name="file_name_label">File name:</string>
<string name="master_xpub">主公钥(P2SH)</string> <string name="master_xpub">Master Public Key (Nested SegWit)</string>
<string name="electrum_qrcode_hint"><![CDATA[扫码困难?点击二维码进行放大<br>或 <u>\"点击此处导出文件\"</u>]]></string> <string name="electrum_qrcode_hint"><![CDATA[Difficulty scanning?Tap the QR code to enlarge<br>or <u>\"touch here to export via microSD.\"</u>]]></string>
<string name="scan_electrum_hint">Difficulty scanning Electrum? You can export the pending transactions as a file using a microSD card (Menu > MicroSD Card)</string>
<string name="sign_txn_in_sdcard">Files Awaiting Signature</string>
<string name="master_pubkey_not_match">Master Public Key not match</string>
<string name="change">Change</string>
</resources> </resources>

8
coinlib/src/main/java/com/cobo/coinlib/coins/BTC/Electrum/TxUtils.java

@ -90,6 +90,12 @@ public class TxUtils {
} }
public static boolean isMasterPublicKeyMatch(String xpub, ElectrumTx tx) {
return tx.getInputs()
.stream()
.allMatch(input -> xpub.equals(input.pubKey.xpub));
}
public static class PubKeyInfo { public static class PubKeyInfo {
public String xpub; public String xpub;
public List<Long> levels; public List<Long> levels;
@ -102,7 +108,7 @@ public class TxUtils {
// since bitcoinj current version not support ypub, // since bitcoinj current version not support ypub,
// current we only support p2sh-p2wpkh address type, // current we only support p2sh-p2wpkh address type,
// we use 49 index now // we use 49 index now
if(xpub.startsWith("ypub") || xpub.startsWith("xpub")){ if(xpub.startsWith("ypub") || xpub.startsWith("xpub")) {
this.hdPath = String.format(Locale.US,"M/49'/0'/0'/%d/%d", this.levels.get(0), this.levels.get(1)); this.hdPath = String.format(Locale.US,"M/49'/0'/0'/%d/%d", this.levels.get(0), this.levels.get(1));
} else if(xpub.startsWith("zpub")) { } else if(xpub.startsWith("zpub")) {
this.hdPath = String.format(Locale.US,"M/84'/0'/0'/%d/%d", this.levels.get(0), this.levels.get(1)); this.hdPath = String.format(Locale.US,"M/84'/0'/0'/%d/%d", this.levels.get(0), this.levels.get(1));

25
coinlib/src/test/java/com/cobo/coinlib/coin/ElectrumTxTest.java

@ -1,3 +1,20 @@
/*
* Copyright (c) 2020 Cobo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
*/
package com.cobo.coinlib.coin; package com.cobo.coinlib.coin;
import com.cobo.coinlib.coins.BTC.Electrum.ElectrumTx; import com.cobo.coinlib.coins.BTC.Electrum.ElectrumTx;
@ -63,7 +80,6 @@ public class ElectrumTxTest {
System.out.println(tx); System.out.println(tx);
} }
@Test @Test
public void testFinal() { public void testFinal() {
String hex = "02000000000101069494a45bd029cd27da7d9de90172702e2bfda40b4ad67bf3cc700c4212e4ac01000000171600141ea4ea0a12d7c2b07e9084d36abd03a2edd72798fdffffff02b80b00000000000017a91472ec889523c81e94fd9d65701417d76b371ae3a5874f0e00000000000017a9141c174319445d98f4da84b51da9c8feb686b4fb198702473044022008ab229e2878e339cfb5c4a8589d5f8072024c74413df0d113f26ca0d680660402202e541f490b1937c15cd45323cefbcba4d07eb7cab089fcdb44f795c92e2c6826012102a18c6e271a995b162348b4332b63f13bf031617192d6232e809210ad5d85c382e5940900"; String hex = "02000000000101069494a45bd029cd27da7d9de90172702e2bfda40b4ad67bf3cc700c4212e4ac01000000171600141ea4ea0a12d7c2b07e9084d36abd03a2edd72798fdffffff02b80b00000000000017a91472ec889523c81e94fd9d65701417d76b371ae3a5874f0e00000000000017a9141c174319445d98f4da84b51da9c8feb686b4fb198702473044022008ab229e2878e339cfb5c4a8589d5f8072024c74413df0d113f26ca0d680660402202e541f490b1937c15cd45323cefbcba4d07eb7cab089fcdb44f795c92e2c6826012102a18c6e271a995b162348b4332b63f13bf031617192d6232e809210ad5d85c382e5940900";
@ -72,4 +88,11 @@ public class ElectrumTxTest {
String hex1 = "020000000001025573b0158c7ce0b25bb3d0cff7344e369ca3dbcee2ce4d888840fff49ae17cd00100000017160014e9cf9131d9c02a3a02d246bb4297b5606c6cb2f9ffffffffe014dc2363486a66e337b802da70722a32065f7a9e4fd4daf0e067aa31f8e58900000000171600143007abdafe8f875c3d3b714428e7761494a71f6cffffffff02f78f01000000000017a914915892366a6cdf24afa6e1c480db2ad88c6337808798895b000000000017a914915892366a6cdf24afa6e1c480db2ad88c633780870247304402206b891a2c6b2a95bb7b7e25275544f3dd761269392dde98ab65c8f8187194ce0502203061881a7d0e92fd19ac68b77a1fee35d7b6b7b72e8fce369a35088c1b35cca7012103fbe02e16d35d3c9c6772c75ba5d0d1387573724082266ea667c53b9d00decd72024730440220236f1c70df027dd7c0c862c9b89b28cb3fca6a96d4c73f7f565b7cc4b0fdff6902202262c87aec91d1ad9e661d807d5f9a8aaacd8a634028a74013cc2141c8750d13012102f325a85902d264dbcb0cbe144e9b2463f8252bd0c51bc19666f4c82461e4baa200000000"; String hex1 = "020000000001025573b0158c7ce0b25bb3d0cff7344e369ca3dbcee2ce4d888840fff49ae17cd00100000017160014e9cf9131d9c02a3a02d246bb4297b5606c6cb2f9ffffffffe014dc2363486a66e337b802da70722a32065f7a9e4fd4daf0e067aa31f8e58900000000171600143007abdafe8f875c3d3b714428e7761494a71f6cffffffff02f78f01000000000017a914915892366a6cdf24afa6e1c480db2ad88c6337808798895b000000000017a914915892366a6cdf24afa6e1c480db2ad88c633780870247304402206b891a2c6b2a95bb7b7e25275544f3dd761269392dde98ab65c8f8187194ce0502203061881a7d0e92fd19ac68b77a1fee35d7b6b7b72e8fce369a35088c1b35cca7012103fbe02e16d35d3c9c6772c75ba5d0d1387573724082266ea667c53b9d00decd72024730440220236f1c70df027dd7c0c862c9b89b28cb3fca6a96d4c73f7f565b7cc4b0fdff6902202262c87aec91d1ad9e661d807d5f9a8aaacd8a634028a74013cc2141c8750d13012102f325a85902d264dbcb0cbe144e9b2463f8252bd0c51bc19666f4c82461e4baa200000000";
assertTrue(ElectrumTx.isFinal(hex1)); assertTrue(ElectrumTx.isFinal(hex1));
} }
@Test
public void testMasterPublicKeyMatched() throws ElectrumTx.SerializationException {
String hex = "45505446ff00020000000001019c27c79ffc9ea5773030e2f31c7f4852fdec0a9d5d56bfd96bd5635faa9da1050000000017160014926abdcbe75e28d62ba442d0f2033eded710bd91fdffffff02b30b00000000000017a914f2c0b14d07c5ac95185487ba27d4256d8e83e76887102700000000000017a914460effb9083c27112687e637a29f0893e110615d87feffffffff4d3700000000000000000201ff53ff049d7cb203e0a197408000000043e91b2cfe457819db8a86101dbf60f9310cd3d95dafb7f4dc9fc3d816bb6c8a0276f4cca4bd18335cc482cf0c6dce442ecdb9eeda4ef8909e0c8beec6a35d416c00000900a09a0900";
ElectrumTx electrumTx = ElectrumTx.parse(Hex.decode(hex));
assertTrue(TxUtils.isMasterPublicKeyMatch("xpub6DJXnFtECztcYS2Gm5pyPpbbxuqMJ7BvqBtrTH1ZBjXraQJAgnHsgzBn9Q6HvXieoT7TNb1ynkyWmQDF64GCaybUYnXhof7McNZhfmFRgp3",electrumTx));
}
} }

Loading…
Cancel
Save